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一个对象的过程说明

原文地址:https://www.cnblogs.com/shenbing/p/6943747.html