用 JavaScript 的匿名函数理解 C# 的委托
我的脑子里有个名词一直在纠结:委托。
顾名思义,委托,把事情托付给他人或机构(办理)。造句诸如:“当事人委托律师出庭辩护”,“我能委托你办一件事吗”。 很明显,委托是个抽象动作(Action),目的具体不详,“出庭辩护”,“办一件事”才是真正要做的事。但C#中委托却让我之前一头雾水,因为这个概念从来未有如此摊开摆上台面。
我确信在以往的 JavaScript 编程中,有类似“委托”这个概念的,比如按钮事件绑定,匿名函数。而网上搜罗有关 C# 委托的言语也大多与函数指针、事件绑定有关。下面将用 JavaScript 与 C# 两种“委托”相对比,用于加深理解。
先看JavaScript中,给按钮定义事件的方法:
1 <input type="button" value="Hello" id="btn" />
2 <script type="text/javascript">
3 //方法一:
4 function SayHello()
5 {
6 alert("Hello world!");
7 }
8 document.getElementById("btn").attachEvent("onclick", SayHello);
9
10 //方法二,匿名函数
11 document.getElementById("btn").attachEvent("onclick", function(){alert("Hello world")});
12 </script>
方法一是把一个现有的函数引用(类指针)赋值给按钮的事件,而方法二是在给按钮定义事件时,就地定义一个匿名函数。这两种方法其实是一致的,如果把 SayHello 的定义方式换用另外一种方式,将会发现,这一切不过是代码的游戏:
var SayHello = function(){alert("Hello world!")};
方法一与方法二不过是引用上面代码等号的左边与右边而已!
再看看C#:
1 protected void Page_Load(object sender, EventArgs e)
2 {
3 btn.Click += new System.EventHandler(btn_Click);
4 }
5
6 void btn_Click(object sender, EventArgs e)
7 {
8 //do sth
9 }
这是众所熟悉的代码,btn 是页面上的一个按钮,+= 表示在原有的基础上续加一个事件,这与IE下的 attachEvent(FF下的 addEventLisener) 同出一辙。System.EventHandler 实际上就是一个委托,通过 ILSpy 查看这个类:
1 using System;
2 using System.Runtime.InteropServices;
3 namespace System
4 {
5 [ComVisible(true)]
6 [Serializable]
7 public delegate void EventHandler(object sender, EventArgs e);
8 }
9
10 //构造函数:
11 // System.EventHandler
12 public extern EventHandler(object @object, IntPtr method);
因此,给按钮增加事件,是先创建一个委托,委托再指定需要调用的函数(如btn_Click)。
通过对比两种语言在以上事件定义实例上,C# 非常细致地抽象出这个动作,把委托与事件函数区分开来,这是JavaScript比较模糊的地方(或称为JS的灵活性?)。
再看另外一种场合,JQuery 可以这样遍历数组:
1 <script type="text/javascript">
2 function DoSth(obj, i)
3 {
4 /*do sth*/
5 }
6 $("#box ul li").each(DoSth);
7
8 //或者这样:
9 $("#box ul li").each(function(obj, i){/*do sth*/});
10 </script>
以上代码的 $() 函数返回的是元素数组,each() 方法的作用是遍历并处理此数组。那它是怎样做这样的效果呢? 它把另外一个函数(假设为函数DoSth)当作参数,传到 each() 内部,所有处理动作都由函数DoSth来完成,并且默认规定好了接口,函数DoSth只能接收两个参数,第一个是数组中的元素(弱类型),第二个是整型计数器。
把一个函数当地另外一个函数的参数,这样的案例在原生的 JavaScript 还有如: replace(/re/, function($1){}), array.sort(function(x){}),用法一致,不再讨论。
再看看 C#:
1 protected void Page_Load(object sender, EventArgs e)
2 {
3 List<string> Arr = new List<string>() { "2011年","10月","22日"};
4 //方法一
5 Arr.ForEach(delegate(string txt)
6 {
7 Write(txt);
8 });
9
10 //方法二
11 Arr.ForEach(new Action<string>(Write));
12
13 //方法三
14 Arr.ForEach(Write);
15 }
16
17
18 private void Write(string txt)
19 {
20 Response.Write(txt);
21 }
上面的代码与 JavaScript 何其相似。方法一就地定义委托,并定义委托的实际内容。方法二采用C#3.0的 Action<T> 委托,它是 delegate 类的泛型重载。方法三是编译器给我们提供的便利,它是方法二的简写。
行文至此,委托的概念顿时明晰,混沌的思维豁然开朗,揭开面纱,原来早已熟悉多时,猛拍脑门,狠掐大腿......