[C#学习教程-委托]001.大道至简之委托(代理),匿名函数,Lambda表达式
引言:此文翻译自CodeProject上的同名文章<C# Delegates,Anonymous Methods, and Lambda Expressions>,在此一起Mark一下,此篇文章写的大道至简,深入浅出的让大家理解C#中较难的三个概念-委托(代理),匿名函数,Lambda表达式。
内容索引:
- 简介
- 什么是委托?
- 委托
- 灯光,摄像,行动 - 也许我们需要函数Func!
- 匿名函数
- Lambda 表达式
- 写在最后
简介(Introduction)
在.NET中,委托,匿名函数和Lambda表达式很容易发生混淆。我想下面的代码能证实这点。下面哪一个First会被编译?哪一个会返回我们需要的结 果?即Customer.ID=5.答案是6个First不仅被编译,并都获得正确答案,且他们的结果一样。如果你对此感到困惑,那么请继续看这篇文章。
class Customer
{
public int ID { get; set; }
public static bool Test(Customer x)
{
return x.ID == ;
}
}
...
List<Customer> custs = new List<Customer>();
custs.Add(new Customer() { ID = });
custs.Add(new Customer() { ID = }); custs.First(new Func<Customer, bool>(delegate(Customer x) { return x.ID == ; }));
custs.First(new Func<Customer, bool>((Customer x) => x.ID == ));
custs.First(delegate(Customer x) { return x.ID == ; });
custs.First((Customer x) => x.ID == );
custs.First(x => x.ID == );
custs.First(Customer.Test);
首先,什么是委托?(The setup – What are delegates?)
现在你定义一个处理用户订单的购物车ShoppingCart类。管理层决定根据数量,价格等给客人折扣。做为其中的一部分,他们已经实现了处理订单时你 要考虑一方面。不用考虑过多,你简单声明一个变量来保存有“吸引力的折扣”(magicDisCount),然后实现逻辑。
class Program {
static void Main(string[] args) {
new ShoppingCart().Process();
}
}
class ShoppingCart {
public void Process() {
int magicDiscount = ;
// ...
}
}
第二天,异想天开的管理层决定根据购买时间调整折扣。这个很简单,但需要你改动一点代码。
class ShoppingCart {
public void Process() {
int magicDiscount = ;
if (DateTime.Now.Hour < ) {
magicDiscount = ;
}
}
}
接下来一段时间里,管理层又反复添加更多的折扣逻辑。这时你就会在心理抱怨“受够了”。那么我该怎么做才能把这些无聊的逻辑从我的代码中剥离出去,让该处理的人去处理呢?这时你要做的是移交或者委派给相应职能的别人。幸运的是,.NET为此提供了一种叫做“委托”的机制。
委托(Delegates)
如果你有C/C++编程背景,描述委托最好的方法是“函数指针”。对所有人来说,可以认为把委托传递给方法与把值或对象传递给方法一样。比如下面三行代码就表现出一样的基本原则:你在传递数据给Process处理而不是你自己使用。
// 给方法Process传递一个整形值
Process( );
// 给方法Process传递一个ArrayList的引用
Process( new ArrayList() );
// 给方法Process传递一个方法的引用
Process( discountDelegate );
DiscountDelegate是什么?我如何创建?Process方法如何使用?首先如同声明一个类一样,声明一个委托类型。
delegate int DiscountDelegate();
这句话的意思是我们有一个叫DiscountDelegate的委托类型,我们可以像使用类,结构体等一样使用它。它不需要数据参数,但返回一个整数值。像类一样,我们必须创建一个它的实例它才有意义。记住,创建一个委托实例实质上是创建一个方法的引用。创建实例时关键是要明白DiscountDelegate没有任何构造器,它有一个隐式的构造函数来构造一个与它相同签名的方法(没有传入参数,返回一个整数)。那你怎么给这个构造函数一个方法呢?.NET向你提供了一个向它名字一样简单的方法,你所做的只是忽略圆括号。
DiscountDelegate discount = new DiscountDelegate(class.method);
在深入之前,先回到开始的例子,整理一个代码。我们会添加一个Calculator类来帮助我们处理折扣逻辑,并给我们的委托提供一些方法。
delegate int DiscountDelegate(); class Program {
static void Main(string[] args) {
Calculator calc = new Calculator();
DiscountDelegate discount = null;
if (DateTime.Now.Hour < ) {
discount = new DiscountDelegate(calc.Morning);
}
else if (DateTime.Now.Hour < ) {
discount = new DiscountDelegate(calc.Afternoon);
}
else {
discount = new DiscountDelegate(calc.Night);
}
new ShoppingCart().Process(discount);
}
}
class Calculator {
public int Morning() {
return ;
}
public int Afternoon() {
return ;
}
public int Night() {
return ;
}
}
class ShoppingCart {
public void Process(DiscountDelegate discount) {
int magicDiscount = discount();
// ...
}
}
正如你所见,在Calculator类中,我们为每个逻辑分支创建了一个方法。在Main方法中,我们创建一个Calculator实例和一个DiscountDelegate实例,并按照我们所期望的把它们整合在一起。
太棒了,我们不用担心Process方法中的逻辑了,我们只需要简单得回调我们定义的委托。记住!我们不关心委托是如何创建的(或什么时间),我们就像调用其他方法一样调用它。如你所见,另一种理解委托的方法是,它延迟执行一个方法。Calculator方法在过去某个时间本选择,但不会执行,直到我们调用discount()的时候。现在看看我们的解决方案,这里仍然存在一些丑陋的代码。在Calculator类中,我们可以用一个不同的方法来返回替代每个有返回值得方法吗?答案是肯定的,让我们把这些乱糟糟的代码合并起来。
delegate int DiscountDelegate(); class Program {
static void Main(string[] args) {
new ShoppingCart().Process(new DiscountDelegate(Calculator.Calculate));
}
}
class Calculator {
public static int Calculate() {
int discount = ;
if (DateTime.Now.Hour < ) {
discount = ;
}
else if (DateTime.Now.Hour < ) {
discount = ;
}
else {
discount = ;
}
return discount;
}
}
class ShoppingCart {
public void Process(DiscountDelegate discount) {
int magicDiscount = discount();
// ...
}
}
这样子看起来更好点。你会注意到我们用一个静态的Calculate方法替换了所有原来的方法,在Main方法中也不用费心维护一个指向DiscountDelegate的引用。现在你明白了所有关于委托的东西了吗?在2004年.NET1.1中可以这么说,但是很不幸的是,这种框架自那以后更加成熟了。
灯光,摄像,行动 - 也许我们更需要函数Func!(Lights, camera, Action -or- We want the Func!)
微软在.NET 2.0中引入了泛型,并提供了一个泛型委托:Action<T>。老实说,我认为它远不够用。后来在.NET 3.5中,它为我们提供了一些我们不想定义的通用委托。他们扩展了Action,并添加了Func,二者唯一区别在于Func型方法有一个返回值而Action型方法没有。
这意味着我们不需要声明自己的DiscountDelegate,可以用Func<int>替代。为说明这些观点是如何工作的,我们来假设管理层又一次改变了我们的逻辑,我们需要提供一些特殊的折扣。很简单,我们将给Calculate方法传入一个bool型值。
现在我们的委托签名变成Func<bool,int>。注意Calculate方法现在包含一个bool型参数,我们用一个bool值调用discount()。
class Program {
static void Main(string[] args) {
new ShoppingCart().Process(new Func<bool, int>(Calculator.Calculate));
}
} class Calculator {
public static int Calculate(bool special) {
int discount = ;
if (DateTime.Now.Hour < ) {
discount = ;
}
else if (DateTime.Now.Hour < ) {
discount = ;
}
else if (special) {
discount = ;
}
else {
discount = ;
}
return discount;
}
} class ShoppingCart {
public void Process(Func<bool,int> discount) {
int magicDiscount = discount(false);
int magicDiscount2 = discount(true);
}
}
好像还算不错,我们又省了一行代码,这样算结束了吗?当然没有,我们甚至能省掉类型判断。只要我们传递的方法有严格签名的委托,.NET允许我们完全忽略掉显式创建Func<bool,int>。
// 因为Process期望的方法有一个bool型输入参数和返回一个int值,所以下面这句话是正确的
new ShoppingCart().Process(Calculator.Calculate);
至此,首先通过忽略自定义委托,我们省略了代码;然后排出了明确的创建Func委托。我们能继续压缩代码行吗?到此我们才完成此文的一半,答案显然是“能”。
匿名函数(Anonymous methods)
匿名函数能够让你声明一个方法体而不需要给它指定一个名字。在接下来的场景里,它们以一个“普通的”方法存在;但是在你的代码中没有任何方法显式调用它。匿名函数只能在使用委托的时候创建,事实上,它们通过delegate关键字创建。
class Program {
static void Main(string[] args) {
new ShoppingCart().Process(
new Func<bool, int>(delegate(bool x) { return x ? : ; }
));
}
}
正如你所见,我们完全删除了Calculator类的需求。你可以在打括号中添加任何其他方法中的逻辑。如果你在看它如何执行时有困难,那就把delegate(bool x)做为一个方法签名,而不是一个关键字。设想这段代码在一个类里,delegate(bool x){return 5;}是一个完整的合法方法声明(我们确实有一个返回值),恰好delegate是一个保留字,在这里,它让这个方法匿名。
至此,我确信现在你知道这里我们甚至能压缩更多的代码。顺利成章的,我们能忽略显式声明Func委托的需要;.NET让我们使用delegate关键字更方便。
class Program {
static void Main(string[] args) {
new ShoppingCart().Process(
delegate(bool x) { return x ? : ; }
);
}
}
当把.NET方法做为委托参数时或处理时间时,就能看到匿名函数的真正用处。之前,你会为你所关注的所有可能行为创建了一个方法,现在你仅需以内联的方式创建它们,并可以避免污染你的命名空间。
// 创建一个匿名比对方法
custs.Sort(delegate(Customer c1, Customer c2) {
return Comparer<int>.Default.Compare(c1.ID, c2.ID);
}); // 创建一个匿名事件
button1.Click += delegate(object o, EventArgs e) { MessageBox.Show("Click!"); };
Lambda表达式(Anonymous methods)
MSDN中写道:“Lambda 表达式”是一个匿名函数,它可以包含表达式和语句,并且可用于创建委托或表达式树类型。你应当明白“用户创建委托”部分,但什么是“表达式”呢?老实说,表达式和表达式树不在此为讨论范围内。现在我们唯一需要明白的是,表达式是.NET程序运行时表示数据或对象的代码(C#代码)。引用Jon Skeet的话:“表达式树是一种表达逻辑,这样其他的代码可以查询的方法。当一个lambda表达式转换成一个表达式树,编译器不会发出了lambda表达式的白细胞介素,它会发出白细胞介素这将会建立一个表达式树表示相同的逻辑。”
我们需要关注的是Lambda表达式替换匿名函数,和其他的特性。回顾我们最后例子,我们已经在一行代码里压缩了处理整个折扣算法的逻辑。
class Program {
static void Main(string[] args) {
new ShoppingCart().Process(
delegate(bool x) { return x ? : ; }
);
}
}
你相信我们能让这个更短吗?Lambda表达式用'=>'运算符表明什么参数传递给表达式。编译器进一步处理,允许我们忽略类型并自动替我们推断这些类型。如果你有2个或更多个参数,你需要用圆括号:(x,y)=>。如果只有一个,你设置不需要这样:x=>。
static void Main(string[] args) {
Func<bool, int> del = x => x ? : ;
new ShoppingCart().Process(del);
}
// 更短啦...
static void Main(string[] args) {
new ShoppingCart().Process(x => x ? : );
}
就是这样子。x被推断为bool型,并且有返回值,因为Process接收一个Func<bool,int>。如果我们想实现像之前那样的完整代码块,我们只需要加上大括号。
static void Main(string[] args) {
new ShoppingCart().Process( x => {
int discount = ;
if (DateTime.Now.Hour < ) {
discount = ;
}
else if (DateTime.Now.Hour < ) {
discount = ;
}
else if(x) {
discount = ;
}
else {
discount = ;
}
return discount;
});
}
写在最后(One last thing…)
使用与不使用大括号有一个重要的不同。当你用时,你创建一个“语句Lambda”,反之,它是"表达Lambda"。语句Lambda能执行多条语句(因此需要大括号),但不能创建表达树。你可能只在使用IQueryable接口是遇到这个问题。下面的例子说明这个问题。
List<string> list = new List<string>();
IQueryable<string> query = list.AsQueryable();
list.Add("one");
list.Add("two");
list.Add("three"); string foo = list.First(x => x.EndsWith("o"));
string bar = query.First(x => x.EndsWith("o"));
// foo and bar are now both 'two' as expected
foo = list.First(x => { return x.EndsWith("e"); }); // no error
bar = query.First(x => { return x.EndsWith("e"); }); // error
bar = query.First((Func<string,bool>)(x => { return x.EndsWith("e"); })); // no error
倒数第二行在编译时失败。这是因为IQueryable.First期望得到一个表达式作为参数,然而List<T>.First期望得到一个委托。你可以按照最后一行强制转换Lambda到一个委托(使用First的方法重载)。
这里很难结束讨论,但是我觉得必须停止。Lambda大体上分为两类:一类创建匿名函数和委托;另一类创建表达式。表达式自成一体,并不是.NET开发者的必备知识(无疑在LINQ中已有实现)。
结论(Conclusion)
我希望这篇文章能够让大家完全了解之委托(代理),匿名函数,Lambda表达式的概念,并能够让大家各自灵活运用。
本站文章为宝宝巴士 SD.Team原创,转载务必在明显处注明:(作者官方网站:宝宝巴士)
转载自【宝宝巴士SuperDo团队】 原文链接: http://www.cnblogs.com/superdo/p/4837143.html
[C#学习教程-委托]001.大道至简之委托(代理),匿名函数,Lambda表达式的更多相关文章
- (28)C#委托,匿名函数,lambda表达式,事件
一.委托 委托是一种用于封装命名和匿名方法的引用类型. 把方法当参数,传给另一个方法(这么说好理解,但实际上方法不能当参数,传入的是委托类型),委托是一种引用类型,委托里包含很多方法的引用 创建的方法 ...
- Python学习笔记010——匿名函数lambda
1 语法 my_lambda = lambda arg1, arg2 : arg1 + arg2 + 1 arg1.arg2:参数 arg1 + arg2 + 1 :表达式 2 描述 匿名函数不需要r ...
- Java学习day35(《大道至简》读后感)
对于大一之前的我来说,并不明白计算机这个专业要做的是什么,在我的眼中敲敲键盘打打字就是这个专业的全部:对于现在的我而言,这样的想法显然是十分幼稚的. 当初高考完填报志愿时选择了这门专业,也并不是因为自 ...
- C#多线程+委托+匿名方法+Lambda表达式
线程 下面是百度写的: 定义英文:Thread每个正在系统上运行的程序都是一个进程.每个进程包含一到多个线程.进程也可能是整个程序或者是部分程序的动态执行.线程是一组指令的集合,或者是程序的特殊段,它 ...
- 委托-异步调用-泛型委托-匿名方法-Lambda表达式-事件【转】
1. 委托 From: http://www.cnblogs.com/daxnet/archive/2008/11/08/1687014.html 类是对象的抽象,而委托则可以看成是函数的抽象.一个委 ...
- C#委托总结-匿名方法&Lambda表达式
1,匿名方法 匿名方法可以在声明委托变量时初始化表达式,语法如下 之前写过这么一段代码: delegate void MyDel(string value); class Program { void ...
- 委托delegate 泛型委托action<> 返回值泛型委托Func<> 匿名方法 lambda表达式 的理解
1.使用简单委托 namespace 简单委托 { class Program { //委托方法签名 delegate void MyBookDel(int a); //定义委托 static MyB ...
- [PHP学习教程 - 数字]001.数字补0(Num padding)
引言:在日常工作中,经常要用到数字前后补0的操作,如:日期格式yyyy-MM-dd等等. 在php中有多种前后填充函数——今天,我们就介绍常用的两种,实现数字补零: str_pad sprintf 大 ...
- [PHP学习教程 - 网络]001.下载/采集远程文件到本地(Download File)
引言:如何把http://mzitu.com里的图片全部下载下来呢? 一身浩然正气的AC陷入的深思.... 当然这里涉及到的功能有线程,网页请求,页面提取,下载图片等等.今天,我们先讲一下如何下载文件 ...
随机推荐
- .Net Core中使用Dapper构建泛型仓储
前言:Dapper是.NET的简单对象映射器,在速度方面拥有ORM之王的称号,与使用原始ADO.NET读取数据一样快.ORM是对象关系映射器,它负责数据库和编程语言之间的映射. 仓储主要是用来解耦业务 ...
- STL之traits编程技法
traits编程技法利用了“内嵌型别”的编程技巧与编译器的template参数推导功能. 下面主要看看利用traits编程技法实现的迭代器萃取机制. 5种迭代器类型定义: struct input_i ...
- 模块(类)之间解耦利器:EventPublishSubscribeUtils 事件发布订阅工具类
如果熟悉C#语言的小伙伴们一般都会知道委托.事件的好处,只需在某个类中提前定义好公开的委托或事件(委托的特殊表现形式)变量,然后在其它类中就可以很随意的订阅该委托或事件,当委托或事件被触发执行时,会自 ...
- POJ2686(状压)
描述: \(m个城市有p条双向道路.道路的花费是道路的距离/票上的数字.给出n张票,求a->b的最短路\). 开始本来想老套路把城市状态来压缩,但城市最多可以有30个,故考虑把船票压缩. 定义\ ...
- 【Linux基础总结】Linux基本命令
Linux基本命令 Linux系统下的文件类型.权限.所属用户与组 文件类型 - (文件) d(目录) ->类似windows系统下的文件夹 l (链接) ->类似windows系统下的快 ...
- java1.8新特性之stream
什么是Stream? Stream字面意思是流,在java中是指一个来自数据源的元素队列并支持聚合操作,存在于java.util包中,又或者说是能应用在一组元素上一次执行的操作序列.(stream是一 ...
- HMM-前向后向算法(附代码)
目录 基本要素 HMM三大问题 概率计算问题 前向算法 后向算法 前向-后向算法 基本要素 状态 \(N\)个 状态序列 \(S = s_1,s_2,...\) 观测序列 \(O=O_1,O_2,.. ...
- JDBC10 Blob二进制对象
//将图片输入到数据库中 // String sql="insert into t_user2 (username,headImg) values (?,?)"; // ps=co ...
- [CodeForces 344D Alternating Current]栈
题意:两根导线绕在一起,问能不能拉成两条平行线,只能向两端拉不能绕 思路:从左至右,对+-号分别进行配对,遇到连续的两个“+”或连续的两个“-”即可消掉,最后如果全部能消掉则能拉成平行线.拿两根线绕一 ...
- 接口测试/soapUI
忙过了2019年的下半年终于在2020年快上线了,~鞭炮噼啪过~ 项目技术架构:XML请求数据 -> JAVA (转换)-> JOSN请求数据 项目使用工具:soapUI/Jmeter,m ...