匿名函数与委托有着密切的关系。
1、为什么要用匿名方法,什么时候用?
当用常规的委托调用方法时使代码很混乱或者不简洁的时候,可以使用匿名方法(内联代码块定义)。什么意思呢,我们用委托调用方法时,是根据方法名调用的,当需要待用的方法很多时,我们创建很多的方法,但是很多方法又不常用,现在我们可以用匿名方法代替。
例如,我直接用我上篇博客“委托”中的例子(根据学生的属性排序)来讲,我们可以将委托实现的调用改为如下匿名方法的调用:
//匿名方法实现 Sort(s, delegate(Student s1, Student s2)//根据id排序 { return s1.id > s2.id; }); //Sort(s, delegate(Student s1, Student s2)//根据age排序 //{ // return s1.age > s2.age; //});
匿名方法允许我们定义委托对象可以接受的代码块。这个功能省去我们创建委托时想要传递给一个委托的小型代码块的一个额外的步骤。它也消除了类代码中小型方法的混乱。
2、匿名方法的静态数据成员的用法
匿名方法总是以一个delegate关键字开始,后面跟着用在方法和方法体(the method body)本身中的参数。正如从上面示例中所见,用户不需要确定匿名方法的返回类型。它(译注:指返回类型)由方法体中的return语句推断而来。.NET CLR不能执行像匿名方法一样的自由流(free flowing)代码块。CLR要求:它执行的每个方法是一个类型的一部分,并且应该是一个静态(static)方法或实例(instance)方法(译注:若一个方法声明中含有 static 修饰符,则称该方法为静态方法。若其中没有 static 修饰符时,则称该方法为实例方法。静态方法不对特定实例进行操作,在静态方法中引用 this 是编译时错误。实例方法对类的某个给定的实例进行操作,而且可以用 this来访问该实例)。因此当你在一个类的代码中写匿名方法并编译这个代码时,C#编译器默默地在你定义匿名方法的相同的类中创建了一个静态或实例方法。所以匿名方法只是一个在类中定义你自己方法以传递到委托(委托处理器/事件处理器)的方便的语法。
当你编译上面的示例时,C#编译器在类''Program''内部即我们定义匿名方法的地方创建了两个private static方法。它此时用这些static方法的地址取代了匿名方法。编译器决定如何创建静态方法或实例方法取决于匿名方法被定义的类中的静态或实例数据成员的用法。在我们的示例中,我们没有用到任何类''Program''的数据成员,因为调用一个静态方法而不是一个实例方法将是高效的,因此C#编译器创建一个static方法来封装我们的匿名方法的代码。
3、匿名方法的局部变量用法
从根本上说,C#创建了private方法来包装匿名方法。同时这些方法的签名与它们被分配到的委托相匹配。
在让我们进入匿名方法的高级话题。一个匿名方法有封装在其方法体中使用了的环境变量的值的能力。这个封装应用于匿名方法被定义的方法中的所有局部变量。当C#编译器在一个匿名方法的方法体中识别出用到一个局部变量,它就会做如下事情:
1. 创建一个新的private类作为匿名方法被定义的类的一个内部类。
2. 在新类(译注:即内部类)中创建一个公共数据成员,使用与用在匿名方法体中的局部变量相同的类型和名称。
3. 在包装匿名方法的新类中创建一个public实例方法。
4. 用新类中的声明替代局部变量的声明。创建该新类的一个实例代替局部变量的声明。
5. 用新类实例的数据成员替代在匿名方法体内部和外部使用的局部变量。
6. 用在新类中定义的实例方法的地址取代匿名方法的定义。
详细参考:http://www.cnblogs.com/zhili/archive/2012/12/01/anonymousmethod.html
4、总结
匿名方法是C#2.0语言增加的一个非常有用和强大的功能。除了介绍的一些对委托声明和用法上的语法改进,Microsoft已在使匿名方法代码自然融入所包含的方法体方面获得很大进展,包括访问在包含(匿名方法)的方法定义的作用域中的局部变量。
=========================================================
为啥用内联函数(inline functions)
在C++中函数调用需要建立栈环境,进行参数复制,保护调用现场,返回时进行返回值复制,恢复调用现场.这些操作都需要额外开销.那有啥好办法避免频繁的函数调用呢,
首先想到的可能是把函数中的代码直接拿来用,而不用先在某处弄个函数,然后再去调用,当然也只是函数比较简单时可行.但如果多处用到那函数的话,你全部替换会使得代码冗余,可读性下降,于是你想到了用宏,一些简单的函数基本上是些表达式的组合,你用个宏把它全部替换掉.这样用起来代码量会少点,可读性也强点.不过宏只是简单的替换,不会做多余的检查,比如检查函数函数是否匹配啊.这时内联函数就派上用场了.内联函数是结合了宏和函数的优点.一方面它在会把函数用函数体中的内容替换掉,这就不需要调用函数的额外开销,这像宏.另一方面它是在编译阶段而不是预编译阶段,这样编译器能做一些安全检查,比如参数类型检查啊.
内联函数用法
内联函数用法很简单,在调用函数之前先声明那函数为内联函数即可.举个简单例子
inline void Print(); //声明内联函数,当然你也可以把声明和定义放到一起,就不用在其他地方再去定义了
int main()
{
Print();
}
inline void Print() //inline关键字也可去掉
{
cout<<"this is inline function.;
}
另外据说如果函数中有while,switch等复杂控件结构,或者有递归调用.内联函数会自动失效.会当成一般函数去调用了.不过这玩艺也不太好验证.不知道啥时确实去内联了,啥时是只当一般函数处理了.
匿名函数
C#中没有内联函数的概念,不过有个比较类似的概念,叫匿名函数.
举个简单例子
delegate void MyDelegate(string name);
class Program
{
static void Print(string name)
{
Console.WriteLine("hello," + name);
}
static void Main(string[] args)
{
MyDelegate arwen = Print;
arwen("arwen");
}
}
这里用到了代理,类似于一个函数指针.当定义一个代理时要指定一个函数,我们还得另外在某个地方定义一个函数.如果函数比较简单的话我们可以不用在其他地方定义,直接就地展开,用一个匿名函数就OK.例如
MyDelegate weiwen = delegate(string name) { Console.WriteLine("hi," + name); }; //这就是匿名函数
weiwen("weiwen");
在C# 3.0中出现了拉姆达(lambda)表达式,可以把匿名方法进一步简化
MyDelegate weiwenhp = (xxx) => { Console.WriteLine("hi," + xxx);};
weiwenhp("weiwenhp");
这看着更爽吧.函数的参数可以给你推断出来,所以参数你随便用个啥东东表示下,用xxx或者yyy这些乱七八糟的都可以.
拉姆达表达式是从Lisp, Scheme等函数式编程语言里借鉴过来的东东.在C#里主要应用在Linq中.
===================================================================
[C# 基础知识系列]专题十一:匿名方法解析
引言:
感觉好久没有更新博客了的,真是对不住大家了。在这个专题中将介绍匿名方法,匿名方法看名字也能明白,当然就是没有名字的方法了(现实生活中也有很多这样的匿名过程,如匿名投票,匿名举报等等,相信微软在命名方面肯定是根据了生活中例子的),然而匿名方法的理解却不是仅仅是这一句话(这句话指的是没有名字的方法),它还有很多内容,下面就具体介绍下匿名方法有哪些内容
一、匿名方法
之前一直认为匿名方法是在C# 3.0中提出的,之前之所以这么认为主要是因为知道C# 3.0中提出了匿名类型,所以看到匿名方法就很理所当然的认为也是在C# 3.0中提出来,然而经过系统的学习C#特性后才发现匿名方法在C# 2.0 的时候就已经提出来了,从特性的提出发展中可以看出,微软的团队是非常有计划的,后面的特性其实在之前特性的提出就已经计划好,并且后面的特性都是之前特性演变而来,之所以有新特性的提出,主要是为了方便大家编写程序,减轻程序员的工作,让编译器去执行更加复杂的操作,使程序员可以把精力放在实现自己系统的业务逻辑方法(这也是微软的主要思想,也是大部分软件所强调的良好的用户体验),然而匿名方法也正是建立在C#1.0中委托的基础上的(同时C# 2.0中对委托有所增强,提出了泛型委托,以及委托参数的协变和逆变,具体的可以参考本系列的前面专题),下面就具体介绍下为什么说匿名方法是如何建立在委托基础之上的(委托是方法的包装,匿名方法也是方法,只是匿名方法是没有名字的方法而已,所以委托也可以包装匿名方法)。
首先,先介绍下匿名方法的概念,匿名方法——没有名字的方法(方法也就是数学中的函数的概念),匿名方法只是在我们编写的源代码中没有指定名字而已,其实编译器会帮匿名方法生成一个名字,然而就是因为在源代码中没有名字,所以匿名方法只能在定义的时候才能调用,在其他地方不能被调用(匿名方法把方法的定义和方法的实现内嵌在一起),下面通过一个例子来看看匿名方法的使用和如何与委托关联起来的:
namespace 匿名方法Demo { class Program { // 定义投票委托 delegate void VoteDelegate(string name); static void Main(string[] args) { // 实例化委托对象 VoteDelegate votedelegate = new VoteDelegate(new Friend().Vote); // 使用匿名方法的代码 // 匿名方法内联了一个委托实例(可以对照上面的委托实例化的代码来理解) // 使用匿名方法后,我们就不需要定义一个Friend类以及单独定义一个投票方法 // 这样就可以减少代码量,代码少了,阅读起来就容易多了,以至于不会让过多的回调方法的定义而弄糊涂了 //VoteDelegate votedelegate = delegate(string nickname) //{ // Console.WriteLine("昵称为:{0} 来帮Learning Hard投票了", nickname); //}; // 通过调用托来回调Vote()方法 votedelegate("SomeBody"); Console.Read(); } } public class Friend { // 朋友的投票方法 public void Vote(string nickname) { Console.WriteLine("昵称为:{0} 来帮Learning Hard投票了", nickname); } } }
因为前段时间参加了51博客大赛,在投票阶段也拉了好多朋友来帮忙投票的,所以为了感谢他们,所以上面就以投票作为例子来引出匿名方法,注释的部分中已经解释了匿名方法的好处的,可以帮助我们减少书写代码量,便于阅读,然而上面地方可以使用匿名方法来代替委托呢?是不是所有使用委托的地方我们都需要用匿名方法去代替的呢?事实不是这样的,因为匿名方法是没有名字的方法,所以在其他地方就不能被调用,所以不具有复用作用,并且匿名方法自动形成"闭包"(如果对于闭包不理解的朋友可以参考这两个链接:http://baike.baidu.com/view/648413.htm 和http://zh.wikipedia.org/wiki/闭包_(计算机科学) ,我理解的闭包大概是当一个函数中(外部函数)调用了另个一个函数(称内部函数)时,当内部函数使用了外部函数中的变量时,这样就可能会形成闭包。具体的概念可以参考上面的两个链接,关于闭包在后面部分也会给出相关的例子来帮助大家理解,由于匿名函数会形成闭包,这就会延长变量的生命周期)。所以如果委托包装的方法相对简单(就像上面代码中只是单独一行输出语句),并且这个方法在其他地方使用的频率很低时,这时候就可以考虑用匿名方法来代替委托。
二、使用匿名方法来忽略委托参数
第一部分主要介绍了匿名方法的概念,使用以及介绍了我所理解的为什么会有匿名方法的提出(为了方便我们实例化委托实例,通过匿名方法可以内联委托实例,这样就避免额外定义一个实例方法,减少代码量,利于阅读),在这一部分中将介绍匿名方法的另外一个好处——忽略委托参数。下面通过一个示例代码来来帮助大家理解,代码中会有详细的注释,所以这里就不多说了,直接看代码了:
namespace 忽略委托参数Demo { class Program { static void Main(string[] args) { // Timer类在应用程序中生成定期事件 System.Timers.Timer timer = new System.Timers.Timer(); // 该值指示是否引发Elapsed事件 timer.Enabled = true; // 设置引发Elapsed事件的间隔 timer.Interval = 1000; // Elapsed事件是达到间隔时发生,前面设置了时间间隔为1秒, // 所以每一秒就会触发Elapsed事件,从而回调timer_Elapsed方法,输出当前的时间 // timer.Elapsed += new System.Timers.ElapsedEventHandler(timer_Elapsed); // 此时timer_Elapsed方法中的参数根本就不需要,所以我们可以使用匿名方法来省略委托参数 // 省略了参数后我们的代码就更加简洁了,看的多舒服啊 // 在开发WinForm程序中我们经常会用不到委托的参数,此时就可以使用匿名方法来省略参数 timer.Elapsed += delegate { Console.WriteLine(DateTime.Now); }; Console.Read(); } public static void timer_Elapsed(object sender, System.Timers.ElapsedEventArgs e) { Console.WriteLine(DateTime.Now); } } }
运行结果为:
上面代码使用了匿名方法来省略委托参数,然而对于编译器而言,它还是会调用委托的构造函数来实例化委托,所以如果匿名方法能转换为多个委托类型时,此时如果省略了委托参数,编译器就不知道把匿名方法转化为哪个具体的委托类型,所以此时就会出现编译时错误,此时就必须人为的指定参数来告诉编译器如何实例化委托,下面就以创建线程为例子来帮助大家理解匿名方法省略委托参数所带来的问题(因为线程的创建涉及了两个委托类型: public delegate void ThreadStart() 和public delegaye void ParameterizedThreadStart(objec obj)):
class Program { static void Main(string[] args) { new Thread(delegate() { Console.WriteLine("线程一"); }); new Thread(delegate(object o) { Console.WriteLine("线程二"); }); new Thread(delegate { Console.WriteLine("线程三"); }); Console.Read(); } }
此时第三个创建线程的代码会出现下面的编译错误:
三、在匿名方法中捕捉变量
前面介绍中提到使用匿名方法时会形成闭包,闭包指的就是在匿名方法中捕捉了变量,为了更好的理解闭包的概念,首先需要理解两个概念 ——外部变量和被捕捉的外部变量,下面通过一个例子来解释这个两个概念:
class Program { // 定义闭包委托 delegate void ClosureDelegate(); static void Main(string[] args) { closureMethod(); Console.Read(); } // 闭包方法 private static void closureMethod() { // outVariable和capturedVariable对于匿名方法而言都是外部变量 // 然而outVariable是未捕获的外部变量,子所以是未捕获,是因为匿名方法中未引用该变量 string outVariable = "外部变量"; // 而capturedVariable是被匿名方法捕获的外部变量 string capturedVariable = "捕获变量"; ClosureDelegate closuredelegate = delegate { // localvariable是匿名方法中局部变量 string localvariable = "匿名方法局部变量"; Console.WriteLine(capturedVariable+" "+localvariable); }; // 调用委托 closuredelegate(); } }
一个变量被捕捉后,被匿名方法捕捉到的是真的变量,而不是创建委托实例时该变量的值,并且被匿名方法中捕捉到的变量会延长生命周期(意思是说对于一个被捕捉的变量,只要还有任何委托实例引用它,它就一直存在,而不会当委托实例调用结束后就被垃圾回收),下面通过一个具体的例子看看匿名方法是如何延长变量的生命周期的:
class Program { // 定义闭包委托 delegate void ClosureDelegate(); static void Main(string[] args) { ClosureDelegate test = CreateDelegateInstance(); test(); Console.Read(); } // 闭包延长变量的生命周期 private static ClosureDelegate CreateDelegateInstance() { int count = 1; ClosureDelegate closuredelegate = delegate { Console.WriteLine(count); count++; }; // 调用委托 closuredelegate(); return closuredelegate; } }
运行结果为:
第一行中的1是CreateDelegateInstance内部调用委托实例输出的结果,首先大家肯定认为count是在栈上分配的(因为count是值类型),当CreateDelegateInstance方法调用完后,count的值也会被销毁,当执行 test()这行代码时,此时会回调匿名方法来输出count的值,因为count被销毁,按理应该会出现异常才对的,然而结果却为2,然而结果并没有错,根据结果去倒推的话,可以得出,第二次调用委托实例也还是在使用原来的那个count,然而之所以我们认为会有异常抛出,主要原因是因为我们认为count是分配在栈上的,然而事实并不是这样的,count变量并不是分配在栈上的,事实上,编译器会创建一个额外的类来容纳变量(此时count变量时分配在堆上的),CreateDelegateInstance方法有该类的一个实例的引用,所以此时匿名方法捕捉到的变量count是它的一个引用,而不是真真的值,同时匿名方法也延长了变量count的生命周期,使它感觉不再像是一个局部变量,反而像是一个"全局变量"了(因为第二次中调用的委托实例使用的是同一个count)。
匿名方法捕捉到的变量,编译器会额外创建一个类来容纳该变量,对于这点,大家可以通过IL反汇编程序进行查看,下面是上面程序中使用反汇编程序得到的截图:
从上面的截图中可以看出,在源代码中根本没有<>c_DisplayClass1类的定义的,然而这个类真是编译器为我们创建来容纳捕获变量count的,并且该类中容纳了CreateDelegateInstance方法,从上图的左半部分中间语言代码可以看出,源代码中定义的CreateDelegateInstance方法具有该<>c_DisplayClass1的一个引用,在源代码中使用到的count变量编译器认为是<>c_DisplayClass1中的一个字段。
四、小结
这个专题中主要介绍了匿名方法的使用以及匿名方法通过捕获变量来延长变量的生命周期,希望通过本专题的介绍大家可以对匿名方法可以有个全面的认识,并且匿名方法也是Lambda表达式的基础,Lambda表达式只是C# 3.0中提出更简洁的方式来实现匿名方法的。
MSDN:https://msdn.microsoft.com/zh-cn/library/0yw3tz5k.aspx