------------------------------目錄----------------------------
1.隱式類型
2.匿名類型
3.自動屬性
4.初始化器
4.1 new一個對象的過程說明
5.委托
6.泛型
7.泛型委托
8.匿名方法
9.Lambda表達式
10.擴展方法
11.迭代器
12.LINQ
13.線程和線程池Thread&ThreadPool
14.任務Task
15.異步方法async/await
16.Parallel
17.異步的回調
----------------------------正文-------------------------------
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 };
4.1關於new 值的不同聲明
1 A a = new A(); // 創建A的對象並對其進行初始化。 2 B b = null; // 聲明引用b,並指向對象為null 3 C c; // 聲明引用b,不指向任何對象
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等
這些操作,我們接下來會講到。
12.LINQ
1.基礎認知
| 程序集 |
命名空間 |
描述 |
|
| LINQ to Objects |
System.Core.dll |
System.Linq |
提供對內存中集合操作的支持 |
| LINQ to XML |
System.Xml.Linq.dll |
System.Xml.Linq |
提供對XML數據源的操作的支持 |
| LINQ to SQL |
System.Data.Linq.dll |
System.Data.Linq |
提供對Sql Server數據源操作的支持。(微軟已宣布不再更新,推薦使用LINQ to Entities) |
| LINQ to DataSet |
System.Data.DataSetExtensions.dll |
System.Data |
提供對離線數據操作的支持。 |
| LINQ to Entities |
System.Core.dll 和 System.Data.Entity.dll |
System.Linq 和System.Data.Objects |
LINQ to Entities 是 Entity Framework 的一部分並且取代 LINQ to SQL 作為在數據庫上使用 LINQ 的標准機制。(Entity Framework 是由微軟發布的開源對象-關系映射(ORM)框 |
| 約束 | LINQ查詢表達式必須以from子句開頭,以select或group子句結束。 |
|
|
|
| 關鍵字 |
功能 |
| from…in… |
指定要查找的數據源以及范圍變量,多個from子句則表示從多個數據源查找數據。 注意:c#編譯器會把“復合from子句”的查詢表達式轉換為SelectMany()擴展方法。 |
| join…in…on…equals… |
指定多個數據源的關聯方式 |
| let |
引入用於存儲查詢表達式中子表達式結果的范圍變量。通常能達到層次感會更好,使代碼更易於閱讀。 |
| orderby、descending |
指定元素的排序字段和排序方式。當有多個排序字段時,由字段順序確定主次關系,可指定升序和降序兩種排序方式 |
| where |
指定元素的篩選條件。多個where子句則表示了並列條件,必須全部都滿足才能入選。每個where子句可以使用謂詞&&、||連接多個條件表達式。 |
| group |
指定元素的分組字段。 |
| select |
指定查詢要返回的目標數據,可以指定任何類型,甚至是匿名類型。(目前通常被指定為匿名類型) |
| into |
提供一個臨時的標識符。該標識可以引用join、group和select子句的結果。 1) 直接出現在join子句之后的into關鍵字會被翻譯為GroupJoin。(into之前的查詢變量可以繼續使用) 2) select或group子句之后的into它會重新開始一個查詢,讓我們可以繼續引入where, orderby和select子句,它是對分步構建查詢表達式的一種簡寫方式。(into之前的查詢變量都不可再使用) |
2.各種LINQ示例
1.過濾操作符
根據條件返回匹配元素的集合IEnumerable<T>。
1)Where:根據返回bool值的Func委托參數過濾元素。
業務說明:查詢獲得車手冠軍次數大於15次且是Austria國家的一級方程式賽手
|
1
2
3
4
5
6
7
|
// 查詢表達式
var
racer =
from
r
in
Formula1.GetChampions()
where
r.Wins > 15 && r.Country ==
"Austria"
select
r;
// 方法語法
var
racer = Formula1.GetChampions().Where(r => r.Wins > 15
&& r.Country ==
"Austria"
);
|
2)OfType<TResult>:接收一個非泛型的IEnumerable集合,根據OfType泛型類型參數過濾元素,只返回TResult類型的元素。
業務說明:過濾object數組中的元素,返回字符串類型的數組。
|
1
2
|
object
[] data = {
"one"
, 2, 3,
"four"
,
"five"
, 6 };
var
query = data.OfType<
string
>();
// "one", "four", "five"
|
3)Distinct:刪除序列中重復的元素。
2.投影操作符
1)Select 將序列的每個元素經過lambda表達式處理后投影到一個新類型元素上。(與SelectMany不同在於,若單個元素投影到IEnumerable<TResult>,Select不會對多個IEnumerable<TResult>進行合並)
API:
|
1
2
3
|
public
static
IEnumerable<TResult> Select<TSource, TResult>(
this
IEnumerable<TSource> source
, Func<TSource, TResult> selector);
|
2) SelectMany
a) c#編譯器會把“復合from子句”的查詢表達式轉換為SelectMany()擴展方法。
b) 將序列的每個元素經過lambda表達式處理后投影到一個 IEnumerable<TResult>,再將多個IEnumerable<TResult>序列合並為一個返回序列IEnumerable<TResult>。
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
|
public
static
IEnumerable<TResult> SelectMany<TSource
, TResult>(
this
IEnumerable<TSource> source
, Func<TSource, IEnumerable<TResult>> selector);
//示例:
string
[] fullNames = {
"Anne Williams"
,
"John Fred Smith"
,
"Sue Green"
};
IEnumerable<
string
> query = fullNames.SelectMany(name => name.Split());
foreach
(
string
name
in
query)
Console.Write(name +
"|"
);
// Anne|Williams|John|Fred|Smith|Sue|Green|
//如果使用Select,則需要雙重循環。
IEnumerable<
string
[]> query = fullNames.Select(name => name.Split());
foreach
(
string
[] stringArray
in
query)
foreach
(
string
name
in
stringArray)
Console.Write(name +
"|"
);
// Anne|Williams|John|Fred|Smith|Sue|Green|
|
c) 將序列的每個元素經過lambda表達式處理后投影到一個 IEnumerable<TCollection>,再將多個IEnumerable<TCollection>序列合並為一個返回序列IEnumerable<TCollection>,並對其中每個元素調用結果選擇器函數。
|
1
2
3
4
|
public
static
IEnumerable<TResult> SelectMany<TSource, TCollection
, TResult>(
this
IEnumerable<TSource> source
, Func<TSource, IEnumerable<TCollection>> collectionSelector
, Func<TSource, TCollection, TResult> resultSelector);<br><br>
|
示例:
業務說明:(Racer類定義了一個屬性Cars,Cars是一個字符串數組。)過濾駕駛Ferrari的所有冠軍
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
// 查詢表達式
var
ferrariDrivers =
from
r
in
Formula1.GetChampions()
from
c
in
r.Cars
where
c ==
"Ferrari"
orderby
r.LastName
select
r.FirstName +
" "
+ r.LastName;
// 方法語法
var
ferrariDrivers = Formula1.GetChampions()
.SelectMany(
r => r.Cars,
(r, c) =>
new
{ Racer = r, Car = c }
)
.Where(r => r.Car ==
"Ferrari"
)
.OrderBy(r => r.Racer.LastName)
.Select(r => r.Racer.FirstName +
" "
+ r.Racer.LastName);<br><br>
|
3.排序操作符
1)OrderBy<TSource,TKey>,OrderByDescending<TSource,TKey>:根據指定鍵按升序或降序對集合進行第一次排序,輸出IOrderedEnumerable<TSource>。
2) ThenBy<TSource,TKey>,ThenByDescending<TSource,TKey>:只會對那些在前一次排序中擁有相同鍵值的elements重新根據指定鍵按升序或降序排序。輸入IOrderedEnumerable <TSource>。
業務說明:獲取車手冠軍列表,並依次按照Country升序、LastName降序、FirstName升序進行排序。
|
1
2
3
4
5
6
7
8
9
|
// 查詢表達式
var
racers =
from
r
in
Formula1.GetChampions()
orderby
r.Country, r.LastName
descending
, r.FirstName
select
r;
// 方法語法
var
racers = Formula1.GetChampions()
.OrderBy(r => r.Country)
.ThenByDescending(r => r.LastName)
.ThenBy(r => r.FirstName);
|
3) Reverse<TSource>:反轉集合中所有元素的順序。
4.連接操作符
先准備兩個集合,如下:(racers表示在1958到1965年間獲得車手冠軍的信息列表;teams表示在1958到1965年間獲得車隊冠軍的信息列表)
|
1
2
3
4
5
6
7
8
9
10
11
12
|
var
racers =
from
r
in
Formula1.GetChampions()
from
y
in
r.Years
where
y > 1958 && y < 1965
select
new
{
Year = y,
Name = r.FirstName +
" "
+ r.LastName
};
var
teams = Formula1.GetContructorChampions()
.SelectMany(y => y.Years, (t, y) =>
new
{ Year = y, Name = t.Name })
.Where(ty => ty.Year > 1958 && ty.Year < 1965);
|
注意:join…on…關鍵字后的相等使用equals關鍵字。
1) Join:基於匹配鍵對兩個序列的元素進行關聯。
API:
|
1
2
3
4
|
public
static
IEnumerable<TResult> Join<TOuter, TInner, TKey, TResult>(
this
IEnumerable<TOuter> outer, IEnumerable<TInner> inner
, Func<TOuter, TKey> outerKeySelector, Func<TInner, TKey> innerKeySelector
, Func<TOuter, TInner, TResult> resultSelector);
|
業務說明:返回1958到1965年間的車手冠軍和車隊冠軍信息,根據年份關聯
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
// 查詢表達式
var
racersAndTeams =
from
r
in
racers
join
t
in
teams
on
r.Year
equals
t.Year
select
new
{
Year = r.Year,
Racer = r.Name,
Team = t.Name
};
// 方法語法
var
racersAndTeams = racers.Join(teams
, r => r.Year, t => t.Year
, (r, t) =>
new
{ Year = r.Year, Racer = r.Name, Team = t.Name }
);
|
2) GroupJoin:基於鍵相等對兩個序列的元素進行關聯並對結果進行分組。常應用於返回“主鍵對象-外鍵對象集合”形式的查詢。
API:
|
1
2
3
4
|
public
static
IEnumerable<TResult> GroupJoin<TOuter, TInner, TKey, TResult>(
this
IEnumerable<TOuter> outer, IEnumerable<TInner> inner
, Func<TOuter, TKey> outerKeySelector, Func<TInner, TKey> innerKeySelector
, Func<TOuter, IEnumerable<TInner>, TResult> resultSelector);
|
業務說明:返回1958到1965年間的車手冠軍和車隊冠軍信息,根據年份關聯並分組
注意:直接出現在join子句之后的into關鍵字會被翻譯為GroupJoin,而在select或group子句之后的into表示繼續一個查詢。
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
// 查詢表達式
var
racersAndTeams =
from
r
in
racers
join
t
in
teams
on
r.Year
equals
t.Year
into
groupTeams
select
new
{
Year = r.Year,
Racer = r.Name,
GroupTeams = groupTeams
};
// 方法語法
var
racersAndTeams = racers
.GroupJoin(teams
, r => r.Year, t => t.Year
, (r, t) =>
new
{ Year = r.Year, Racer = r.Name, GroupTeams = t }
);
|
3) join…on…equals…支持多個鍵關聯
可以使用匿名類型來對多個鍵值進行Join,如下所示:
from x in sequenceX
join y in sequenceY on new { K1 = x.Prop1, K2 = x.Prop2 }
equals new { K1 = y.Prop3, K2 = y.Prop4 }
...
兩個匿名類型的結構必須完全一致,這樣編譯器會把它們對應到同一個實現類型,從而使連接鍵值彼此兼容。
4) Join與GroupJoin結果集對比(為了實現此業務,將1959年設置了兩個車隊冠軍)
5.分組操作符
1) 返回值為 IEnumerable<IGrouping<TKey, TSource>> ,根據指定的鍵選擇器函數對序列中的元素進行分組。
業務說明:按城市分組,獲取每個城市的車手冠軍。
|
1
2
3
4
5
6
7
8
|
// 查詢表達式
var
countries =
from
r
in
Formula1.GetChampions()
group
r
by
r.Country
into
g
select
new
{ Country = g.Key, Racers = g };
// 方法語法
var
countries = Formula1.GetChampions()
.GroupBy(r => r.Country)
.Select(g =>
new
{ Country = g.Key, Racer = g });
|
2) 返回值為 IEnumerable<TResult>,根據指定的鍵選擇器函數對序列中的元素進行分組,並且從每個組及其鍵中創建結果值。
業務說明:按城市分組,獲取每個城市的車手冠軍。
|
1
2
3
|
// 方法語法 (等價上面兩種方式)
var
countries = Formula1.GetChampions()
.GroupBy(r => r.Country, (k, g) =>
new
{ Country = k, Racer = g });
|
6.量詞操作符
如果元素序列滿足指定的條件,量詞操作符就返回布爾值。
1) Any:確定序列是否包含任何元素;或確定序列中的任何元素是否都滿足條件。
2) All:確定序列中的所有元素是否滿足條件。
3) Contains:確定序列是否包含指定的元素。
|
1
2
3
|
// 獲取是否存在姓為“Schumacher”的車手冠軍
var
hasRacer_Schumacher = Formula1.GetChampions()
.Any(r => r.LastName ==
"Schumacher"
);
|
7.分區操作符
添加在查詢的“最后”,返回集合的一個子集。
1) Take:從序列的開頭返回指定數量的連續元素。
2) TakeWhile:只要滿足指定的條件,就會返回序列的元素。
3) Skip:跳過序列中指定數量的元素,然后返回剩余的元素。
4) SkipWhile:只要滿足指定的條件,就跳過序列中的元素,然后返回剩余元素。
業務說明:將車手冠軍列表按每頁5個名字進行分頁。
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
|
private
static
void
Paging()
{
int
pageSize = 5;
int
numberPages = (
int
)Math.Ceiling(
Formula1.GetChampions().Count() / (
double
)pageSize);
for
(
int
page = 0; page < numberPages; page++)
{
Console.WriteLine(
"Page {0}"
, page);
var
racers = (
from
r
in
Formula1.GetChampions()
orderby
r.LastName
select
r.FirstName +
" "
+ r.LastName
)
.Skip(page * pageSize).Take(pageSize);
foreach
(
var
name
in
racers)
{
Console.WriteLine(name);
}
Console.WriteLine();
}
}
|
8.集合操作符
1) Union:並集,返回兩個序列的並集,去掉重復元素。
2) Concat:並集,返回兩個序列的並集。
3) Intersect:交集,返回兩個序列中都有的元素,即交集。
4) Except:差集,返回只出現在一個序列中的元素,即差集。
業務說明:獲取使用車型”Ferrari”和車型”Mclaren”都獲得過車手冠軍車手列表
|
1
2
3
4
5
6
7
8
9
10
11
12
|
Func<
string
, IEnumerable<Racer>> racersByCar =
Car =>
from
r
in
Formula1.GetChampions()
from
c
in
r.Cars
where
c == Car
orderby
r.LastName
select
r;
foreach
(
var
racer
in
racersByCar(
"Ferrari"
)
.Intersect(racersByCar(
"McLaren"
)))
{
Console.WriteLine(racer);
}
|
5) Zip:通過使用指定的委托函數合並兩個序列,集合的總個數不變。
API:
|
1
2
3
|
public
static
IEnumerable<TResult> Zip<TFirst, TSecond, TResult>(
this
IEnumerable<TFirst> first, IEnumerable<TSecond> second
, Func<TFirst, TSecond, TResult> resultSelector);
|
示例:合並html開始標簽和結束標簽
|
1
2
3
4
5
6
7
8
|
string
[] start = {
"<html>"
,
"<head>"
,
"<body>"
};
string
[] end = {
"</html>"
,
"</head>"
,
"</body>"
};
var
tags = start.Zip(end, (s, e) => {
return
s + e; });
foreach
(
string
item
in
tags)
{
Console.WriteLine(item);
}
|
6) SequenceEqual:判斷兩個序列是否相等,需要內容及順序都相等。
示例:
|
1
2
3
4
5
6
7
|
int
[] arr1 = { 1, 4, 7, 9 };
int
[] arr2 = { 1, 7, 9, 4 };
Console.WriteLine(
"排序前 是否相等:{0}"
, arr1.SequenceEqual(arr2) ?
"是"
:
"否"
);
// 否
Console.WriteLine();
Console.WriteLine(
"排序后 是否相等:{0}"
, arr1.SequenceEqual(arr2.OrderBy(k => k)) ?
"是"
:
"否"
);
// 是
|
9.元素操作符
這些元素操作符僅返回一個元素,不是IEnumerable<TSource>。(默認值:值類型默認為0,引用類型默認為null)
1) First:返回序列中的第一個元素;如果是空序列,此方法將引發異常。
2) FirstOrDefault:返回序列中的第一個元素;如果是空序列,則返回默認值default(TSource)。
3) Last:返回序列的最后一個元素;如果是空序列,此方法將引發異常。
4) LastOrDefault:返回序列中的最后一個元素;如果是空序列,則返回默認值default(TSource)。
5) Single:返回序列的唯一元素;如果是空序列或序列包含多個元素,此方法將引發異常。
6) SingleOrDefault:返回序列中的唯一元素;如果是空序列,則返回默認值default(TSource);如果該序列包含多個元素,此方法將引發異常。
7) ElementAt:返回序列中指定索引處的元素,索引從0開始;如果索引超出范圍,此方法將引發異常。
8) ElementAtOrDefault:返回序列中指定索引處的元素,索引從0開始;如果索引超出范圍,則返回默認值default(TSource)。
業務說明:獲取冠軍數排名第三的車手冠軍
|
1
2
3
|
var
Racer3 = Formula1.GetChampions()
.OrderByDescending(r => r.Wins)
.ElementAtOrDefault(2);
|
10.合計操作符
1) Count:返回一個 System.Int32,表示序列中的元素的總數量。
2) LongCount:返回一個 System.Int64,表示序列中的元素的總數量。
3) Sum:計算序列中元素值的總和。
4) Max:返回序列中的最大值。
5) Min:返回序列中的最小值。
6) Average:計算序列的平均值。
7) Aggregate:對序列應用累加器函數。
Aggregate比較復雜,所以只列出Aggregate示例。
Aggregate的第一個參數是算法的種子,即初始值。第二個參數是一個表達式,用來對每個元素進行計算(委托第一個參數是累加變量,第二個參數當前項)。第三個參數是一個表達式,用來對最終結果進行數據轉換。
|
1
2
3
4
5
6
7
|
int
[] numbers = { 1, 2, 3 };
// 1+2+3 = 6
int
y = numbers.Aggregate((prod, n) => prod + n);
// 0+1+2+3 = 6
int
x = numbers.Aggregate(0, (prod, n) => prod + n);
// (0+1+2+3)*2 = 12
int
z = numbers.Aggregate(0, (prod, n) => prod + n, r => r * 2);
|
11.轉換操作符
1) Cast:將非泛型的 IEnumerable 集合元素轉換為指定的泛型類型,若類型轉換失敗則拋出異常。
2) ToArray:從 IEnumerable<T> 創建一個數組。
3) ToList:從 IEnumerable<T> 創建一個 List<T>。
4) ToDictionary:根據指定的鍵選擇器函數,從 IEnumerable<T> 創建一個 Dictionary<TKey,TValue>。
5) ToLookup:根據指定的鍵選擇器函數,從 IEnumerable<T> 創建一個 System.Linq.Lookup<TKey,TElement>。
6) DefaultIfEmpty:返回指定序列的元素;如果序列為空,則返回包含類型參數的默認值的單一元素集合。
Eg:
|
1
|
var
defaultArrCount = (
new
int
[0]).DefaultIfEmpty().Count();
// 1
|
7) AsEnumerable:返回類型為 IEnumerable<T> 。用於處理LINQ to Entities操作遠程數據源與本地集合的協作。(后續在LINQ to Entities博文中會詳細解說)
ToLookup使用比較復雜,所以以ToLookup為示例。
Lookup類似於Dictionary,不過,Dictionary每個鍵只對應一個值,而Lookup則是1:n 的映射。Lookup沒有公共構造函數,而且是不可變的。在創建Lookup之后,不能添加或刪除其中的元素或鍵。(可以將ToLookup 視為GroupBy與ToDictionary的功能合體)
API:
|
1
2
3
|
public
static
ILookup<TKey, TElement> ToLookup<TSource, TKey, TElement>(
this
IEnumerable<TSource> source, Func<TSource, TKey> keySelector,
Func<TSource, TElement> elementSelector);
|
業務說明:將車手冠軍按其使用車型進行分組,並顯示使用”williams”車型的車手名字。
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
ILookup<
string
, Racer> racers =
(
from
r
in
Formula1.GetChampions()
from
c
in
r.Cars
select
new
{
Car = c,
Racer = r
}
).ToLookup(cr => cr.Car, cr => cr.Racer);
if
(racers.Contains(
"Williams"
))
{
foreach
(
var
williamsRacer
in
racers[
"Williams"
])
{
Console.WriteLine(williamsRacer);
}
}
|
12.生成操作符
生成操作符返回一個新的集合。(三個生成操作符不是擴展方法,而是返回序列的正常靜態方法)
1) Empty:生成一個具有指定類型參數的空序列 IEnumerable<T>。
2) Range:生成指定范圍內的整數的序列 IEnumerable<Int32>。
3) Repeat:生成包含一個重復值的序列 IEnumerable<T>。
API:
|
1
2
3
|
public
static
IEnumerable<TResult> Empty<TResult>();
public
static
IEnumerable<
int
> Range(
int
start,
int
count);
public
static
IEnumerable<TResult> Repeat<TResult>(TResult element,
int
count);
|
13.線程
基礎知識
1、進程與線程:進程作為操作系統執行程序的基本單位,擁有應用程序的資源,進程包含線程,進程的資源被線程共享,線程不擁有資源。
2、前台線程和后台線程:通過Thread類新建線程默認為前台線程。當所有前台線程關閉時,所有的后台線程也會被直接終止,不會拋出異常。
3、掛起(Suspend)和喚醒(Resume):由於線程的執行順序和程序的執行情況不可預知,所以使用掛起和喚醒容易發生死鎖的情況,在實際應用中應該盡量少用。
4、阻塞線程:Join,阻塞調用線程,直到該線程終止。
5、終止線程:Abort:拋出 ThreadAbortException 異常讓線程終止,終止后的線程不可喚醒。Interrupt:拋出 ThreadInterruptException 異常讓線程終止,通過捕獲異常可以繼續執行。
6、線程優先級:AboveNormal BelowNormal Highest Lowest Normal,默認為Normal。
多線程的意義在於一個應用程序中,有多個執行部分可以同時執行;對於比較耗時的操作(例如io,數據庫操作),或者等待響應(如WCF通信)的操作,可以單獨開啟后台線程來執行,這樣主線程就不會阻塞,可以繼續往下執行;等到后台線程執行完畢,再通知主線程,然后做出對應操作!
在C#中開啟新線程比較簡單
static void Main(string[] args) { Console.WriteLine("主線程開始"); //IsBackground=true,將其設置為后台線程 Thread t = new Thread(Run) { IsBackground = true }; t.Start();
Console.WriteLine("主線程在做其他的事!"); //主線程結束,后台線程會自動結束,不管有沒有執行完成 //Thread.Sleep(300); Thread.Sleep(1500); Console.WriteLine("主線程結束"); } static void Run() { Thread.Sleep(700); Console.WriteLine("這是后台線程調用"); }
執行結果如下圖,

可以看到在啟動后台線程之后,主線程繼續往下執行了,並沒有等到后台線程執行完之后。
1.1 線程池(ThreadPool)
試想一下,如果有大量的任務需要處理,例如網站后台對於HTTP請求的處理,那是不是要對每一個請求創建一個后台線程呢?顯然不合適,這會占用大量內存,而且頻繁地創建的過程也會嚴重影響速度,那怎么辦呢?線程池就是為了解決這一問題,把創建的線程存起來,形成一個線程池(里面有多個線程),當要處理任務時,若線程池中有空閑線程(前一個任務執行完成后,線程不會被回收,會被設置為空閑狀態),則直接調用線程池中的線程執行(例asp.net處理機制中的Application對象),
使用事例:
for (int i = 0; i < 10; i++) { ThreadPool.QueueUserWorkItem(m => { Console.WriteLine(Thread.CurrentThread.ManagedThreadId.ToString()); }); } Console.Read();
運行結果:

可以看到,雖然執行了10次,但並沒有創建10個線程。
1.2 信號量(Semaphore)
Semaphore負責協調線程,可以限制對某一資源訪問的線程數量
這里對SemaphoreSlim類的用法做一個簡單的事例:
static SemaphoreSlim semLim = new SemaphoreSlim(3); //3表示最多只能有三個線程同時訪問 static void Main(string[] args) { for (int i = 0; i < 10; i++) { new Thread(SemaphoreTest).Start(); } Console.Read(); } static void SemaphoreTest() { semLim.Wait(); Console.WriteLine("線程" + Thread.CurrentThread.ManagedThreadId.ToString() + "開始執行"); Thread.Sleep(2000); Console.WriteLine("線程" + Thread.CurrentThread.ManagedThreadId.ToString() + "執行完畢"); semLim.Release(); }
執行結果如下:


可以看到,剛開始只有三個線程在執行,當一個線程執行完畢並釋放之后,才會有新的線程來執行方法!
除了SemaphoreSlim類,還可以使用Semaphore類,感覺更加靈活,感興趣的話可以搜一下,這里就不做演示了!
14.Task
一個可以有返回值(需要等待)的多線程工具。Task傳入方法不能有參數,可以有返回值。
14.1 Task
Task是.NET4.0加入的,跟線程池ThreadPool的功能類似,用Task開啟新任務時,會從線程池中調用線程,而Thread每次實例化都會創建一個新的線程。
Console.WriteLine("主線程啟動"); //Task.Run啟動一個線程 //Task啟動的是后台線程,要在主線程中等待后台線程執行完畢,可以調用Wait方法 //Task task = Task.Factory.StartNew(() => { Thread.Sleep(1500); Console.WriteLine("task啟動"); }); Task task = Task.Run(() => { Thread.Sleep(1500); Console.WriteLine("task啟動"); }); Thread.Sleep(300); task.Wait(); Console.WriteLine("主線程結束");
執行結果如下:

開啟新任務的方法:Task.Run()或者Task.Factory.StartNew(),開啟的是后台線程
要在主線程中等待后台線程執行完畢,可以使用Wait方法(會以同步的方式來執行)。不用Wait則會以異步的方式來執行。
比較一下Task和Thread:
static void Main(string[] args) { for (int i = 0; i < 5; i++) { new Thread(Run1).Start(); } for (int i = 0; i < 5; i++) { Task.Run(() => { Run2(); }); } } static void Run1() { Console.WriteLine("Thread Id =" + Thread.CurrentThread.ManagedThreadId); } static void Run2() { Console.WriteLine("Task調用的Thread Id =" + Thread.CurrentThread.ManagedThreadId); }
執行結果:

可以看出來,直接用Thread會開啟5個線程,用Task(用了線程池)開啟了3個!
14.2 Task<TResult>
Task<TResult>就是有返回值的Task,TResult就是返回值類型。
Console.WriteLine("主線程開始"); //返回值類型為string Task<string> task = Task<string>.Run(() => { Thread.Sleep(2000); return Thread.CurrentThread.ManagedThreadId.ToString(); }); //會等到task執行完畢才會輸出; Console.WriteLine(task.Result); Console.WriteLine("主線程結束");
運行結果:

通過task.Result可以取到返回值,若取值的時候,后台線程還沒執行完,則會等待其執行完畢!
簡單提一下:
Task任務可以通過CancellationTokenSource類來取消,感覺用得不多,用法比較簡單,感興趣的話可以搜一下!
15. async/await
async/await是C#5.0中推出的,先上用法:
static void Main(string[] args) { Console.WriteLine("-------主線程啟動-------"); Task<int> task = GetStrLengthAsync(); Console.WriteLine("主線程繼續執行"); Console.WriteLine("Task返回的值" + task.Result); Console.WriteLine("-------主線程結束-------"); } static async Task<int> GetStrLengthAsync() { Console.WriteLine("GetStrLengthAsync方法開始執行"); //此處返回的<string>中的字符串類型,而不是Task<string> string str = await GetString(); Console.WriteLine("GetStrLengthAsync方法執行結束"); return str.Length; } static Task<string> GetString() {
//Console.WriteLine("GetString方法開始執行") return Task<string>.Run(() => { Thread.Sleep(2000); return "GetString的返回值"; }); }
async用來修飾方法,表明這個方法是異步的,聲明的方法的返回類型必須為:void,Task或Task<TResult>。
await必須用來修飾Task或Task<TResult>,而且只能出現在已經用async關鍵字修飾的異步方法中。通常情況下,async/await成對出現才有意義,
看看運行結果:

可以看出來,main函數調用GetStrLengthAsync方法后,在await之前,都是同步執行的,直到遇到await關鍵字,main函數才返回繼續執行。
那么是否是在遇到await關鍵字的時候程序自動開啟了一個后台線程去執行GetString方法呢?
現在把GetString方法中的那行注釋加上,運行的結果是:

大家可以看到,在遇到await關鍵字后,沒有繼續執行GetStrLengthAsync方法后面的操作,也沒有馬上反回到main函數中,而是執行了GetString的第一行,以此可以判斷await這里並沒有開啟新的線程去執行GetString方法,而是以同步的方式讓GetString方法執行,等到執行到GetString方法中的Task<string>.Run()的時候才由Task開啟了后台線程!
那么await的作用是什么呢?
可以從字面上理解,上面提到task.wait可以讓主線程等待后台線程執行完畢,await和wait類似,同樣是等待,等待Task<string>.Run()開始的后台線程執行完畢,不同的是await不會阻塞主線程,只會讓GetStrLengthAsync方法暫停執行。
那么await是怎么做到的呢?有沒有開啟新線程去等待?

只有兩個線程(主線程和Task開啟的線程)!至於怎么做到的(我也不知道......>_<),大家有興趣的話研究下吧!
16.Parallel
最后說一下在循環中開啟多線程的簡單方法:
Stopwatch watch1 = new Stopwatch(); watch1.Start(); for (int i = 1; i <= 10; i++) { Console.Write(i + ","); Thread.Sleep(1000); } watch1.Stop(); Console.WriteLine(watch1.Elapsed); Stopwatch watch2 = new Stopwatch(); watch2.Start(); //會調用線程池中的線程 Parallel.For(1, 11, i => { Console.WriteLine(i + ",線程ID:" + Thread.CurrentThread.ManagedThreadId); Thread.Sleep(1000); }); watch2.Stop(); Console.WriteLine(watch2.Elapsed);
運行結果:

循環List<T>:
List<int> list = new List<int>() { 1, 2, 3, 4, 5, 6, 6, 7, 8, 9 };
Parallel.ForEach<int>(list, n => { Console.WriteLine(n); Thread.Sleep(1000); });
執行Action[]數組里面的方法:
Action[] actions = new Action[] { new Action(()=>{ Console.WriteLine("方法1"); }), new Action(()=>{ Console.WriteLine("方法2"); }) }; Parallel.Invoke(actions);
17.異步的回調
為了簡潔(偷懶),文中所有Task<TResult>的返回值都是直接用task.result獲取,這樣如果后台任務沒有執行完畢的話,主線程會等待其執行完畢。這樣的話就和同步一樣了,一般情況下不會這么用。簡單演示一下Task回調函數的使用:
Console.WriteLine("主線程開始"); Task<string> task = Task<string>.Run(() => { Thread.Sleep(2000); return Thread.CurrentThread.ManagedThreadId.ToString(); }); //會等到任務執行完之后執行 task.GetAwaiter().OnCompleted(() => { Console.WriteLine(task.Result); }); Console.WriteLine("主線程結束"); Console.Read();
執行結果:

OnCompleted中的代碼會在任務執行完成之后執行!
另外task.ContinueWith()也是一個重要的方法:
Console.WriteLine("主線程開始"); Task<string> task = Task<string>.Run(() => { Thread.Sleep(2000); return Thread.CurrentThread.ManagedThreadId.ToString(); }); task.GetAwaiter().OnCompleted(() => { Console.WriteLine(task.Result); }); task.ContinueWith(m=>{Console.WriteLine("第一個任務結束啦!我是第二個任務");}); Console.WriteLine("主線程結束"); Console.Read();
執行結果:

ContinueWith()方法可以讓該后台線程繼續執行新的任務。
參考博客:
liulun:http://www.cnblogs.com/liulun/archive/2013/02/26/2909985.html
Mr靖:http://www.cnblogs.com/doforfuture/p/6293926.html
滴答的雨:http://www.cnblogs.com/heyuquan/p/Linq-to-Objects.html
