用 JavaScript 的匿名函数理解 C# 的委托

我的脑子里有个名词一直在纠结:委托。

顾名思义,委托,把事情托付给他人或机构(办理)。造句诸如:“当事人委托律师出庭辩护”,“我能委托你办一件事吗”。 很明显,委托是个抽象动作(Action),目的具体不详,“出庭辩护”,“办一件事”才是真正要做的事。但C#中委托却让我之前一头雾水,因为这个概念从来未有如此摊开摆上台面。

我确信在以往的 JavaScript 编程中,有类似“委托”这个概念的,比如按钮事件绑定,匿名函数。而网上搜罗有关 C# 委托的言语也大多与函数指针、事件绑定有关。下面将用 JavaScript 与 C# 两种“委托”相对比,用于加深理解。

先看JavaScript中,给按钮定义事件的方法:

 <input type="button" value="Hello" id="btn" />
 <script type="text/javascript">
 //方法一:
 function SayHello()
 {
   alert("Hello world!");
 }
 document.getElementById("btn").attachEvent("onclick", SayHello);
 
 //方法二,匿名函数
 document.getElementById("btn").attachEvent("onclick", function(){alert("Hello world")});
 </script>

方法一是把一个现有的函数引用(类指针)赋值给按钮的事件,而方法二是在给按钮定义事件时,就地定义一个匿名函数。这两种方法其实是一致的,如果把 SayHello 的定义方式换用另外一种方式,将会发现,这一切不过是代码的游戏:

var SayHello = function(){alert("Hello world!")};

方法一与方法二不过是引用上面代码等号的左边与右边而已!

再看看C#:

protected void Page_Load(object sender, EventArgs e)
{
    btn.Click += new System.EventHandler(btn_Click);            
}

void btn_Click(object sender, EventArgs e)
{
    //do sth
}

这是众所熟悉的代码,btn 是页面上的一个按钮,+= 表示在原有的基础上续加一个事件,这与IE下的 attachEvent(FF下的 addEventLisener) 同出一辙。System.EventHandler 实际上就是一个委托,通过 ILSpy 查看这个类:

using System;
using System.Runtime.InteropServices;
 namespace System
 {
     [ComVisible(true)]
     [Serializable]
     public delegate void EventHandler(object sender, EventArgs e);
 }
 
 //构造函数:
 // System.EventHandler
 public extern EventHandler(object @object, IntPtr method);

因此,给按钮增加事件,是先创建一个委托,委托再指定需要调用的函数(如btn_Click)。

通过对比两种语言在以上事件定义实例上,C# 非常细致地抽象出这个动作,把委托与事件函数区分开来,这是JavaScript比较模糊的地方(或称为JS的灵活性?)。

再看另外一种场合,JQuery 可以这样遍历数组:

 <script type="text/javascript">
 function DoSth(obj, i)
 {
    /*do sth*/
 }
 $("#box ul li").each(DoSth);
 
 //或者这样:
 $("#box ul li").each(function(obj, i){/*do sth*/});
 </script>

以上代码的 $() 函数返回的是元素数组,each() 方法的作用是遍历并处理此数组。那它是怎样做这样的效果呢? 它把另外一个函数(假设为函数DoSth)当作参数,传到 each() 内部,所有处理动作都由函数DoSth来完成,并且默认规定好了接口,函数DoSth只能接收两个参数,第一个是数组中的元素(弱类型),第二个是整型计数器。

把一个函数当地另外一个函数的参数,这样的案例在原生的 JavaScript 还有如: replace(/re/, function($1){}), array.sort(function(x){}),用法一致,不再讨论。

再看看 C#:

protected void Page_Load(object sender, EventArgs e)
{
         List<string> Arr = new List<string>() { "2011年","10月","22日"};
         //方法一
         Arr.ForEach(delegate(string txt)
         {
             Write(txt);
         });
 
         //方法二
         Arr.ForEach(new Action<string>(Write));
 
         //方法三
         Arr.ForEach(Write); 
}
 
private void Write(string txt)
{
         Response.Write(txt);
}

上面的代码与 JavaScript 何其相似。方法一就地定义委托,并定义委托的实际内容。方法二采用C#3.0的 Action<T> 委托,它是 delegate 类的泛型重载。方法三是编译器给我们提供的便利,它是方法二的简写。

 

行文至此,委托的概念顿时明晰,混沌的思维豁然开朗,揭开面纱,原来早已熟悉多时,猛拍脑门,狠掐大腿......


本文最早发布于博客园:https://www.cnblogs.com/xmlnode/archive/2011/10/22/2220890.html,再转帖至此。

2020-06-19 C#

发布评论