C#中的委托(Delegate)和事件(Event)

分类:

  把C#中的委托(Delegate)和事件(Event)放到现在讲是有目的的:给下次写的设计模式——观察者(Observer)有一个参考。
 
  委托和事件应该是C#相较于C++等之前的非托管的语言提出的一个新的术语(term)。“旧瓶装新酒”这样的描述似乎有些“贬义”,但确实是这样。委托也好,事件也好最初的起源是C/C++中的函数指针,关于函数指针的简单介绍可以参见我以前的一篇《 C/C++中指向函数的指针》。不过旧瓶装新酒没有什么不好,反而给人添加了许多新滋味。
 
1. Function pointer--the origin of delegates and events .
 
 
  书回正传,既然函数指针是它们(委托和事件)的起源。那我们先看看什么情况下我们需要函数指针。函数指针最常用的方式就是回调(callback)——在函数休内回调主函数里的函数。有些绕口,看代码:
 

//Platform: WinXP + VC6.0
#include <iostream.h>
#include <list>
using namespace std;

void max(int a, int b)
{
    cout<<"now call max("<<a<<","<<b<<")..."<<endl;
    int t = a>b?a:b;
    cout<<t<<endl;
}
void min(int a, int b)
{
    cout<<"now call min("<<a<<","<<b<<")..."<<endl;
    int t = a<b?a:b;
    cout<<t<<endl;
}
typedef void (*myFun)(int a, int b); //定义一个函数指针用来引用max,min


//回调函数
void callback(myFun fun, int a, int b)
{
    fun(a,b);
}
void main()
{
    int i = 10;
    int j = 55;
    callback(max,i,j); 

    callback(min,i,j);
}

 
Output:
now call max(10,55)...
55
now call min(10,55)...
10
Press any key to continue

  输出的结果有可能另一些对函数指针不熟悉的朋友我些意外,我们并没在main()中显式调用max(),min()呀,怎么会调用到它们呢。再仔细检查一下:你可能发现了:
 
  callback(max,i,j); 
 
这个函数调用了max(),这下好了。你便可以回答类似于这样的问题:我怎么在一个函数(callback)体内调用[主调用函数中的函数(max或min)],最好能通过参数指入具体需要指定哪一个函数?
 
  这便是函数指针的作用了,通过转入函数指针,可以很方便的回调(callback)另外一些函数,而且可以实现参观化具体需要回调用的函数。
 
  2,Introduce delegate in c#;
 
 
  .net里一向是"忌讳"提及"指针"的,"指针"很多程度上意味着不安全。C#.net里便提出了一个新的术语:委托(delegate)来实现类似函数指针的功能。我们来看看在C#中怎么样实现上面的例子。
 

//Platform: WinXP + C# in vs2003
using System;
namespace Class1
{ 
    class ExcelProgram
    {
        static void max(int a, int b)
        {
            Console.WriteLine("now call max({0},{1})",a,b);
            int t = a>b?a:b;
            Console.WriteLine(t);
        }
        static void min(int a, int b)
        {
            Console.WriteLine("now call min({0},{1})",a,b);
            int t = a<b?a:b;
            Console.WriteLine(t);
        }
        delegate void myFun(int a, int b); //定义一个委托用来引用max,min

        //回调函数
        static void callback(myFun fun, int a, int b)
        {
            fun(a,b);
        }
        [STAThread]
        static void Main(string[] args)
        {
            int i = 10;
            int j = 55;
            callback(new myFun(max),i,j); 
            callback(new myFun(min),i,j);
            Console.ReadLine();
        }
    }
}

 
其实代码上大同小异,除了几个static申明以外(C#除静态成员外必须要求对象引用),最大的变化要算定义"函数指指",哦...不..不..不..应该是定义"委托"(小样穿上马甲了..). 定义委托的语法如下:
 
     delegate void  myFun(int a, int b);      //定义一个委托用来引用max,min
 
其中delegate是关键字,myFun是委托名,剩下的是函数签名(signature).我们可以申明一个委托:
 
   myFun Max = new myFun(max);
 
那么上面的回调函数的代码便可以写成:callback(Max,i,j);
 
3, Difference between function pointer and delegate;
 
 
  委托除了可以引用一个函数外,能力上还有了一些加强,其中有一点不得不提的是:多点委托(Multicast delegate).简单地讲就是可以通过一个申明一个委托,来调用多个函数,不信?我们只要稍微更改一下上面的C#代码中的Main函数就可以了,类似:
 
  static void Main(string[] args)
  {
   int i = 10;
   int j = 55;
 
   myFun mulCast = new myFun(max);
   mulCast += new myFun(min);      
//(1)
 
   callback( mulCast,i,j);   
    //callback(new myFun(min),i,j);
   Console.ReadLine();
  }
 
输出如下:
now call max(10,55)...
55
now call min(10,55)...
10
Press any key to continue
 
  没骗你吧,我们只用了一个委托mulCast便同时调用了max和min。不知你注意到没有,上面代码的(1)处用"+="给已经存在的委托(mulCast)又加了一个函数(min)。这样看来C#中的委托更像一个函数指针链表。实质是在C#中,delegate关键字指定的委托自动从 System.MulticastDelegate派生.而 System.MulticastDelegate是一个带有链接的委托列表,在callback中只需调用mulCast的引用便可以以同样的参数调用该链表中的所有函数。
 
  如果还是觉得不过隐,那我们就继续,下图展示了刚才那段C#代码的IL(用ILDasm反汇编即可):
 
 
 
在C#中委托是作为一个特殊的类型(Type,Object)来对待的,委托对象也有自己的成员:BeginInvoke,EndInvoke,Invoke。这几个成员是你定义一个委托时编译器帮你自动自成的,而且他们都是virtual函数,具体函数体由runtime来实现。我们双击一个callback,可以看见以下IL:
{
  // 代码大小       9 (0x9)
  .maxstack  8
  IL_0000:  ldarg.0
  IL_0001:  ldarg.1
  IL_0002:  ldarg.2
  IL_0003:   callvirt   instance void Class1.ExcelProgram/myFun::Invoke(int32,
                                                                       int32)
  IL_0008:  ret
} // end of method ExcelProgram::callback
 
从这段IL我们可以看出,当我们使用语句:fun(a,b)时,调用的却是委托对象(即然委托是类型,那么他自也就会有对象)的myFun::Invoke().该委托对象(即上面的mulCast)通过调用Invoke来调用对象本身所关系的函数引用。
 
  那我们再看看,一个委托对象是怎么样关联到函数的呢,我们双击Main函数,可以看到以下IL,虽然IL语法复杂但仍不影响我们了解它是怎么样将一个委托关联到一个(或多个)函数的引用的。
 
.method private hidebysig static void  Main(string[] args) cil managed
{
  .entrypoint
  .custom instance void [mscorlib]System.STAThreadAttribute::.ctor() = ( 01 00 00 00 ) 
  // 代码大小       58 (0x3a)
  .maxstack  4
  .locals ([0] int32 i,
           [1] int32 j,
            [2] class Class1.ExcelProgram/myFun  mulCast)
  IL_0000:  ldc.i4.s   10
  IL_0002:  stloc.0
  IL_0003:  ldc.i4.s   55
  IL_0005:  stloc.1
  IL_0006:  ldnull
  IL_0007:  ldftn      void Class1.ExcelProgram::max(int32,
                                                     int32)
  IL_000d:  newobj     instance void Class1.ExcelProgram/myFun::.ctor(object,
                                                                      native int)
  IL_0012:  
stloc.2
  IL_0013:  ldloc.2
  IL_0014:  ldnull
   IL_0015:  ldftn      void Class1.ExcelProgram::min(int32,
                                                     int32)
  IL_001b:  newobj     instance void Class1.ExcelProgram/myFun::.ctor(object,
                                                                      native int)
  IL_0020:  call       class [mscorlib]System.Delegate [mscorlib]System.Delegate::Combine(class [mscorlib]System.Delegate,
                                                                                          class [mscorlib]System.Delegate)
  IL_0025:  castclass  Class1.ExcelProgram/myFun
  IL_002a:  stloc.2
  IL_002b:  ldloc.2
  IL_002c:  ldloc.0
  IL_002d:  ldloc.1
  IL_002e:  call       void Class1.ExcelProgram::callback(class Class1.ExcelProgram/myFun,
                                                          int32,
                                                          int32)
  IL_0033:  call       string [mscorlib]System.Console::ReadLine()
  IL_0038:  pop
  IL_0039:  ret
} // end of method ExcelProgram::Main
 
从上面的IL可以看出对于语句:
  myFun mulCast = new myFun(max);
是通过以max作为参数构建一个委托对象mulCast。但对于语句:
  mulCast += new myFun(min);
等价于(你甚至可以用下面的语句代码上面的:mulCast += new myFun(min)):
  mulCast = (myFun) Delegate.Combine(mulCast, new myFun(min));
 
  哦,原来是通过调用Delegate.Combine的静态方法将mulCast和min函数进行关联,Delegate.Combine方法只是简单地将min函数的引用加至委托对象mulCast的函数引用列表中。
 
  4,Introduce event;
 
 
  事件/消息机制是Windows的核心,其实提供事件功能的却是函数指针,你信么?接下来我们再看看C#事件(Event).在C#中 事件是一类特殊的委托.
  一个类提供了"事件",那么他至少提供了以下字段/方法:
 
  一个委托类型的字段(field),用来保存一旦事件时通知哪些对象。即通知所有订阅该事件的对象.别忘记C#中委托是支持多播的。
  两个方法,以委托类型为参数。作用是将 订阅该事件的对象方法加至上面的委托类型字段中,以便事件发生后可以通过调用该方法来通知对象事件已发生。
 
  我们简单地定义一个类Test,该类支持事件:
  class Test
  {
   public event EventHandler OnClick;
 
   public void GenEvent(EventArgs e)   //引发事件方法
   {
    EventHandler temp = OnClick;  
    //通知所有已订阅事件的对象
    if(temp != null)
     temp(this,e);  
   }
  
  }
 
我们反汇编这段代码,如下图:
 
 
  简单地定义一个字段哪来的那么多方法?其实这都是编译器帮你加上去的。当你定义一个事件时,编译器为了实现事件的功能会自动加上两个方法来提供“订阅”和“取消订阅”的功能。
  通过下面的语法,你便可以订阅事件:
  test.OnClick +=new EventHandler(test_OnClick);
  也就是说,一旦test事件发生时(通过调用test.GenEvent()方法)。test便会调用注册到OnClick上的方法。来通知所有订阅该事件的对象。
 
  订阅是什么?“订阅就是调用定义事件时自动生成的add_OnClick.”“那取消订阅就是调用定义事件时自动生成的remove_OnClick”,恭喜你!都学会抢答了.对于上面的订阅事件语句,逻辑意义上等同于:
  test.add_OnClick(new EventHandler(test_OnClick));
但C#并不能直接调用该方法,只能通过 "+=" 来实现。来看IL:

  IL_003b:  ldftn      void Class1.ExcelProgram::test_OnClick(object,
                                                              class [mscorlib]System.EventArgs)   //先将test_OnClick压栈
  IL_0041:  newobj     instance void [mscorlib]System.EventHandler::.ctor(object,
                                                                         native int)               //new一个委托对对象
  IL_0046:  callvirt   instance void Class1.ExcelProgram/Test::add_OnClick(class [mscorlib]System.EventHandler)    //通过调用add_OnClick方法将上面生委托加至test的事件(委托列表)中.
 
 
  5,summarize.
 
  如果对设计模式中的观察者模式较为熟悉的话。其实支持事件的类也就是观察者模式中的Subject(主题,我个人比较喜欢这么译).而所有订阅事件的对象构成了Observers.
 
  最后来句总结吧,总结也许不严谨,但提供理解那还是绝佳滴..我 骗你..(鼻子又变长了).....
  "委托"是"函数指针"链表,当然该链表也可以只有一个元素,如果这样的话:"委托"  约等于 "函数指针";
  "事件"是一类特特殊的"委托",你定义一个"事件",表示你同时定义了:一个委托+两个方法。
 
  后记:如果还不理解事件,先不要急,说不定你先把它忘记不想,等会一闪光,你就会理解了。或者你等着我下一篇《设计模式----观察者(Observer)》,我想等你看完设计模式中的观察者之后再回来看"事件",看"多播委托(MulticastDelegate)"应该可以:忽然开朗。
 
  如果还觉得不过隐。下面给出一个很好的帮助理解的例子,来自Jeffrey Richter.希望我的注解能帮上些忙:
  

using System;
using System.Text;
using System.Data;

namespace Class1
{    
    //定义事件引发时,需要传的参数
    class NewMailEventArgs:EventArgs
    {
        private readonly string m_from;
        private readonly string m_to;
        private readonly string m_subject;
        public NewMailEventArgs(string from, string to, string subject)
        {
            m_from = from;
            m_to = to;
            m_subject = subject;
        }
        public string From
        {
            get{return m_from;}
        }
        public string To
        {
            get{return m_to;}
        }
        public string Subject
        {
            get{return m_subject;}
        }

    }

    //事件所用的委托(链表)
    delegate void NewMailEventHandler(object sender, NewMailEventArgs e);

    //提供事件的类
    class MailManager
    {
        public event NewMailEventHandler NewMail;
        //通知已订阅事件的对象
        protected virtual void OnNewMail(NewMailEventArgs e)
        {
            NewMailEventHandler temp = NewMail; //MulticastDelegate一个委托链表
            //通知所有已订阅事件的对象
            if(temp != null)
                temp(this,e); 
//通过事件NewMail(一种特殊的委托)逐一回调客户端的方法

        }
        //提供一个方法,引发事件
        public void SimulateNewMail(string from, string to, string subject)
        {
            NewMailEventArgs e = new NewMailEventArgs(from,to,subject);
            OnNewMail(e);
        }
    }


    //使用事件
    class Fax
    {
        public Fax(MailManager mm)
        {
            //Subscribe 
            mm.NewMail += new NewMailEventHandler(Fax_NewMail);
        }
        private void Fax_NewMail(object sender, NewMailEventArgs e)
        {
            Console.WriteLine("Message arrived at Fax...");
            Console.WriteLine("From={0}, To={1}, Subject='{2}'",e.From,e.To,e.Subject);
        }
        public void Unregister(MailManager mm)
        {
            mm.NewMail -= new NewMailEventHandler(Fax_NewMail);
        }
    }
    class Print
    {
        public Print(MailManager mm)
        {
            //Subscribe ,在mm.NewMail的委托链表中加入Print_NewMail方法
            mm.NewMail += new NewMailEventHandler(Print_NewMail);
        }
        private void Print_NewMail(object sender, NewMailEventArgs e)
        {
            Console.WriteLine("Message arrived at Print...");
            Console.WriteLine("From={0}, To={1}, Subject='{2}'",e.From,e.To,e.Subject);
        }
        public void Unregister(MailManager mm)
        {
            mm.NewMail -= new NewMailEventHandler(Print_NewMail);
        }
    }

    class ExcelProgram
    {
        [STAThread]
        static void Main(string[] args)
        {    
            MailManager mm = new MailManager();
            if(true)
            {
                Fax fax = new Fax(mm);
                Print prt = new Print(mm);
            }

            mm.SimulateNewMail("Anco","Jerry","Event test");
            Console.ReadLine();
        }
    }
}


 事件(event):


让我通过一个例子来模拟事件的整个过程:


   1. 创建一个button类,它里面有一个click 事件。
   2. 创建一个Form类,他里面有一个我们上面定义的button类。
   3. 要求:当我们用户单击button类的时候From类要对他进行处理,输出一条信息“我知道你被单击了”


请看下图:


首先我们会单击button,然后button会通知Form,然后From就作出相应。这个过程在C#里面应该怎么做到呢?


下面我会列出上述例子的源代码(这里就不介绍怎么声明event等等内容了):


using System;
using System.Collections.Generic;
using System.Text;


namespace UsingEvent
{
    public delegate void ClickEventHandler(object sender, EventArgs e);//声明一个代表:请看文章最后面Note


    public class MyButton              //创建MyBottom
    {
        public event ClickEventHandler ClickEvent;//声明一个事件


        public void Click()                                 //单击MyButton
        {
            if (ClickEvent != null)
            {
                Console.WriteLine("MyButton: 我被单击了");
                ClickEvent(this, null);                          //抛出事件,给所有相应者
            }
        }
    }


    public class MyForm
    {
        public MyButton myButton = new MyButton();


        public MyForm()
        {


            //添加事件到myButton中,当myButton被单击的时候就会调用相应的处理函数


            myButton.ClickEvent += new ClickEventHandler(OnClickEvent);   


         }


       //事件处理函数


       void OnClickEvent(object sender, EventArgs e)
        {
            Console.WriteLine("MyForm: 我知道你被单击了!");
        }
    }


    class Program
    {
        static void Main(string[] args)
        {
            MyForm form = new MyForm();//生成一个MyForm


            form.myButton.Click();//单击MyForm中的鼠标,效果就出来了
        }
    }
}
 


Note:public delegate void ClickEventHandler(object sender, EventArgs e);这是事件委托标准的声明方法,其实在参数里面我们可以不传,也可以是其他类型的。但是最好还是使用上面的声明方法,你可以继承EventArgs,来包装你要传送的其他任何参数。
 

//------------------------------------------------------------------------------------------------

把委托说透(2):深入理解委托

上一篇随笔中我们通过示例逐步引入了委托,并比较了委托和接口。本文将重点剖析委托的实质。

委托在本质上仍然是一个类,我们用delegate关键字声明的所有委托都继承自System.MulticastDelegate。后者又是继承自System.Delegate类,System.Delegate类则继承自System.Object。委托既然是一个类,那么它就可以被定义在任何地方,即可以定义在类的内部,也可以定义在类的外部。

正如很多资料上所说的,委托是一种类型安全的函数回调机制, 它不仅能够调用实例方法,也能调用静态方法,并且具备按顺序执行多个方法的能力。

委托揭秘

把委托说透(1)中可以看到,委托的使用其实是很简单的。尽管如此,其内部实现仍然相当复杂。.NET强大的编译器和CLR掩盖了这种复杂性。

为了解释方便,我们把(1)中的委托代码复制在下面,并做一处小小的改动,将LogToTextFile设置为实例方法。

namespace DelegateSample
{

    public delegate void Log(string message);

    class UserService
    {
        public Log LogDelegate { get; set; }

        public UserService() { }

        public void Register(User user)
        {
            if (user.Name == "Kirin")
            {
                LogDelegate("注册失败,已经包含名为" + user.Name + "的用户");
            }
            else
            {
                LogDelegate("注册成功!");
            }
        }
    }

    class Program
    {
        static void Main(string[] args)
        {
            User user = new User { Name = "Kirin", Password = "123" };
            UserService service = new UserService();
            service.LogDelegate = LogToConsole;
Program p = new Program(); service.LogDelegate += p.LogToTextFile; service.Register(user);
Console.ReadLine(); } static void LogToConsole(string message) { Console.WriteLine(message); } void LogToTextFile(string message) { using (StreamWriter sw = File.AppendText("log.txt")) { sw.WriteLine(message); sw.Flush(); sw.Close(); } } } }

打开Reflector反编译Log委托,可以看到Log类被编译为如下形式:

image

在上图中可以得出如下结论:

委托是一个类

可以很清晰的看出Log—>MulticastDelegate—>Delegate这种继承机制。

尽管委托继承自System.MulticastDelegate类,但我们并不能显示地声明一个继承自System.MulticastDelegate类的委托。委托必须使用delegate关键字声明,编译器会自动为我们生成继承代码。

由于委托继承自System.MulticastDelegate类,自然也继承MulticastDelegate类的字段、属性和方法。这些成员中,最重要的当属三个非公共字段,如下表所示:

字段名称 字段类型 描述
_target System.Object 该字段指明委托所调用的方法所在的实例类型。如果委托调用的为静态方法,该字段为null;如果为实例方法则为该方法所在的对象。
_methodPtr System.IntPtr 标识回调方法的指针。
_invocationList System.Object 在构建委托链时指向一个委托数组,在委托刚刚构建时通常为null。

由上表可以看出,每个委托对象实际上是对方法及其调用时操作的对象的封装。MulticastDelegate类还定义了两个只读公有实例属性:Target和Method,分别对应_target和_methodPtr。Target属性返回一个方法回调时操作的对象引用。如果是静态方法则返回null。Method属性返回一个标识回调方法的System.Reflection.MethodInfo对象。

编译器自动为委托创建了BeginInvoke、EndInvoke和Invoke三个方法

当我们在像调用普通的方法一样调用委托时,如

LogDelegate("注册失败,已经包含名为" + user.Name + "的用户");

这时实际上调用的是编译器自动生成的Invoke方法

LogDelegate.Invoke("注册失败,已经包含名为" + user.Name + "的用户");

使用IL DASM查看UserService的IL代码,可以验证以上结论,如下图所示:

image 在使用委托时,我们也可以显示调用Invoke方法(CLR 2.0)。

image

Invoke方法的参数和返回值与委托是一致的。在调用Invoke方法时,会使用_target和_methodPtr字段。

BeginInvoke和EndInvoke方法用来实现异步调用,本文在此不进行讨论。

委托链

委托链是一个委托的集合,它允许我们调用这个集合中的委托所代表的所有方法(对于有返回值的方法,委托链的返回值为链表中最后一个方法的返回值,本文后面会有详细介绍)。在Delegate类中定义了3个静态方法来帮助我们操作委托链。

public static Delegate Combine(params Delegate[] delegates);
public static Delegate Combine(Delegate a, Delegate b);
public static Delegate Remove(Delegate source, Delegate value);

要理解委托链,我们首先基于前面的例子,重新声明两个委托:logDel1和logDel2。

Log logDel1 = LogToConsole;
Program p = new Program();
Log logDel2 = p.LogToTextFile;

这两个委托的_target、_methodPtr和_invocationList值分别如下图所示:

image

构造委托链

然后,我们使用Combin方法来构造一个委托链:

Log logChain = null;
logChain = (Log)Delegate.Combine(logChain, logDel1);

由于logChain初始为null,在使用Combin方法构造委托链时,将返回另外一个参数logDel1,再将logDel1的引用赋给logChain。这时logChain将指向logDel1所指向的对象。

image

接下来我们将logDel2也添加到logChain中来:

logChain = (Log)Delegate.Combine(logChain, logDel2);

此时,由于logChain已经不再是null,将重新构建一个新的委托对象。该委托对象的_target和_methodPtr字段与logDel2(第二个参数)相同,_invocationList字段将指向一个委托数组。该委托数组中包含两个元素,第一个元素(索引为0)指向封装了LogToConsole方法的委托(即logDel1指向的委托);第二个元素(索引为1)指向封装了LogToTextFile方法的委托(即logDel2指向的委托)。最后,将这个新创建的委托对象的引用赋给logChain。

image

若再将一个新的委托logDel3添加到委托链中,则仍然会构建一个新的委托对象,并将logDel3的引用添加到该委托对象_invocationList的末尾(此时链表共有3个元素)。然后,再将该委托对象的引用赋给logChain。而logChain之前指向的委托对象则等待垃圾回收

至此,委托链构造完毕,我们来看看如何执行委托链表中的委托。由于logChain仍然指向一个委托对象,因此执行委托链表的语法与执行委托是一样的:

logChain("执行委托链");

与普通的委托(如logDel1)所不同的是,logChain的_invocationList字段不为null。这时将首先遍历执行_invocationList中的所有委托。所执行的方法的顺序与添加的顺序一致,依次为LogToConsole、LogToTextFile。

委托Log的Invoke方法的实现用伪代码表示如下:

public void Invoke(string message)
{ 
    Delegate[] delegateSet = _InvocationList as Delegate[];
    if (delegateSet != null) 
    {
        // 如果委托数组不为空,则依次执行该委托数组中的委托
        foreach (Feedback d in delegateSet)
            d(value);
    } 
    else 
    {
        // 如果委托数组为空,则该委托不代表一个委托链
        // 按照正常方式执行该委托
        _methodPtr.Invoke(_target, value);
    }
}

 包含返回值的委托的Invoke实现如下,假设返回值为string:

public string Invoke(string message)
{
    string result = null;
    Delegate[] delegateSet = _InvocationList as Delegate[];
    if (delegateSet != null)
    {
        // 如果委托数组不为空,则依次执行该委托数组中的委托
        foreach (Feedback d in delegateSet)
            result = d(value);
    }
    else
    {
        // 如果委托数组为空,则该委托不代表一个委托链
        // 按照正常方式执行该委托
        result = _methodPtr.Invoke(_target, value);
    }
    return result;
}

可以看到在委托链中,返回值为链表中最后一个委托的返回值

那么如果对两个委托链调用Combine方法呢?

Log logChain = null;
Log logChain1 = null;
Log logChain2 = null;
logChain1 = (Log)Delegate.Combine(logChain1, logDel1);
logChain1 = (Log)Delegate.Combine(logChain1, logDel2);
logChain2 = (Log)Delegate.Combine(logChain2, logDel3;
logChain2 = (Log)Delegate.Combine(logChain2, logDel4;
logChain = (Log)Delegate.Combine(logChain1, logChain2);

最终的结果是,logChain的_target和_methodPtr均与logDel4相同(确切地说,两个委托对象的_methodPtr字段并不相同,但Method属性是相同的),而_invocationList中委托的顺序依次为logDel1、logDel2、logDel3、logDel4。

综上所述,可以对Delegate.Combine(Delegate A, Delegate B)方法做如下总结:

1. 如果A和B均为null,则返回null。

2. 如果A或B一个为null而另一个不为null,则返回不为null的委托。

3. 如果A和B均不为null,返回一个新的委托,该委托

    (1)_target字段与B的_target字段的值相同

    (2)Method属性与B的Method属性的值相同

    (3)_invocationList字段为一个委托数组,该数组中委托的顺序为:A中_invacationList所指向的委托数组 + B中_invacationList所指向的委托数组。

移除委托链

Combine方法用来向委托链中添加一个委托,而Remove方法用来从委托链中移除一个委托。

logChain = (Log)Delegate.Remove(logChain, new Log(LogToConsole));

当调用Remove时,会遍历(倒序)第一个参数(logChain)中的中的委托列表(_invocationList字段), 找到与第二个参数(new Log(LogToConsole))的_target和_methodPtr字段相匹配的委托,并将其从委托列表中移除。返回值需分以下几种情况,为了描述方便,我们将logChain记为A,将new Log(LogToConsole)记为B。

1. 如果A为null,返回null。

2. 如果B为null,返回A。

3. 如果A的_invocationList为null,即不包含委托链,那么如果A本身与B匹配,则返回null,否则返回A。

4. 如果A的_invocationList中不包含与B匹配的委托,则返回A。

5. 如果A的_invocationList中包含与B匹配的委托,则从链表中移除B,然后

    (1)如果A的链表中只剩下一个委托,则返回该委托。

    (2)如果A的链表中还剩下多个委托,将重新构建一个新的委托R(R的_invocationList字段为A的_invocationList移除了B之后的链表),并返回R。

注意,Remove方法只移除源委托的_invocationList列表中第一个匹配的委托,要想移除所有匹配的委托,可以使用RemoveAll方法

有了委托链,在(1)中提出的第二个疑问就迎刃而解了。当用户希望使用多种日志记录方式的时候,使用委托链可以轻松地添加和删除某种日志记录方式,从而避免了人为地维护一个列表。

总结

本文首先介绍了委托的实质,委托是一个类,它继承自System.MulticastDelegate,而MulticastDelegate又继承自System.Delegate。然后重点剖析了委托链,讨论了如何创建和移除委托链。

在接下来的随笔中,我们将对.NET中委托的一个典型应用——事件,进行全面深入的介绍。


 
  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值