天天看點

LINQ學習之旅 C#3.0新特性(一)

一:C#3.0新語言的特性

  • 自動屬性(Auto-Implemented Properties)
  • 隐含類型局部變量(Local Variable Type Inference)
  • 匿名類型(Anonymous Types)
  • 對象與集合初始化器(Object and Collection Initializers)
  • 擴充方法(Extension Methods)
  • Lambda表達式和Lambda表達式樹 (Lambda Expression and Lambda Expression Trees)

1.自動屬性

    自動屬性可以避免原來這樣我們手工聲明一個私有成員變量以及編寫get/set邏輯,在VS2008中可以像下面這樣編寫一個類,編譯器會自動地生成私有變量和預設的get/set 操作。你也可以分别定義get和set的“protected”等通路級别。

2.隐含類型局部變量

C#3.0引進了var這個新關鍵字,在聲明局部變量時可用于替代原先的類型名,即當一個變量聲明辨別為var類型并且該範圍域中沒有var名稱類型存在,那麼這個聲明就稱為隐含類型局部變量。

        隐含類型局部變量要點

  1. var為關鍵字,可以根據後面的初始化語句自動推斷類型,這個類型為強類型。
  2. 初始化語句必須為表達式,不可以為空。且編譯時可以推斷類型。一旦初始化之後,隻可以存儲這種類型。
  3. var聲明的僅限于局部變量,不可用于字段。亦可以用于for,foreach,using 等語句中。
  4. 數組也可以作為隐含類型。
  5. 初始化語句不能是一個自身的對象或者集合初始化器,但是他可以是包含一個對象或者初始化器的一個new表達式。
  6. 如果局部變量聲明包含了多個聲明符,其類型必須相同。

3.匿名類型

     匿名類型允許定義行内類型,無須顯式定義類型。常和var配合使用來聲明匿名類型。

var p1 = new { Id = 1, Name = "YJingLee", Age = 22 };//屬性也不需要申明
var p2 = new { Id = 2, Name = "XieQing", Age = 25 };
p1 = p2;//p1,p2結構相同,可以互相指派      

 在這裡編譯器會認為p1,p2相當于:

public class SomeType
{
    public int Id { get; set; }
    public string Name { get; set; }
    public int Age { get; set; }
}      

 那麼數組怎麼定義呢?使用"new[]"關鍵字來聲明數組,加上數組的初始值清單。像這樣:

var intArray = new[] { 2, 3, 5, 6 };
var strArray = new[] { "Hello", "World" };
var anonymousTypeArray = new[] 
{ 
    new { Name = "YJingLee", Age = 22 }, 
    new { Name = "XieQing", Age = 25 } 
};
var a = intArray[0];
var b = strArray[0];
var c = anonymousTypeArray[1].Name;      

匿名類型要點

  1. 可以使用new關鍵字調用匿名初始化器建立一個匿名類型的對象。
  2. 匿名類型直接繼承自System. Object。
  3. 匿名類型的成員是編譯器根據初始化器推斷而來的一些讀寫屬性。

4.對象與集合初始化器

對象初始化器 (Object Initializers) :

使得原來幾行的屬性指派操作可以在一行完成。我們可以這樣簡化:像這樣,對象初始化器由一系列成員對象組成,其對象必須初始化,用逗号間隔,使用{}封閉

User user = new User { Id = 1, Name = "YJingLee", Age = 22 };      

 又例如,我把二個人加到一個基于泛型的類型為User的List集合中:

List<User> user = new List<User>{
    new User{Id=1,Name="YJingLee",Age=22},
    new User{Id=2,Name="XieQing",Age=25},
};      

 如果有相同名字和類型的兩個對象初始化器将會産生相同的執行個體,可以互相指派。例如:

User user = new User { Id = 1, Name = "YJingLee", Age = 22 };
User user2 = new User { Id = 2, Name = "XieQing", Age = 25 };
user = user2;      

 除了在初始化類時設定簡單的屬性值外,對象初始化器特性也允許我們設定更複雜的嵌套(nested)屬性類型。例如我們可以在上面定義的User類型同時擁有一個屬于Address類型的叫“Address”的屬性:

User user = new User
{
    Id = 1,
    Name = "YJingLee",
    Age = 22,
    Address = new Address
    {
        City = "NanJing",
        Zip = 21000
    }
};      

 集合初始化器(Collection Initializers):

集合初始化器由一系列集合對象組成,用逗号間隔,使用{}封閉。

集合初始化器可以簡化把幾個對象一起添加到一個集合,編譯器會自動為你做集合插入操作。例如我把七個數加到一個基于泛型的類型為int的List集合中

List<int> num = new List<int> { 0, 1, 2, 6, 7, 8, 9 };      

對象與集合初始化器要點

  1. 對象初始化器實際上利用了編譯器對對象中對外可見的字段和屬性進行按序指派。
  2. 對象初始化器允許隻給一部分屬性指派,包括internal通路級别
  3. 對象初始化器可以結合構造函數一起使用,并且構造函數初始化先于對象初始化器執行。
  4. 集合初始化器會對初始化器中的元素進行按序調用ICollection<T>.Add(T)方法。
  5. 注意對象初始化器和集合初始化器中成員的可見性和調用順序。
  6. 對象與集合初始化器同樣是一種編譯時技術。

5.擴充方法

往往我們需要對CLR類型進行一些操作,但苦于無法擴充CLR類型的方法,隻能建立一些helper方法,或者繼承類。我們來修改上面的User類:

public class User
{
    public int Id { get; set; }
    public string Name { get; set; }
    public int Age { get; set; }
    public string Read()
    {
        return "Id:" + Id + "姓名:" + Name + "年齡:" + Age;
    }      

 然後調用:

var user = new { Id = 1, Name = "YJingLee", Age = 22 };
var str = user.Read();      

現在有了擴充方法就友善多了。

擴充方法允許開發人員往一個現有的CLR類型的公開契約(contract)中添加新的方法,而不用生成子類或者重新編譯原來的類型。擴充方法有助于把今天動态語言中流行的對duck typing的支援之靈活性,與強類型語言之性能和編譯時驗證融合起來。

擴充方法是可以通過使用執行個體方法文法調用的靜态方法。效果上,使得附加的方法擴充已存在類型和構造類型成為可能。他可以對現有類功能進行擴充,進而使該類型的執行個體具有更多的方法(功能)。

擴充方法允許我們在不改變源代碼的情況下擴充(即添加不能修改)現有類型中的執行個體方法。

擴充方法給我們一個怎樣的思路呢?我們一步一步做一下!

首先聲明擴充方法:通過指定關鍵字this修飾方法的第一個參數。注意擴充方法僅可聲明在靜态類中。擴充方法具備所有正常靜态方法的所有能力,可以使用執行個體方法文法來調用。接着就可以調用擴充方法了。下面通過一個具體的執行個體分析一下:

例如我們要檢查一個字元串變量是否是合法的電子郵件位址?在.Net2.0架構下像這樣:

var email = "[email protected]";
if (EmailValidator.IsValid(email))
{
    Response.Write("YJingLee提示:這是一個正确的郵件位址");
}      

 而使用擴充方法的話,我可以添加“IsValidEmailAddress()”方法到string類本身中去,該方法傳回目前字元串執行個體是否是個合法的字元串。

if (email.IsValidEmailAddress())
{
    Response.Write("YJingLee提示:這是一個正确的郵件位址");
}      

 我們是怎麼把這個IsValidEmailAddress()方法添加到現有的string類裡去的呢?先定義一個靜态類,再定義“IsValidEmailAddress”這個靜态的法來實作的。

public static class Extensions//靜态類
{
    public static bool IsValidEmailAddress(this string s)
    //靜态方法和this
    {
        Regex regex = new Regex(@"^[\w-\.]+@([\w-]+\.)+[\w-]{2,4}$");
        return regex.IsMatch(s);
    }
}      

注意,上面的靜态方法在第一個類型是string的參數變量前有個“this”關鍵詞,這告訴編譯器,這個特定的擴充方法應該添加到類型為 “string”的對象中去。然後在IsValidEmailAddress()方法實作裡,我可以通路調用該方法的實際string執行個體的所有公開屬性 /方法/事件,取決于它是否是合法電子郵件位址來傳回true/false。

擴充方法不僅能夠應用到個别類型上,也能應用到.NET架構中任何基類或接口上。即可用于整個.NET架構豐富的可組合的架構層擴充。

擴充方法要點

  1. 擴充方法的本質為将執行個體方法調用在編譯期改變為靜态類中的靜态方法調用。事實上,它确實擁有靜态方法所具有的所有功能。
  2. 擴充方法的作用域是整個namespace可見的,并且可以通過using namespace來導入其它命名空間中的擴充方法。
  3. 擴充方法的優先級:現有執行個體方法優先級最高,其次為最近的namespace下的靜态類的靜态方法,最後為較遠的namespace下的靜态類的靜态方法。
  4. 擴充方法是一種編譯時技術,注意與反射等運作時技術進行差別,并慎重使用。

 6.Lambda表達式和Lambda表達式樹

  Lambda表達式

   我們從“所有字元串查找包含YJingLee子字元串”說起。在C# 2.0中,匿名方法允許我們以内聯的方式來實作委托執行個體,它提供強大的函數式程式設計語言,但是标記顯得相當的冗長和帶有強制性。我們使用C# 2.0 中的匿名方法查找,代碼如下:

var inString = list.FindAll(delegate(string s)
{ return s.Indexof("YJingLee") >= 0; });      

 現在可以使用C# 3.0帶來的Lambda表達式允許我們使用一種更接近人的思維、更自然的方式來實作類似于匿名方法同樣的效果,看下面的代碼多麼簡潔:

var inString = list.FindAll(s => s.Indexof("YJingLee") >= 0);      

Lambda表達式格式:(參數清單)=>表達式或語句塊

具體意義:定義Lambda接受參數清單,運作表達式或語句塊傳回表達式或語句塊的值傳給這個參數清單。

Lambda表達式參數類型可以是隐式類型或顯式類型。在顯式清單中,每個參數的類型是顯式指定的,在隐式清單中,參數的類型由Lambda表達式出現的語境自動推斷類型。

Lambda表達式的參數清單可以有一個或多個參數,或者無參數。在有單一的隐型參數的lambda表達式中,圓括号可以從參數清單中省略。

例如:

(x, y) => x * y;//多參數,隐式類型=>表達式
x => x * 10;//單參數,隐式類型=>表達式
x => { return x * 10; }; //單參數,隐式類型=>語句塊
(int x) => x * 10;//單參數,顯式類型=>表達式
(int x) => { return x * 10; };//單參數,顯式類型=>語句塊
() => Console.WriteLine(); //無參數      

 下面看這個例子:

我們寫了一個User類及增加了2個人,接下來,我們使用由LINQ提供的新的Where和Average方法來傳回集合中的人的一個子集,以及計算這個集合中的人的平均年齡:

List<User> user = new List<User>{
    new User{Id=1,Name="YJingLee",Age=22},
    new User{Id=2,Name="XieQing",Age=25},
};
//擷取特定人時所用的過濾條件,p參數屬于User類型
var results = user.Where(p => p.Name == "YJingLee").ToList();
//用User對象的Age值計算平均年齡
var average = user.Average(p => p.Age);      

 對這個Lambda表達式做個簡要分析:

var resultsdelegate = user.Where(delegate(User p)
{
    return p.Name == "YJingLee";// 傳回一個布爾值
});
var averagedelegate = user.Average(delegate(User p)
{
    return p.Age;
});      

   Lambda表達式L可以被轉換為委托類型D,需要滿足以下條件:

L的參數類型要與D的參數個數相等,類型相同,傳回類型相同,無論是表達式,還是語句塊。注意隐式類型要參與類型辨析。

 Lambda表達式樹

Lambda表達式樹允許我們像處理資料(比如讀取,修改)一樣來處理Lambda表達式。我以一個例子簡單說明:

Expression<Func<int, bool>> filter = n => (n * 3) < 5;
BinaryExpression lt = (BinaryExpression)filter.Body;
BinaryExpression mult = (BinaryExpression)lt.Left;
ParameterExpression en = (ParameterExpression)mult.Left;
ConstantExpression three = (ConstantExpression)mult.Right;
ConstantExpression five = (ConstantExpression)lt.Right;
var One = filter.Compile();
Console.WriteLine("Result: {0},{1}", One(5), One(1));
Console.WriteLine("({0} ({1} {2} {3}) {4})", lt.NodeType,
         mult.NodeType, en.Name, three.Value, five.Value);      

Lambda表達式和Lambda表達式樹要點

  1. Lambda表達式的參數類型可以忽略,因為可以根據使用的上下文進行推斷。
  2. Lambda表達式的主體(body)可以是表達式,也可以是語句塊。
  3. Lambda表達式傳入的實參将參與類型推斷,以及方法重載辨析。
  4. Lambda表達式和表達式體可以被轉換為表達式樹。
  5. 表達式樹允許lambda表達式能夠代表資料結構替代表示為執行代碼。