------------------------------目录----------------------------
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 值的不同声明
A a = new A(); // 创建A的对象并对其进行初始化。
B b = null; // 声明引用b,并指向对象为null
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