一、匿名类:[ C# 3.0/.NET 3.x 新增特性 ]
1.1 不好意思,我匿了
在开发中,我们有时会像下面的代码一样声明一个匿名类:可以看出,在匿名类的语法中并没有为其命名,而是直接的一个new { }就完事了。从外部看来,我们根本无法知道这个类是干神马的,也不知道它有何作用。
var annoyCla1 = new
{
ID = 10010,
Name = "EdisonChou",
Age = 25
};
Console.WriteLine("ID:{0}-Name:{1}-Age:{2}", annoyCla1.ID,annoyCla1.Name, annoyCla1.Age);
经过调试运行,我们发现匿名类完全可以实现具名类的效果:
1.2 深入匿名类背后
既然我们发现匿名类可以完全实现具名类的效果,那么我们可以大胆猜测编译器肯定在内部帮我们生成了一个类似具名类的class,于是,我们还是借助反编译工具对其进行探索。通过Reflector反编译,我们找到了编译器生成的匿名类如下图所示:
从上图可以看出:
(1)匿名类被编译后会生成一个[泛型类],可以看到上图中的<>f__AnonymousType0<<ID>j__TPar, <Name>j__TPar, <Age>j__TPar>就是一个泛型类;
(2)匿名类所生成的属性都是只读的,可以看出与其对应的字段也是只读的;
所以,如果我们在程序中为属性赋值,那么会出现错误;
(3)可以看出,匿名类还重写了基类的三个方法:Equals,GetHashCode和ToString;我们可以看看它为我们所生成的ToString方法是怎么来实现的:
实现的效果如下图所示:
1.3 匿名类的共享
可以想象一下,如果我们的代码中定义了很多匿名类,那么是不是编译器会为每一个匿名类都生成一个泛型类呢?答案是否定的,编译器考虑得很远,避免了重复地生成类型。换句话说,定义了多个匿名类的话如果符合一定条件则可以共享一个泛型类。下面,我们就来看看有哪几种情况:
(1)如果定义的匿名类与之前定义过的一模一样:属性类型和顺序都一致,那么默认共享前一个泛型类
var annoyCla1 = new
{
ID = 10010,
Name = "EdisonChou",
Age = 25
};
Console.WriteLine("ID:{0}-Name:{1}-Age:{2}", annoyCla1.ID,
annoyCla1.Name, annoyCla1.Age);
Console.WriteLine(annoyCla1.ToString());
// 02.属性类型和顺序与annoyCla1一致,那么共同使用一个匿名类
var annoyCla2 = new
{
ID = 10086,
Name = "WncudChou",
Age = 25
};
Console.WriteLine("ID:{0}-Name:{1}-Age:{2}", annoyCla1.ID,
annoyCla1.Name, annoyCla1.Age);
Console.WriteLine("Is The Same Class of 1 and 2:{0}",
annoyCla1.GetType() == annoyCla2.GetType());
通过上述代码中的最后两行:我们可以判断其是否是一个类型?答案是:True
(2)如果属性名称和顺序一致,但属性类型不同,那么还是共同使用一个泛型类,只是泛型参数改变了而已,所以在运行时会生成不同的类:
var annoyCla3 = new
{
ID = "EdisonChou",
Name = 10010,
Age = 25
};
Console.WriteLine("ID:{0}-Name:{1}-Age:{2}", annoyCla3.ID,
annoyCla3.Name, annoyCla3.Age);
Console.WriteLine("Is The Same Class of 2 and 3:{0}",
annoyCla3.GetType() == annoyCla2.GetType());
我们刚刚说到虽然共享了同一个泛型类,只是泛型参数改变了而已,所以在运行时会生成不同的类。所以,那么可以猜测到最后两行代码所显示的结果应该是False,他们虽然都使用了一个泛型类,但是在运行时生成了两个不同的类。
(3)如果数据型名称和类型相同,但顺序不同,那么编译器会重新创建一个匿名类
var annoyCla4 = new
{
Name = "EdisonChou",
ID = 10010,
Age = 25
};
Console.WriteLine("ID:{0}-Name:{1}-Age:{2}", annoyCla4.ID,
annoyCla4.Name, annoyCla4.Age);
Console.WriteLine("Is The Same Class of 2 and 4:{0}",
annoyCla4.GetType() == annoyCla2.GetType());
运行判断结果为:False
通过Reflector,可以发现,编译器确实重新生成了一个泛型类:
二、匿名方法:[ C# 2.0/.NET 2.0 新增特性 ]
2.1 从委托的声明说起
C#中的匿名方法是在C#2.0引入的,它终结了C#2.0之前版本声明委托的唯一方法是使用命名方法的时代。不过,这里我们还是看一下在没有匿名方法之前,我们是如何声明委托的。
(1)首先定义一个委托类型:
publicdelegate void DelegateTest(string testName);
(2)编写一个符合委托规定的命名方法:
publicvoid TestFunc(string name)
{
Console.WriteLine("Hello,{0}", name);
}
(3)最后声明一个委托实例:
DelegateTest dgTest = new DelegateTest(TestFunc);
dgTest("Edison Chou");
(4)调试运行可以得到以下输出:
由上面的步凑可以看出,我们要声明一个委托实例要为其编写一个符合规定的命名方法。但是,如果程序中这个方法只被这个委托使用的话,总会感觉代码结构有点浪费。于是,微软引入了匿名方法,使用匿名方法声明委托,就会使代码结构变得简洁,也会省去实例化的一些开销。
2.2 引入匿名方法
(1)首先,我们来看看上面的例子如何使用匿名方法来实现:
DelegateTest dgTest2 = new DelegateTest(delegate(string name)
{
Console.WriteLine("Good,{0}", name);
});
从运行结果图中可以看出,原本需要传递方法名的地方我们直接传递了一个方法,这个方法以delegate(参数){方法体}的格式编写,在{}里边直接写了方法体内容。于是,我们不禁欢呼雀跃,又可以简化一些工作量咯!
(2)其次,我们将生成的程序通过Reflector反编译看看匿名方法是怎么帮我们实现命名方法的效果的。
①我们可以看到,在编译生成的类中,除了我们自己定义的方法外,还多了两个莫名其妙的成员:
②经过一一查看,原来编译器帮我们生成了一个私有的委托对象以及一个私有的静态方法。我们可以大胆猜测:原来匿名方法不是没有名字的方法,还是生成了一个有名字的方法,只不过这个方法的名字被藏匿起来了,而且方法名是编译器生成的。
③经过上面的分析,我们还是不甚了解,到底匿名方法委托对象在程序中是怎么体现的?这里,我们需要查看Main方法,但是通过C#代码我们没有发现一点可以帮助我们理解的。这时,我们想要刨根究底就有点麻烦了。还好,在高人指点下,我们知道可以借助IL(中间代码)来分析一下。于是,在Reflector中切换展示语言,将C#改为IL,就会看到另外一番天地。
(3)由上面的分析,我们可以做出结论:编译器对于匿名方法帮我们做了两件事,一是生成了一个私有静态的委托对象和一个私有静态方法;二是将生成的方法的地址存入了委托,在运行时调用委托对象的Invoke方法执行该委托对象所持有的方法。因此,我们也可以看出,匿名方法需要结合委托使用。
2.3 匿名方法扩展
(1)匿名方法语法糖—更加简化你的代码
在开发中,我们往往会采用语法糖来写匿名方法,例如下面所示:
DelegateTest dgTest3 = delegate(string name)
{
Console.WriteLine("Goodbye,{0}", name);
};
dgTest3("Edison Chou");
可以看出,使用该语法糖,将new DelegateTest()也去掉了。可见,编译器让我们越来越轻松了。
(2)传参也有大学问—向方法中传入匿名方法作为参数
①在开发中,我们往往声明了一个方法,其参数是一个委托对象,可以接受任何符合委托定义的方法。
staticvoid InvokeMethod(DelegateTest dg)
{
dg("Edison Chou");
}
②我们可以将已经定义的方法地址作为参数传入InvokeMethod方法,例如:InvokeMethod(TestFunc); 当然,我们也可以使用匿名方法,不需要单独定义就可以调用InvokeMethod方法。
InvokeMethod(delegate(string name)
{
Console.WriteLine("Fuck,{0}", name);
});
(3)省略省略再省略—省略"大括号"
经过编译器的不断优化,我们发现连delegate后边的()都可以省略了,我们可以看看下面一段代码:
InvokeMethod(delegate {
Console.WriteLine("I love C sharp!");
});
而我们之前的定义是这样的:
publicdelegate void DelegateTest(string testName);
staticvoid InvokeMethod(DelegateTest dg)
{
dg("Edison Chou");
}
我们发现定义时方法是需要传递一个string类型的参数的,但是我们省略了deletegate后面的括号之后就没有参数了,那么结果又是什么呢?经过调试,发现结果输出的是:I love C sharp!
这时,我们就有点百思不得其解了!明明都没有定义参数,为何还是满足了符合委托定义的参数条件呢?于是,我们带着问题还是借助Reflector去一探究竟。
①在Main函数中,可以看到编译器为我们自动加上了符合DelegateTest这个委托定义的方法参数,即一个string类型的字符串。虽然,输出的是I love C sharp,但它确实是符合方法定义的,因为它会接受一个string类型的参数,尽管在方法体中没有使用到这个参数。
②刚刚在Main函数中看到了匿名方法,现在可以看看编译器为我们所生成的命名方法。
三、扩展方法:[ C# 3.0/.NET 3.x 新增特性 ]
3.1 神奇—初玩扩展方法
(1)提到扩展方法,我想大部分的园友都不陌生了。不过还是来看看MSDN的定义:
MSDN 说:扩展方法使您能够向现有类型"添加"方法,而无需创建新的派生类型、重新编译或以其他方式修改原始类型。这里的"添加"之所以使用引号,是因为并没有真正地向指定类型添加方法。
那么,有时候我们会问:为什么要有扩展方法呢?这里,我们可以顾名思义地想一下,扩展扩展,那么肯定是涉及到可扩展性。在抽象工厂模式中,我们可以通过新增一个工厂类,而不需要更改源代码就可以切换到新的工厂。这里也是如此,在不修改源码的情况下,为某个类增加新的方法,也就实现了类的扩展。
(2)空说无凭,我们来看看在C#中是怎么来判断扩展方法的:通过智能提示,我们发现有一些方法带了一个指向下方的箭头,查看"温馨提示",我们知道他是一个扩展方法。所得是乃,原来我们一直对集合进行筛选的Where()方法居然是扩展方法而不是原生的。
我们再来看看使用Where这个扩展方法的代码示例:
staticvoid UseExtensionMethod()
{
List<Person> personList = new List<Person>()
{
new Person(){ID=1,Name="Big Yellow",Age=10},
new Person(){ID=2,Name="Little White",Age=15},
new Person(){ID=3,Name="Middle Blue",Age=7}
};
// 下面就使用了IEnumerable的扩展方法:Where
var datas = personList.Where(delegate(Person p)
{
return p.Age >= 10;
});
foreach (var data in datas)
{
Console.WriteLine("{0}-{1}-{2}",
data.ID, data.Name, data.Age);
}
}
上述代码使用了Where扩展方法,找出集合中Age>=10的数据形成新的数据集并输出:
(3)既然扩展方法是为了对类进行扩展,那么我们可不可以进行自定义扩展呢?答案是必须可以。我们先来看看扩展方法是如何的定义的,可以通过刚刚的IEnumerable接口中的Where方法定义来看看有哪些规则:通过 转到定义 的方式,我们可以看到在System.Linq命名空间下,有叫做Enumerable的这样一个静态类,它的成员方法全是静态方法,而且每个方法的大部分第一参数都是以this开头。于是,我们可以总结出,扩展方法的三个要素是:静态类、静态方法以及this关键字。
publicstatic class Enumerable
{
publicstatic IEnumerable<TSource> Union<TSource>(this IEnumerable<TSource> first, IEnumerable<TSource> second, IEqualityComparer<TSource> comparer);
}
那么问题又来了:为何一定得是static静态的呢?这个我们都知道静态方法是不属于某个类的实例的,也就是说我们不需要实例化这个类,就可以访问这个静态方法。所以,你懂的啦。
(4)看完扩展方法三要素,我们就来自动动手写一个扩展方法:
publicstatic class PersonExtension
{
publicstatic string FormatOutput(this Person p)
{
returnstring.Format("ID:{0},Name:{1},Age:{2}",
p.ID, p.Name, p.Age);
}
}
上面这个扩展方法完成了一个格式化输出Person对象属性信息的字符串构造,可以完成上面例子中的输出效果。于是,我们可以将上面的代码改为以下的方式进行输出:
staticvoid UseMyExtensionMethod()
{
List<Person> personList = new List<Person>()
{
new Person(){ID=1,Name="Big Yellow",Age=10},
new Person(){ID=2,Name="Little White",Age=15},
new Person(){ID=3,Name="Middle Blue",Age=7}
};
var datas = personList.Where(delegate(Person p)
{
return p.Age >= 10;
});
foreach (var data in datas)
{
Console.WriteLine(data.FormatOutput());
}
}
3.2 嗦嘎—探秘扩展方法
刚刚我们体验了扩展方法的神奇之处,现在我们本着刨根究底的学习态度,借助Reflector看看编译器到底帮我们做了什么工作?
(1)通过反编译刚刚那个UseMyExtensionMethod方法,我们发现并没有什么奇怪之处。
(2)这时,我们可以将C#切换到IL代码看看,或许会有另一番收获?于是,果断切换之后,发现了真谛!
原来编译器在编译时自动将Person.FormatOutput更改为了PersonExtension.FormatOutput,这时我们仿佛茅塞顿开,所谓的扩展方法,原来就是静态方法的调用而已,所德是乃(原来如此)!于是,我们可以将这样认为:person.FormatOutput() 等同于调用 PersonExtension.FormatOutput(person);
(3)再查看所编译生成的方法,发现this关键已经消失了。我们不禁一声感叹,原来this只是一个标记而已,标记它是扩展的是哪一个类型,在方法体中可以对这个类型的实例进行操作。
3.3 注意—总结扩展方法
(1)如何定义扩展方法:
定义静态类,并添加public的静态方法,第一个参数 代表 扩展方法的扩展类。
a) 它必须放在一个非嵌套、非泛型的静态类中(的静态方法);
b) 它至少有一个参数;
c) 第一个参数必须附加 this 关键字;
d) 第一个参数不能有任何其他修饰符(out/ref)
e) 第一个参数不能是指针类型
(2)当我们把扩展方法定义到其它程序集中时,一定要注意调用扩展方法的环境中需要包含扩展方法所在的命名空间!
(3)如果要扩展的类中本来就有和扩展方法的名称一样的方法,到底会调用成员方法还是扩展方法呢?
答案:编译器默认认为一个表达式是要使用一个实例方法,但如果没有找到,就会检查导入的命名空间和当前命名空间里所有的扩展方法,并匹配到适合的方法。
http://www.cnblogs.com/edisonchou/p/4088959.html
1.1 初识Action
MSDN给出的定义:封装一个方法,该方法不具有参数并且不返回值。
可以使用此委托以参数形式传递方法,而不用显式声明自定义的委托。封装的方法必须与此委托定义的方法签名相对应。也就是说,封装的方法不得具有参数,并且不得返回值。(在 C# 中,该方法必须返回 void)通常,这种方法用于执行某个操作。
现在,我们来看看如何使用Action委托:
(1)先看看之前我们是怎么来使用无返回值委托的例子:
View Code
可以清楚地看出,我们之前要先显式声明了一个名为 ShowValue 的委托,并将对 Name.DisplayToWindow 实例方法的引用分配给其委托实例。
(2)再看看有了Action委托之后我们怎么来达到上面的效果的例子:
View Code
可以清楚地看出,现在使用 Action 委托时,不必显式定义一个封装无参数过程的委托。
1.2 深入Action
在实际开发中,我们经常将一个委托实例作为一个方法的参数进行传递,于是我们来看一下这个典型的场景,再通过Reflector反编译工具查看编译器到底帮我们做了什么好玩的事儿!
(1)首先来看一下在List集合类型的ForEach方法的定义:
//
// 摘要:
// 对 System.Collections.Generic.List<T> 的每个元素执行指定操作。
//
// 参数:
// action:
// 要对 System.Collections.Generic.List<T> 的每个元素执行的 System.Action<T> 委托。
//
// 异常:
// System.ArgumentNullException:
// action 为 null。
publicvoid ForEach(Action<T> action);
可以看出,ForEach方法的参数是一个Action委托实例,也就是说是一个无返回值的委托实例。
(2)定义一个实体类,并通过Action委托使用ForEach方法:
View Code
可以看出,我们为ForEach方法传递了一个Action委托的实例,本质上是一个无返回值的方法指针,遍历输出了每个Person对象的信息。
(3)也许有些童鞋看到上面的还是有点不解,只要你了解过委托,那么我们可以通过Reflector反编译工具去看看编译器到底做了啥事,Action委托的本质就会一如了然:(这里我们可以先看看没有Action的做法,是不是需要首先显式声明了一个无返回值的委托,然后是不是还要顶一个命名的无返回值的方法?)
①将编译好的程序集拖动到Reflector中,可以看到以下的情形:
②现在分别看看编译器为我们自动生成的无返回值的委托定义和方法定义:
可以看出,不管是自动生成的委托还是方法,都是不带返回值的。
③有了上面的分析,我们再来看看执行的语句是怎么被编译的:
可以看出,在编译后的代码里边连new Action<Person>()都省掉了,我们也可以知道,在代码中可以更加简化。但是,首先,我们得了解到底编译器是怎么识别Action委托的。于是,按照前两篇的思路,在反编译后的C#代码看不出什么端倪的时候,切换到IL代码一探究竟:
由IL代码可以看出,还是原来的方法,还是原来的味道。委托还是那个委托,执行委托还是执行那个方法。这里,我们再来看看List类型的ForEach方法是怎么使用Action委托的:
现在,我们可以知道,原来所不解的东西现在终于释怀了:在ForEach会通过一个循环遍历依次调用委托所持有的方法,这个方法是一个符合Action委托定义的无返回值方法。至于,为什么我们可以省略new Action<T>(),则是编译器为我们提供的一个便利。例如,我们在使用List<Person>对象的ForEach方法时,我们可以这样写:
personList.ForEach(delegate(Person p)
{
Console.WriteLine(p.ID + "-" + p.Name + "-" + p.Age);
});
首先,由于我们是使用的personList这个对象(List<Person>类型),所以编译器自动识别了泛型委托的T(即指定类型)为Person。其次,编译器自动将无返回值的匿名方法转换为了new Action<Person>对象。当然,如果是有返回值的匿名方法则会转换为指定类型的new Func<T>()对象,这里因为ForEach只接受无参数的委托实例或方法,所以如果传入了有返回值的匿名方法则会报错。
1.3 你究竟有几个Action可用?
从图中可以看出,.NET Framework为我们提供了多达16个参数的Action委托定义,对于常见的开发场景已经完全够用了。
二、有返回类型的内置委托—Func
2.1 初识Func
MSDN给出的定义:封装一个具有一个参数并返回 TResult 参数指定的类型值的方法。
此委托的定义如下:
publicdelegate TResult Func<in T, out TResult>(T arg)
(1)in T :此委托封装的方法的参数类型。
(2)out TResult :此委托封装的方法的返回值类型。
可以使用此委托表示一种能以参数形式传递的方法,而不用显式声明自定义委托。封装的方法必须与此委托定义的方法签名相对应。也就是说,封装的方法必须具有一个通过值传递给它的参数,并且必须返回值。
2.1.1 没有Func时的使用
View Code
2.1.2 有了Func后的使用
View Code
当然,我们还可以借助匿名方法更加便捷地使用:
View Code
可以清楚地看出,现在使用 Func 委托时,不必显式定义一个新委托并将命名方法分配给该委托。
2.2 深入Func
2.2.1 用法先行:爽一下
我们已经知道Func委托是带指定返回值类型的委托,那么我们来看看在实际开发场景的一幕。还是以刚刚那个数据集合PersonList为例,在很多时候我们需要对从数据库中读取的数据集合进行二次筛选,这时我们可以使用List集合的Select方法,我们将一个Func委托实例作为方法参数传递给Select方法,就可以返回一个符合我们指定条件的新数据集合。
(1)先来看看Select方法的定义:
//
// 摘要:
// 将序列中的每个元素投影到新表中。
//
// 参数:
// source:
// 一个值序列,要对该序列调用转换函数。
//
// selector:
// 应用于每个元素的转换函数。
//
// 类型参数:
// TSource:
// source 中的元素的类型。
//
// TResult:
// selector 返回的值的类型。
//
// 返回结果:
// 一个 System.Collections.Generic.IEnumerable<T>,其元素为对 source 的每个元素调用转换函数的结果。
//
// 异常:
// System.ArgumentNullException:
// source 或 selector 为 null。
publicstatic IEnumerable<TResult> Select<TSource, TResult>(this IEnumerable<TSource> source, Func<TSource, TResult> selector);
可以看出,Select方法中的参数采用了Func泛型委托,根据泛型委托的定义TSource和TResult分别代表要传入的数据类型以及要返回的数据类型。
(2)再来看看如何在程序中使用Func委托:
首先定义一个与源数据类型不同的新数据类型作为返回值类型:
publicclass LitePerson
{
publicstring Name { get; set; }
}
①标准定义版:
List<Person> personList = GetPersonList();
IEnumerable<LitePerson> litePersonList = personList.Select<Person, LitePerson>(
new Func<Person, LitePerson>
(
delegate(Person p)
{
returnnew LitePerson() { Name = p.Name };
}
)
);
②嘻哈简化版:借助编译器提供的自动识别,简化我们的代码
IEnumerable<LitePerson> litePersonList = personList.Select(
delegate(Person p)
{
returnnew LitePerson() { Name = p.Name };
}
);
③绝逼懒人版:借助匿名类和泛型可以大大简化我们的代码
var liteList = personList.Select(delegate(Person p)
{
returnnew { Name = p.Name, AddDate = DateTime.Now };
});
(3)调试运行可以得到以下结果:
2.2.2 原理为王:探一次
(1)通过Reflector反编译,我们再来看看编译器帮我们生成的东东:
(2)看看自动生成的委托和方法的定义:
相信经过上节Action的详细分析,这里大家应该也可以举一反三了解编译器帮我们到底做了什么事儿了,这里我就不再赘述了,后面也不会再赘述此方面的东东(为了节省页面大小)。
当然,和Action类似,.NET基类库为我们也提供了多达16个输入参数的Func委托,但是,输出参数却只有1个。
三、返回bool类型的内置委托—Predicate
3.1 初识Predicate
经过了Func的了解,我们可以知道接下来的这两个Predicate和Comparison其实都属于有返回值类型的委托,他们不过是两个具体的特殊实例而已(一个返回bool类型,一个返回int类型)。
MSDN给出的定义:表示定义一组条件并确定指定对象是否符合这些条件的方法。
它的定义很简单:(这里就不再对其进行解释了)
publicdelegate bool Predicate<in T>(T obj)
此委托由 Array 和 List<T> 类的几种方法使用,常用于在集合中搜索元素。
3.2 深入Predicate
由于Predicate委托常用于在集合中搜索元素,那么我们就来看看如何使用Predicate委托来进行元素的搜索。于是,我们将目光转到List集合的FindAll方法,相信大部分童鞋都用过这个方法。
(1)先来看看FindAll的定义:
//
// 摘要:
// 检索与指定谓词定义的条件匹配的所有元素。
//
// 参数:
// match:
// System.Predicate<T> 委托,用于定义要搜索的元素应满足的条件。
//
// 返回结果:
// 如果找到,则为一个 System.Collections.Generic.List<T>,其中包含与指定谓词所定义的条件相匹配的所有元素;否则为一个空
// System.Collections.Generic.List<T>。
//
// 异常:
// System.ArgumentNullException:
// match 为 null。
public List<T> FindAll(Predicate<T> match);
(2)再来看看FindAll的实现:
(3)现在我们来用一下Predicate委托:还是以那个PersonList集合为例,假如我们要筛选出Age>20的Person,我们就可以使用FindAll方法。现在我们来写一下这个委托:(后面我们会用Lambda表达式来简写,那才叫一个爽!)可以看出,关键点在于:delegate(Person p) { return p.Age > 20; }这一句上,传入参数是Person类型的对象,返回的是一个比较结果即bool值。
View Code
四、返回int类型的内置委托—Comparison
4.1 初识Comparison
MSDN给出的定义:表示比较同一类型的两个对象的方法。
它的定义也很简单:
publicdelegate int Comparison<in T>(T x, T y)
T是要比较的对象的类型,而返回值是一个有符号整数,指示 x 与 y 的相对值,如下表所示:
值 | 含义 |
小于 0 | x 小于 y。 |
0 | x 等于 y。 |
大于 0 | x 大于 y。 |
此委托由 Array 类的 Sort<T>(T[], Comparison<T>) 方法重载和 List<T> 类的 Sort(Comparison<T>) 方法重载使用,用于对数组或列表中的元素进行排序。
4.2 深入Comparison
由于Comparison委托常用于在集合中进行排序,那么我们就来看看如何使用Comparison委托来进行元素的排序。于是,我们将目光转到List集合的Sort方法,相信大部分童鞋也都用过这个方法。
(1)老惯例,还是先看看Sort方法的定义:
//
// 摘要:
// 使用指定的 System.Comparison<T> 对整个 System.Collections.Generic.List<T> 中的元素进行排序。
//
// 参数:
// comparison:
// 比较元素时要使用的 System.Comparison<T>。
//
// 异常:
// System.ArgumentNullException:
// comparison 为 null。
//
// System.ArgumentException:
// 在排序过程中,comparison 的实现会导致错误。例如,将某个项与其自身进行比较时,comparison 可能不返回 0。
publicvoid Sort(Comparison<T> comparison);
(2)再来看看Sort方法的实现:
可以看出,这里虽然使用Comparison委托但最终还是转换成了Comparer比较器,再次调用重载的Array.Sort静态方法进行排序。
(3)现在我们来用一下Comparison委托:还是以那个PersonList集合为例,假如我们要以Age为条件进行降序排列,我们应该怎么来写这个委托呢?
View Code
实现的效果如下图所示:
那么,如果是要进行升序排列呢?只需要改一下:return p2.Age-p1.Age; 更改一下被减数和减数的位置,即可完成升序和降序的切换。
View Code
五、Lambda表达式:[ C# 3.0/.NET 3.x 新增特性 ]
回顾,发现上面的代码,需要传一个 匿名方法 ,写起来特别别扭。于是我们很想知道能否有简化的语法呢?微软告诉咱们:Of Course,必须有,它就是Lambda表达式。Lambda表达式是比匿名方法更简洁的一种匿名方法语法。
Lambda来源:1920年到1930年期间,数学家Alonzo Church等人发明了Lambda积分。Lambda积分是用于表示函数的一套系统,它使用希腊字母Lambda(λ)来表示无名函数。近年来,函数式编程语言(如Lisp)使用这个术语来表示可以直接描述函数定义的表达式,表达式不再需要有名字了。
5.1 初识Lambda表达式 5.1.1 Lambda表达式要点
①Lambda表达式中的参数列表(参数数量、类型和位置)必须与委托相匹配;
②表达式中的参数列表不一定需要包含类型,除非委托有ref或out关键字(此时必须显示声明);
③如果没有参数,必须使用一组空的圆括号;
5.1.2 Lambda使用示例
View Code
调试运行的结果如下:
5.1.3 Lambda本质探析
(1)以上述案例中的Sort方法为例:personList.Sort((p1, p2) => p1.Age - p2.Age);
(2)通过反编译工具,可以看到其实是声明了一个Comparison委托实例:
(3)现在,我们来分析一下具体的步凑:有了前面的基础,现在再来看就轻松了许多,So Easy!
①编译器自动生成了一个Comparison委托:
②编译器帮我们创建了一个符合Comparison委托签名的静态方法:
③实例化Comparison委托变量,并将方法指针传入该委托;
④调用List<T>实例的Sort方法,并传入Comparison委托实例;
其中,前面两步①和②可以通过反编译后的C#代码获知,而后面两步③和④则需要通过IL代码来分析,前面已经介绍过相关,这里就不再赘述。
5.2 回顾Lambda进化史
前面了解了Lambda是什么,这里我们来回顾一下Lambda的演化过程。
从演化过程可以知道,编译器在越来越智能地帮我们做着更多的事儿,而我们却在享受着编译器带来的便利沉浸在高效的开发效率中,变得越来越"懒"了。
5.3 语句Lambda
Lambda表达式有两种类型:一是Lambda表达式,二是语句Lambda。
那么,语句Lambda和表达式Lambda到底有何区别?
ANSWER:语句Lambda 和 表达式Lambda 的区别在于,前者在 =>右边有一个语句块(大括号),而后者只有一个表达式(没有return 和大括号)。
EXAMPLES:
(1)表达式Lambda:
list.FindAll(d => d.Id > 2);// goes to
list.ForEach(d => Response.Write(d.ToString() + "<br/>"));
(2)语句Lambda:
list.ForEach(d => { if (d.Id > 2) { Response.Write(d.ToString() + "<br/>"); } });
可以看出,语句Lambda的右侧有一个语句块,在这个大括号内的语句可能会有多条。
http://www.cnblogs.com/edisonchou/p/4104612.html
标准查询运算符提供了包括筛选、投影、聚合、排序等功能在内的查询功能,其本质是定义在System.Linq.Enumerable类中的50多个为IEnumerable<T>准备的扩展方法。
从上图可以看出,在Enumerable类中提供了很多的扩展方法,这里我们选择其中几个最常用的方法来作一点介绍,使我们能更好地利用它们。首先,我们需要一点数据来进行演示:
View Code
1.1 筛选高手Where方法
Where方法提供了我们对于一个集合的筛选功能,但需要提供一个带bool返回值的"筛选器"(匿名方法、委托、Lambda表达式均可),从而表明集合中某个元素是否应该被返回。这里,我们以上面的数据为例,筛选出集合中所有性别为男,年龄大于20岁的子集合,借助Where方法实现如下:
staticvoid SQOWhereDemo()
{
List<Person> personList = GetPersonList();
List<Person> maleList = personList.Where(p =>
p.Gender == true && p.Age > 20).ToList();
maleList.ForEach(m => Console.WriteLine(m.ToString()));
}
(1)运行结果如下图所示:
(2)由本系列文章的第二篇可知,扩展方法的本质是在运行时调用扩展类的静态方法,而我们写的Lambda表达式在编译时又会被转为匿名方法(准确地说应该是预定义泛型委托实例)作为方法参数传入扩展方法中,最后调用执行该扩展方法生成一个新的List集合返回。
1.2 投影大牛Select方法
Select方法可以查询投射,返回新对象集合。这里,假设我们先筛选出所有男性集合,再根据男性集合中所有项的姓名生成子集合(这是一个不同于原类型的类型),就可以借助Select方法来实现。
staticvoid SQOSelectDemo()
{
List<Person> personList = GetPersonList();
List<LitePerson> liteList = personList.Where(p =>
p.Gender == true).Select(
p => new LitePerson() { Name = p.Name }).ToList();
liteList.ForEach(p => Console.WriteLine(p.ToString()));
}
(1)运行结果如下图所示:
(2)这里也可以采用匿名类,可以省去事先声明LitePerson类的步凑,但需要配合var使用:
var annoyList = personList.Where(p =>
p.Gender == true).Select(
p => new { Name = p.Name }).ToList();
(3)这里因为实现LitePerson类重写了ToString()方法,所以这里直接调用了ToString()方法。
1.3 排序小生OrderBy方法
说到排序,我们马上想起了SQL中的order by语句,而标准查询运算符中也为我们提供了OrderBy这个方法,值得一提的就是我们可以进行多条件的排序,因为OrderBy方法返回的仍然是一个IEnumerable<T>的类型,仍然可以继续使用扩展方法。但要注意的是,第二次应该使用ThenBy方法。
staticvoid SQOOrderByDemo()
{
List<Person> personList = GetPersonList();
// 单条件升序排序
Console.WriteLine("Order by Age ascending:");
List<Person> orderedList = personList.OrderBy(p => p.Age).ToList();
orderedList.ForEach(p => Console.WriteLine(p.ToString()));
// 单条件降序排序
Console.WriteLine("Order by Age descending:");
orderedList = personList.OrderByDescending(p => p.Age).ToList();
orderedList.ForEach(p => Console.WriteLine(p.ToString()));
// 多条件综合排序
Console.WriteLine("Order by Age ascending and ID descending:");
orderedList = personList.OrderBy(p => p.Age)
.ThenByDescending(p => p.ID).ToList();
orderedList.ForEach(p => Console.WriteLine(p.ToString()));
}
运行结果如下图所示:
1.4 连接道士Join方法
在数据库中,我们对两个表或多个表进行连接查询时往往会用到join语句,然后指定两个表之间的关联关系(例如: a.bid = b.aid)。在标准查询运算符中,细心的.NET基类库也为我们提供了Join方法。现在,假设我们有两个类:Person和Children,其中每个Children对象都有一个ParentID,对应Person对象的ID,现需要打印出所有Person和Children的信息,可以借助Join方法来实现。
staticvoid SQOJoinDemo()
{
List<Person> personList = GetPersonList();
List<Children> childrenList = GetChildrenList();
// 连接查询
var joinedList = personList.Join(childrenList,
p => p.ID, c => c.ParentID, (p, c) => new
{
ParentID = p.ID,
ChildID = c.ChildID,
ParentName = p.Name,
ChildName = c.ChildName
}).ToList();
joinedList.ForEach(c => Console.WriteLine(c.ToString()));
}
运行结果如下图所示:
1.5 分组老师GroupBy方法
在数据库中,我们要对查询结果进行分组会用到 group by 语句,在标准查询运算符中,我们也有对应的GroupBy方法。这里,假设我们对Person数据集按照性别进行分类,该怎么来写代码呢?
staticvoid SQOGroupByDemo()
{
List<Person> personList = GetPersonList();
IEnumerable<IGrouping<bool, Person>> groups =
personList.GroupBy(p => p.Gender);
IList<IGrouping<bool, Person>> groupList = groups.ToList();
foreach (IGrouping<bool, Person> group in groupList)
{
Console.WriteLine("Group:{0}", group.Key ? "男" : "女");
foreach (Person p in group)
{
Console.WriteLine(p.ToString());
}
}
}
(1)这里需要注意的是:通过GroupBy方法后返回的是一个IEnumerable<IGrouping<TKey, TSource>>类型,其中TKey是分组依据的类型,这里是根据Gender来分组的,而Gender又是bool类型,所以TKey这里为bool类型。TSource则是分组之后各个元素的类型,这里是将List<Person>集合进行分组,因此分完组后每个元素都存储的是Person类型,所以TSource这里为Person类型,Do you understand now?
(2)运行结果如下图所示:
(3)可能有人会说我咋记得住GroupBy返回的那个类型,太长了,我也不想记。怎么办呢?不怕,我们可以使用var关键字嘛:
var annoyGroups = personList.GroupBy(p => p.Name).ToList();
foreach (var group in annoyGroups)
{
Console.WriteLine("Group:{0}", group.Key);
foreach (var p in group)
{
Console.WriteLine(p.ToString());
}
}
1.6 分页实战Skip与Take方法
相信很多人都使用过标准查询运算符进行分页操作,这里我们再次来看看如何借助Skip与Take方法来实现分页操作。还是以PersonList集合为例,假如页面上的表格每页显示5条数据,该怎么来写代码呢?
staticvoid SQOPagedDemo()
{
// 这里假设每页5行数据
// 第一页
Console.WriteLine("First Page:");
var firstPageData = GetPagedListByIndex(1, 5);
firstPageData.ForEach(d => Console.WriteLine(d.ToString()));
// 第二页
Console.WriteLine("Second Page:");
var secondPageData = GetPagedListByIndex(2, 5);
secondPageData.ForEach(d => Console.WriteLine(d.ToString()));
// 第三页
Console.WriteLine("Third Page:");
var thirdPageData = GetPagedListByIndex(3, 5);
thirdPageData.ForEach(d => Console.WriteLine(d.ToString()));
}
static List<Person> GetPagedListByIndex(int pageIndex, int pageSize)
{
List<Person> dataList = GetMorePersonList();
return dataList.Skip((pageIndex - 1) * pageSize)
.Take(pageSize).ToList();
}
运行结果如下图所示:
1.7 浅谈延迟加载与即时加载
(1)延迟加载(Lazy Loading):只有在我们需要数据的时候才去数据库读取加载它。
在标准查询运算符中,Where方法就是一个典型的延迟加载案例。在实际的开发中,我们往往会使用一些ORM框架例如EF去操作数据库,Where方法的使用则是每次调用都只是在后续生成SQL语句时增加一个查询条件,EF无法确定本次查询是否已经添加结束,所以没有办法木有办法在每个Where方法执行的时候确定最终的SQL语句,只能返回一个DbQuery对象,当使用到这个DbQuery对象的时候,才会根据所有条件生成最终的SQL语句去查询数据库。
var searchResult = personList.Where(p =>
p.Gender == false).Where(p => p.Age > 20)
.Where(p=>p.Name.Contains("奶茶"));
(2)即时加载(Eager Loading):在加载数据时就把该对象相关联的其它表的数据一起加载到内存对象中去。
在标准查询运算符中,FindAll方法就是一个典型的即时加载案例。与延迟加载相对应,在开发中如果使用FindAll方法,EF会根据方法中的条件自动生成SQL语句,然后立即与数据库进行交互获取查询结果,并加载到内存中去。
var searchResult = personList.FindAll(p=>p.Gender == false
&& p.Name.Contains("奶茶"));
二、查询方式谁更快?LINQ:[ C# 3.0/.NET 3.x 新增特性 ]
2.1 初识LINQ:类似SQL风格的代码
LINQ又称语言集成查询,它是C# 3.0的新语法。在更多的人看来,它是一种方便的查询表达式,或者说是和SQL风格接近的代码。
var maleList = from p in personList
where p.Gender == true
select p;
(1)LINQ表达式以"from"开始,以"select 或 group by子句"结尾;
(2)LINQ表达式的输出是一个 IEnumerable<T> 或 IQueryable<T> 集合;(注:T 的类型 由 select 或 group by 推断出来)
2.2 LINQ使用:实现除Skip和Take外的标准查询运算符的功能
(1)基本条件查询:
List<Person> personList = GetPersonList();
List<Children> childList = GetChildrenList();
// 基本条件查询
Console.WriteLine("Basic Query:");
var maleList = from p in personList
where p.Gender == true
select p;
maleList.ToList().ForEach(m =>
Console.WriteLine(m.ToString()));
(2)排序条件查询:
// 排序条件查询
Console.WriteLine("Order Query:");
var orderedList = from p in personList
orderby p.Age descending
orderby p.Name ascending
select p;
orderedList.ToList().ForEach(m =>
Console.WriteLine(m.ToString()));
(3)连接查询:
// Join连接查询
Console.WriteLine("Join Query:");
var joinedList = from p in personList
join c in childList
on p.ID equals c.ParentID
selectnew
{
Person = p,
Child = c
};
foreach (var item in joinedList)
{
Console.WriteLine(item.ToString());
}
(4)分组查询:
// 分组条件查询
Console.WriteLine("Group Query:");
var groupList = from p in personList
group p by p.Gender;
foreach (var group in groupList)
{
Console.WriteLine("Group:{0}",
group.Key? "男":"女");
foreach(var item in group)
{
Console.WriteLine(item.ToString());
}
}
运行结果请参考上一节标准查询运算符中相关的运行结果,或下载附件运行查看,这里不再贴图。
2.3 LINQ本质:生成对应的标准查询运算符
作为一个细心的.Net码农,我们不由得对LINQ表达式为我们做了哪些工作而好奇?于是,我们又想起了我们的"滑板鞋"—Reflector或ILSpy,去看看编译器为我们做了什么事!
(1)以上述的基本条件查询代码为例,我们看到原来编译器将LINQ生成了对应的标准查询运算符,即Where扩展方法:
(2)再来看看排序条件查询的代码,也是生成了对应的标准查询运算符,即OrderBy扩展方法:
(3)总结:LINQ编译后会生成对应的标准查询运算符(查询->Where,排序->OrderBy,连接->Join,分组->GroupBy),所以LINQ表达式其实就是类似于SQL风格的一种更加友好的语法糖而已。其本质还是扩展方法、泛型委托等"旧酒",被一个"新瓶子"所包装了起来,就变得高大上了。
系列总结
转眼之间,四篇文章的介绍就到此结束了,其实本系列介绍的都是不算新语法,其实也可以说成是老语法了。说它们新,只不过是相对于.NET老版本而言,而且平时开发中大家有可能没有注意到的一些细节,本系列做了一个简单的介绍。这几天看到很多园子里的童鞋开始关注C# 6.0的新特性了,粗略看了看,语法糖居多,相信经过了这一系列的探秘,对于新的语法糖,我们可以站在一个比较高的高度去看待它们。最后,谢谢各位园友的浏览,以及给我的一些鼓励,再次感谢!