聲明 文檔轉載自:http://www.cnblogs.com/liulun/archive/2013/02/26/2909985.html 在說LINQ之前必須先說說幾個重要的C#語言特性 一:與LINQ有關的語言特性 1.隱式類型 (1)源起 在隱式類型出現之前, 我們在聲明一個變數的時候, 總 ...
聲明----文檔轉載自:http://www.cnblogs.com/liulun/archive/2013/02/26/2909985.html
在說LINQ之前必須先說說幾個重要的C#語言特性
一:與LINQ有關的語言特性
1.隱式類型
(1)源起
在隱式類型出現之前,
我們在聲明一個變數的時候,
總是要為一個變數指定他的類型
甚至在foreach一個集合的時候,
也要為遍歷的集合的元素,指定變數的類型
隱式類型的出現,
程式員就不用再做這個工作了。
(2)使用方法
來看下麵的代碼:
var a = 1; //int a = 1;
var b = "123";//string b = "123";
var myObj = new MyObj();//MyObj myObj = new MyObj()
上面的每行代碼,與每行代碼後面的註釋,起到的作用是完全一樣的
也就是說,在聲明一個變數(並且同時給它賦值)的時候,完全不用指定變數的類型,只要一個var就解決問題了
(3)你擔心這樣寫會降低性能嗎?
我可以負責任的告訴你,這樣寫不會影響性能!
上面的代碼和註釋里的代碼,編譯後產生的IL代碼(中間語言代碼)是完全一樣的
(編譯器根據變數的值,推導出變數的類型,才產生的IL代碼)
(4)這個關鍵字的好處:
你不用在聲明一個變數並給這個變數賦值的時候,寫兩次變數類型
(這一點真的為開發者節省了很多時間)
在foreach一個集合的時候,可以使用var關鍵字來代替書寫迴圈變數的類型
(5)註意事項
你不能用var關鍵字聲明一個變數而不給它賦值
因為編譯器無法推導出你這個變數是什麼類型的。
2.匿名類型
(1)源起
創建一個對象,一定要先定義這個對象的類型嗎?
不一定的!
來看看這段代碼
(2)使用
var obj = new {Guid.Empty, myTitle = "匿名類型", myOtherParam = new int[] { 1, 2, 3, 4 } };
Console.WriteLine(obj.Empty);//另一個對象的屬性名字,被原封不動的拷貝到匿名對象中來了。
Console.WriteLine(obj.myTitle);
Console.ReadKey();
new關鍵字之後就直接為對象定義了屬性,並且為這些屬性賦值
而且,對象創建出來之後,在創建對象的方法中,還可以暢通無阻的訪問對象的屬性
當把一個對象的屬性拷貝到匿名對象中時,可以不用顯示的指定屬性的名字,這時原始屬性的名字會被“拷貝”到匿名對象中
(3)註意
如果你監視變數obj,你會發現,obj的類型是Anonymous Type類型的
不要試圖在創建匿名對象的方法外面去訪問對象的屬性!
(4)優點
這個特性在網站開發中,序列化和反序列化JSON對象時很有用
3.自動屬性
(1)源起
為一個類型定義屬性,我們一般都寫如下的代碼:
public class MyObj2
{
private Guid _id;
private string _Title;
public Guid id
{
get { return _id; }
set { _id = value; }
}
public string Title
{
get { return _Title; }
set { _Title = value; }
}
}
但很多時候,這些私有變數對我們一點用處也沒有,比如對象關係映射中的實體類。
自C#3.0引入了自動實現的屬性,
以上代碼可以寫成如下形式:
(2)使用
public class MyObj
{
public Guid id { get; set; }
public string Title { get; set; }
}
這個特性也和var關鍵字一樣,是編譯器幫我們做了工作,不會影響性能的
4.初始化器
(1)源起
我們創建一個對象並給對象的屬性賦值,代碼一般寫成下麵的樣子
var myObj = new MyObj();
myObj.id = Guid.NewGuid();
myObj.Title = "allen";
自C#3.0引入了對象初始化器,
代碼可以寫成如下的樣子
(2)使用
var myObj1 = new MyObj() { id = Guid.NewGuid(), Title = "allen" };
如果一個對象是有參數的構造函數
那麼代碼看起來就像這樣
var myObj1 = new MyObj ("allen") { id = Guid.NewGuid(), Title = "allen" };
集合初始化器的樣例代碼如下:
var arr = new List<int>() { 1, 2, 3, 4, 5, 6 };
(3)優點
我個人認為:這個特性不是那麼amazing,
這跟我的編碼習慣有關,集合初始化器也就罷了,
真的不習慣用對象初始化器初始化一個對象!
5.委托
(1)使用
我們先來看一個簡單的委托代碼
delegate Boolean moreOrlessDelgate(int item);
class Program
{
static void Main(string[] args)
{
var arr = new List<int>() { 1, 2, 3, 4, 5, 6,7,8 };
var d1 = new moreOrlessDelgate(More);
Print(arr, d1);
Console.WriteLine("OK");
var d2 = new moreOrlessDelgate(Less);
Print(arr, d2);
Console.WriteLine("OK");
Console.ReadKey();
}
static void Print(List<int> arr,moreOrlessDelgate dl)
{
foreach (var item in arr)
{
if (dl(item))
{
Console.WriteLine(item);
}
}
}
static bool More(int item)
{
if (item > 3)
{
return true;
}
return false;
}
static bool Less(int item)
{
if (item < 3)
{
return true;
}
return false;
}
}
這段代碼中
<1>首先定義了一個委托類型
delegate Boolean moreOrlessDelgate(int item);
你看到了,委托和類是一個級別的,確實是這樣:委托是一種類型
和class標誌的類型不一樣,這種類型代表某一類方法。
這一句代碼的意思是:moreOrlessDelgate這個類型代表返回值為布爾類型,輸入參數為整形的方法
<2>有類型就會有類型的實例
var d1 = new moreOrlessDelgate(More);
var d2 = new moreOrlessDelgate(Less);
這兩句就是創建moreOrlessDelgate類型實例的代碼,
它們的輸入參數是兩個方法
<3>有了類型的實例,就會有操作實例的代碼
Print(arr, d1);
Print(arr, d2);
我們把前面兩個實例傳遞給了Print方法
這個方法的第二個參數就是moreOrlessDelgate類型的
在Print方法內用如下代碼,調用委托類型實例所指向的方法
dl(item)
6.泛型
(1)為什麼要有泛型
假設你是一個方法的設計者,
這個方法有一個傳入參數,有一個返回值。
但你並不知道這個參數和返回值是什麼類型的,
如果沒有泛型,你可能把參數和返回值的類型都設定為Object了
那時,你心裡肯定在想:反正一切都是對象,一切的基類都是Object
沒錯!你是對的!
這個方法的消費者,會把他的對象傳進來(有可能會做一次裝箱操作)
並且得到一個Object的返回值,他再把這個返回值強制類型轉化為他需要的類型
除了裝箱和類型轉化時的性能損耗外,代碼工作的很好!
那麼這些性能損耗能避免掉嗎?
有泛型之後就可以了!
(2)使用
<1>使用簡單的泛型
先來看下麵的代碼:
var intList = new List<int>() { 1,2,3};
intList.Add(4);
intList.Insert(0, 5);
foreach (var item in intList)
{
Console.WriteLine(item);
}
Console.ReadKey();
在上面這段代碼中我們聲明瞭一個存儲int類型的List容器
並迴圈列印出了容器里的值
註意:如果這裡使用Hashtable、Queue或者Stack等非泛型的容器
就會導致裝箱操作,損耗性能。因為這些容器只能存儲Object類型的數據
<2>泛型類型
List<T>、Dictionary<TKey, TValue>等泛型類型都是.net類庫定義好並提供給我們使用的
但在實際開發中,我們也經常需要定義自己的泛型類型
來看下麵的代碼:
public static class SomethingFactory<T>
{
public static T InitInstance(T inObj)
{
if (false)//你的判斷條件
{
//do what you want...
return inObj;
}
return default(T);
}
}
這段代碼的消費者如下:
var a1 = SomethingFactory<int>.InitInstance(12);
Console.WriteLine(a1);
Console.ReadKey();
輸出的結果為0
這就是一個自定義的靜態泛型類型,
此類型中的靜態方法InitInstance對傳入的參數做了一個判斷
如果條件成立,則對傳入參數進行操作之後並把它返回
如果條件不成立,則返回一個空值
註意:
[1]
傳入參數必須為指定的類型,
因為我們在使用這個泛型類型的時候,已經規定好它能接收什麼類型的參數
但在設計這個泛型的時候,我們並不知道使用者將傳遞什麼類型的參數進來
[2]
如果你想返回T類型的空值,那麼請用default(T)這種形式
因為你不知道T是值類型還是引用類型,所以別擅自用null
<3>泛型約束
很多時候我們不希望使用者太過自由
我們希望他們在使用我們設計的泛型類型時
不要很隨意的傳入任何類型
對於泛型類型的設計者來說,要求使用者傳入指定的類型是很有必要的
因為我們只有知道他傳入了什麼東西,才方便對這個東西做操作
讓我們來給上面設計的泛型類型加一個泛型約束
代碼如下:
public static class SomethingFactory<T> where T:MyObj
這樣在使用SomethingFactory的時候就只能傳入MyObj類型或MyObj的派生類型啦
註意:
還可以寫成這樣
where T:MyObj,new()
來約束傳入的類型必須有一個構造函數。
(3)泛型的好處
<1>演算法的重用
想想看:list類型的排序演算法,對所有類型的list集合都是有用的
<2>類型安全
<3>提升性能
沒有類型轉化了,一方面保證類型安全,另一方面保證性能提升
<4>可讀性更好
這一點就不解釋了
7.泛型委托
(1)源起
委托需要定義delgate類型
使用起來頗多不便
而且委托本就代表某一類方法
開發人員經常使用的委托基本可以歸為三類,
哪三類呢?
請看下麵:
(2)使用
<1>Predicate泛型委托
把上面例子中d1和d2賦值的兩行代碼改為如下:
//var d1 = new moreOrlessDelgate(More);
var d1 = new Predicate<int>(More);
//var d2 = new moreOrlessDelgate(Less);
var d2 = new Predicate<int>(Less);
把Print方法的方法簽名改為如下:
//static void Print(List<int> arr, moreOrlessDelgate<int> dl)
static void Print(List<int> arr, Predicate<int> dl)
然後再運行方法,控制台輸出的結果和原來的結果是一模一樣的。
那麼Predicate到底是什麼呢?
來看看他的定義:
// 摘要:
// 表示定義一組條件並確定指定對象是否符合這些條件的方法。
//
// 參數:
// obj:
// 要按照由此委托表示的方法中定義的條件進行比較的對象。
//
// 類型參數:
// T:
// 要比較的對象的類型。
//
// 返回結果:
// 如果 obj 符合由此委托表示的方法中定義的條件,則為 true;否則為 false。
public delegate bool Predicate<in T>(T obj);
看到這個定義,我們大致明白了。
.net為我們定義了一個委托,
這個委托表示的方法需要傳入一個T類型的參數,並且需要返回一個bool類型的返回值
有了它,我們就不用再定義moreOrlessDelgate委托了,
而且,我們定義的moreOrlessDelgate只能搞int類型的參數,
Predicate卻不一樣,它可以搞任意類型的參數
但它規定的還是太死了,它必須有一個返回值,而且必須是布爾類型的,同時,它必須有一個輸入參數
除了Predicate泛型委托,.net還為我們定義了Action和Func兩個泛型委托
<2>Action泛型委托
Action泛型委托限制的就不那麼死了,
他代表了一類方法:
可以有0個到16個輸入參數,
輸入參數的類型是不確定的,
但不能有返回值,
來看個例子:
var d3 = new Action(noParamNoReturnAction);
var d4 = new Action<int, string>(twoParamNoReturnAction);
註意:尖括弧中int和string為方法的輸入參數
static void noParamNoReturnAction()
{
//do what you want
}
static void twoParamNoReturnAction(int a, string b)
{
//do what you want
}
<3>Func泛型委托
為了彌補Action泛型委托,不能返回值的不足
.net提供了Func泛型委托,
相同的是它也是最多0到16個輸入參數,參數類型由使用者確定
不同的是它規定要有一個返回值,返回值的類型也由使用者確定
如下示例:
var d5 = new Func<int, string>(oneParamOneReturnFunc);
註意:string類型(最後一個泛型類型)是方法的返回值類型
static string oneParamOneReturnFunc(int a)
{
//do what you want
return string.Empty;
}
8.匿名方法
(1)源起
在上面的例子中
為了得到序列中較大的值
我們定義了一個More方法
var d1 = new Predicate<int>(More);
然而這個方法,沒有太多邏輯(實際編程過程中,如果邏輯較多,確實應該獨立一個方法出來)
那麼能不能把More方法中的邏輯,直接寫出來呢?
C#2.0之後就可以了,
請看下麵的代碼:
(2)使用
var arr = new List<int>() { 1, 2, 3, 4, 5, 6, 7, 8 };
//var d1 = new moreOrlessDelgate(More);
//var d1 = new Predicate<int>(More);
var d1 = new Predicate<int>(delegate(int item)
{
//可以訪問當前上下文中的變數
Console.WriteLine(arr.Count);
if (item > 3)
{
return true;
}
return false;
});
Print(arr, d1);
Console.WriteLine("OK");
我們傳遞了一個代碼塊給Predicate的構造函數
其實這個代碼塊就是More函數的邏輯
(3)好處
<1>代碼可讀性更好
<2>可以訪問當前上下文中的變數
這個用處非常大,
如果我們仍舊用原來的More函數
想要訪問arr變數,勢必要把arr寫成類級別的私有變數了
用匿名函數的話,就不用這麼做了。
9.Lambda表達式
(1)源起
.net的設計者發現在使用匿名方法時,
仍舊有一些多餘的字母或單詞的編碼工作
比如delegate關鍵字
於是進一步簡化了匿名方法的寫法
(2)使用
List<int> arr = new List<int>() { 1, 2, 3, 4, 5, 6, 7 };
arr.ForEach(new Action<int>(delegate(int a) { Console.WriteLine(a); }));
arr.ForEach(new Action<int>(a => Console.WriteLine(a)));
匿名方法的代碼如下:
delegate(int a) { Console.WriteLine(a); }
使用lambda表達式的代碼如下:
a => Console.WriteLine(a)
這裡解釋一下這個lambda表達式
<1>
a是輸入參數,編譯器可以自動推斷出它是什麼類型的,
如果沒有輸入參數,可以寫成這樣:
() => Console.WriteLine("ddd")
<2>
=>是lambda操作符
<3>
Console.WriteLine(a)是要執行的語句。
如果是多條語句的話,可以用{}包起來。
如果需要返回值的話,可以直接寫return語句
10.擴展方法
(1)源起
如果想給一個類型增加行為,一定要通過繼承的方式實現嗎?
不一定的!
(2)使用
來看看這段代碼:
public static void PrintString(this String val)
{
Console.WriteLine(val);
}
消費這段代碼的代碼如下:
var a = "aaa";
a.PrintString();
Console.ReadKey();
我想你看到擴展方法的威力了。
本來string類型沒有PrintString方法
但通過我們上面的代碼,就給string類型"擴展"了一個PrintString方法
(1)先決條件
<1>擴展方法必須在一個非嵌套、非泛型的靜態類中定義
<2>擴展方法必須是一個靜態方法
<3>擴展方法至少要有一個參數
<4>第一個參數必須附加this關鍵字作為首碼
<5>第一個參數不能有其他修飾符(比如ref或者out)
<6>第一個參數不能是指針類型
(2)註意事項
<1>跟前面提到的幾個特性一樣,擴展方法只會增加編譯器的工作,不會影響性能(用繼承的方式為一個類型增加特性反而會影響性能)
<2>如果原來的類中有一個方法,跟你的擴展方法一樣(至少用起來是一樣),那麼你的擴展方法獎不會被調用,編譯器也不會提示你
<3>擴展方法太強大了,會影響架構、模式、可讀性等等等等....
11.迭代器
· (1)使用
我們每次針對集合類型編寫foreach代碼塊,都是在使用迭代器
這些集合類型都實現了IEnumerable介面
都有一個GetEnumerator方法
但對於數組類型就不是這樣
編譯器把針對數組類型的foreach代碼塊
替換成了for代碼塊。
來看看List的類型簽名:
public class List<T> : IList<T>, ICollection<T>, IEnumerable<T>, IList, ICollection, IEnumerable
IEnumerable介面,只定義了一個方法就是:
IEnumerator<T> GetEnumerator();
(2)迭代器的優點:
假設我們需要遍歷一個龐大的集合
只要集合中的某一個元素滿足條件
就完成了任務
你認為需要把這個龐大的集合全部載入到記憶體中來嗎?
當然不用(C#3.0之後就不用了)!
來看看這段代碼:
static IEnumerable<int> GetIterator()
{
Console.WriteLine("迭代器返回了1");
yield return 1;
Console.WriteLine("迭代器返回了2");
yield return 2;
Console.WriteLine("迭代器返回了3");
yield return 3;
}
消費這個函數的代碼如下:
foreach (var i in GetIterator())
{
if (i == 2)
{
break;
}
Console.WriteLine(i);
}
Console.ReadKey();
輸出結果為:
迭代器返回了1
1
迭代器返回了2
大家可以看到:
當迭代器返回2之後,foreach就退出了
並沒有輸出“迭代器返回了3”
也就是說下麵的工作沒有做。
(3)yield 關鍵字
MSDN中的解釋如下:
在迭代器塊中用於向枚舉數對象提供值或發出迭代結束信號。
也就是說,我們可以在生成迭代器的時候,來確定什麼時候終結迭代邏輯
上面的代碼可以改成如下形式:
static IEnumerable<int> GetIterator()
{
Console.WriteLine("迭代器返回了1");
yield return 1;
Console.WriteLine("迭代器返回了2");
yield break;
Console.WriteLine("迭代器返回了3");
yield return 3;
}
(4)註意事項
<1>做foreach迴圈時多考慮線程安全性
在foreach時不要試圖對被遍歷的集合進行remove和add等操作
任何集合,即使被標記為線程安全的,在foreach的時候,增加項和移除項的操作都會導致異常
(我在這裡犯過錯)
<2>IEnumerable介面是LINQ特性的核心介面
只有實現了IEnumerable介面的集合
才能執行相關的LINQ操作,比如select,where等
這些操作,我們接下來會講到。
二:LINQ
1.查詢操作符
(1)源起
.net的設計者在類庫中定義了一系列的擴展方法
來方便用戶操作集合對象
這些擴展方法構成了LINQ的查詢操作符
(2)使用
這一系列的擴展方法,比如:
Where,Max,Select,Sum,Any,Average,All,Concat等
都是針對IEnumerable的對象進行擴展的
也就是說,只要實現了IEnumerable介面,就可以使用這些擴展方法
來看看這段代碼:
List<int> arr = new List<int>() { 1, 2, 3, 4, 5, 6, 7 };
var result = arr.Where(a => { return a > 3; }).Sum();
Console.WriteLine(result);
Console.ReadKey();
這段代碼中,用到了兩個擴展方法。
<1>
Where擴展方法,需要傳入一個Func<int,bool>類型的泛型委托
這個泛型委托,需要一個int類型的輸入參數和一個布爾類型的返回值
我們直接把a => { return a > 3; }這個lambda表達式傳遞給了Where方法
a就是int類型的輸入參數,返回a是否大於3的結果。
<2>
Sum擴展方法計算了Where擴展方法返回的集合的和。
(3)好處
上面的代碼中
arr.Where(a => { return a > 3; }).Sum();
這一句完全可以寫成如下代碼:
(from v in arr where v > 3 select v).Sum();
而且兩句代碼的執行細節是完全一樣的
大家可以看到,第二句代碼更符合語義,更容易讀懂
第二句代碼中的where,就是我們要說的查詢操作符。
(4)標準查詢操作符說明
<1>過濾
Where
用法:arr.Where(a => { return a > 3; })
說明:找到集合中滿足指定條件的元素
OfType
用法:arr.OfType<int>()
說明:根據指定類型,篩選集合中的元素
<2>投影
Select
用法:arr.Select<int, string>(a => a.ToString());
說明:將集合中的每個元素投影的新集合中。上例中:新集合是一個IEnumerable<String>的集合
SelectMany
用法:arr.SelectMany<int, string>(a => { return new List<string>() { "a", a.ToString() }; });
說明:將序列的每個元素投影到一個序列中,最終把所有的序列合併
<3>還有很多查詢操作符,請翻MSDN,以後有時間我將另起一篇文章把這些操作符寫全。
2.查詢表達式
(1)源起
上面我們已經提到,使用查詢操作符表示的擴展方法來操作集合;
雖然已經很方便了,但在可讀性和代碼的語義來考慮,仍有不足;
於是就產生了查詢表達式的寫法。
雖然這很像SQL語句,但他們卻有著本質的不同。
(2)用法
from v in arr where v > 3 select v
這就是一個非常簡單的查詢表達式
(3)說明:
先看一段偽代碼:
from [type] id in source
[join [type] id in source on expr equals expr [into subGroup]]
[from [type] id in source | let id = expr | where condition]
[orderby ordering,ordering,ordering...]
select expr | group expr by key
[into id query]
<1>第一行的解釋:
type是可選的,
id是集合中的一項,
source是一個集合,
如果集合中的類型與type指定的類型不同則導致強制類型轉化
<2>第二行的解釋:
一個查詢表達式中可以有0個或多個join子句,
這裡的source可以是一個全新的集合,可以不等於第一句中的source
expr可以是一個表達式
[into subGroup] subGroup是一個中間變數,
它繼承自IGrouping,代表一個分組,也就是說“一對多”里的“多”
可以通過這個變數得到這一組包含的對象個數,以及這一組對象的鍵
比如:
from c in db.Customers
join o in db.Orders on c.CustomerID
equals o.CustomerID into orders
select new
{
c.ContactName,
OrderCount = orders.Count()
};
<3>第三行的解釋:
一個查詢表達式中可以有1個或多個from子句
一個查詢表達式中可以有0個或多個let子句,let子句可以創建一個臨時變數
比如:
from u in users
let number = Int32.Parse(u.Username.Substring(u.Username.Length - 1))
where u.ID < 9 && number % 2 == 0
select u
一個查詢表達式中可以有0個或多個where子句,where子句可以指定查詢條件
<4>第四行的解釋:
一個查詢表達式可以有0個或多個排序方式
每個排序方式以逗號分割
<5>第五行的解釋:
一個查詢表達式必須以select或者group by結束
select後跟要檢索的內容
group by 是對檢索的內容進行分組
比如:
from p in db.Products
group p by p.CategoryID into g
select new { g.Key, NumProducts = g.Count()};
<6>第六行的解釋:
最後一個into子句起到的作用是
將前面語句的結果作為後面語句操作的數據源
比如:
from p in db.Employees
select new
{
LastName = p.LastName,
TitleOfCourtesy = p.TitleOfCourtesy
} into EmployeesList
orderby EmployeesList.TitleOfCourtesy ascending
select EmployeesList;
三:參考資料
《LINQ實戰》
《深入理解C#》第二版
《CLR VIA C#》第三版
《C# 高級編程》第四版
還有很多網路上的文章,就不一一例舉了
四:修改記錄
1.2013-02-12夜
(1)完成了第一部分的大多數內容
(2)修改了文章的排版
(3)通讀了第一部分,修改了一些讀起來不通順的語句,修改了錯別字
2.2013-02-26夜
(1)完成了第二部分的內容
(2)刪掉了表達式樹的內容【文章篇幅實在太長了】
(3)完善了第一部分的內容
2.2013-02-27晨
(1)修改了一些錯別字
3.2017-03-02午後
(1)修改了幾個錯別字,幾個標點符號