C#基礎知識匯總(不斷更新中)


------------------------------目錄----------------------------

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

new一個對象的過程說明


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM