C#再识委托

从C#1到C#3逐步认识委托,因为C#4与C#5对委托改动并不大,故不做说明。

很久没看.NET了,一直在搞HybridAPP,都忘得差很少了,这也是本身从书中摘下笔迹,供往后翻阅。

C# 1

1.什么是委托

委托是一种定义方法签名的类型。当实例化委托时,您能够将其实例与任何具备兼容签名的方法相关联。 您能够经过委托实例调用方法。(MSDN)算法

  • 委托相似于 C++函数指针,但它们是类型安全的
  • 委托容许将方法做为参数进行传递
  • 委托可用于定义回调方法
  • 委托能够连接在一块儿
  • 方法没必要与委托签名彻底匹配。(协变与逆变)
  • C# 2.0 版引入了匿名方法的概念,此类方法容许将代码块做为参数传递,以代替单独定义的方法。 C#3.0引入了Lambda表达式,利用它们能够更简练地编写内联代码块。匿名方法和 Lambda表达式(在某些上下文中)均可编译为委托类型

2.如何使用委托

1. 定义委托类型

定义一个委托类型,实际上只有一个定义委托的关键字、一个类型名称、一个返回值和参数列表。以下所示:

在这里值得注意的是,Processor实际上是一个类,只不过看起来像一个方法的签名,但它不是一个方法,你能够认为它是一个特殊的类,但你必定不要说是一个特殊的方法。还有,由于委托是一个类,固然能够有它的可访问性修饰符了。编程

2. 定义一个兼容委托类型签名的回调方法

如今,已经知道了委托类型的签名,就能够定义一个兼容于委托类型签名的回调方法了。

第4种状况比较特殊,这在C#1.0时代是不容许的,但在C#2.0后是容许的。将一个方法绑定到一个委托时,C#和CLR都容许引用类型的协变性和逆变性。数组

协变性是指方法能返回从委托的返回类型派生的一个类型。逆变性是指方法获取的参数能够是委托的参数类型的基类。

在委托类型签名中参数是string类型,根据逆变性,第4个方法的参数完成符合要求。安全

3.实例化委托类型

在前面,已经有了一个委托类型和一个正确签名的方法,接着就能够建立委托的一个实例了,经过委托实例来真正执行这个先前定义的回调方法。在C#中如何建立委托实例,取决于先前定义的方法是实例方法仍是静态方法。闭包

假定在StaticMethods类中的定义一个静态方法PrintString,在InstanceMethods类中定义一个实例方法PrintString。下面就演示了如何如何建立委托类型Processor实例的两个例子:app

Processor proc1,proc2;
    //静态方法,类直接调用
    proc1 = new Processor(StaticMethods.PrintString)                   
    InstanceMethods instance = new InstanceMethods();
    //实例方法,经过类的实例调用
    proc2 = new Processor (instance.PrintString)

若是须要真正执行的方法是静态方法,指定类型名称就能够了;若是是实例方法,就须要先建立该方法的类型的实例。这个和平时调用方法是如出一辙的。当委托实例被调用时,就会调用须要真正执行的方法。异步

值得注意的是,C#2.0后,可使用一种简洁语法,它仅有方法说明符构成,以下所示代码。使用快捷语法是由于在方法名称和其相应的委托类型之间有隐式转换。ide

Processor proc1,proc2;
    proc1 = StaticMethods.PrintString;    //快捷语法
    InstanceMethods instance = new InstanceMethods();
    proc2 = instance.PrintString           //快捷语法

4.调用委托

调用委托实例指的是调用委托实例的一个方法来执行先前定义的回调方法,不过这显得很是简单。以下所示:函数

Processor proc1,proc2;
   proc1 = new Processor(StaticMethods.PrintString) //静态方法,类直接调用
   InstanceMethods instance = new InstanceMethods();
   proc2 = new Processor (instance.PrintString)            //实例方法,经过类的实例调用
    proc1("PrintString方法执行了");
   //proc1.Invoke("PrintString方法执行了");       
   //proc1("PrintString方法执行了"); 是对proc1.Invoke("PrintString方法执行了"); 的简化调用
    proc2.Invoke("PrintString方法执行了");

值得注意的是,其中的调用委托实例的一个方法指的是Invoke方法,这个方法以委托类型的形式出现,而且具备与委托类型的声明中所指定的相同参数列表和返回类型。因此,在咱们的例子中,有一个像下面这样的方法:测试

void Invoke(string input);

调用Invoke执行先前定义的回调方法,能够在这里向这个执行先前定义的回调方法指定相应参数。能够用下面这一张图来解释:

5.完整委托示例

namespace Program {
   //定义委托
   delegate void Processor(string input);
 
   class InstanceMethods
   {
       //定义与委托签名相同的"实例方法"
       public void PrintString(string message)
       {
           Console.WriteLine(message);
       }
   }
 
   class StaticMethods
   {
       //定义与委托签名相同的"静态方法"
       public static void PrintString(string message)
       {
           Console. WriteLine(message);
       }
   }
 
   class Program
   {
       static void Main(string[] args)
       {

           Processor proc1,proc2;
           proc1 = new Processor(StaticMethods. PrintString);   //静态方法,类直接调用
           InstanceMethods instance = new InstanceMethods();
           proc2 = new Processor (instance. PrintString);       //实例方法,经过类的实例调用
           proc1("PrintString方法执行了");
           //proc1.Invoke("PrintString方法执行了"); //proc1("PrintString方法执行了")是对proc1.Invoke("PrintString方法执行了")的简化调用
           proc2.Invoke("PrintString方法执行了");
           Console.ReadKey();
       }
   }
}

4.委托的用途

实际上,委托在某种程度上提供了间接的方法。换言之,不须要直接指定一个要执行的行为,而是将这个行为用某种方式“包含”在一个对象中。这个对象能够像其余任何对象那样使用。在对象中,能够执行封装的操做。能够选择将委托类型看作只定义了一个方法的接口,将委托的实例看作实现了这个接口的一个对象。

5.委托揭秘

先看下面一段代码,经过这段代码,逐步揭秘委托内部。

namespace Test
{
 // 1.声明委托类型
 internal delegate void Feedback(Int32 value);
 internal class Program
 {
  private static void Main(string[] args)
  {
      StaticDelegateDemo();
      InstanceDelegateDemo();
      ChainDelegateDemo1(new Program());
      ChainDelegateDemo2(new Program());
  }
  private static void StaticDelegateDemo()
  {
      Console.WriteLine("----- Static Delegate Demo -----");
      Counter(1, 3, null);
      // 3.建立委托实例
      Counter(1, 3, new Feedback(Program.FeedbackToConsole));
      Counter(1, 3, new Feedback(FeedbackToMsgBox));
      Console.WriteLine();
  }
  private static void InstanceDelegateDemo()
  {
      Console.WriteLine("----- Instance Delegate Demo -----");
      Program di = new Program();
      // 3.建立委托实例
      Counter(1, 3, new Feedback(di.FeedbackToFile));
      Console.WriteLine();
  }
  private static void ChainDelegateDemo1(Program di)
  {
      Console.WriteLine("----- Chain Delegate Demo 1 -----");
      // 3.建立委托实例
      Feedback fb1 = new Feedback(FeedbackToConsole);
      Feedback fb2 = new Feedback(FeedbackToMsgBox);
      Feedback fb3 = new Feedback(di.FeedbackToFile);
 
      Feedback fbChain = null;
      fbChain = (Feedback)Delegate.Combine(fbChain, fb1);
      fbChain = (Feedback)Delegate.Combine(fbChain, fb2);
      fbChain = (Feedback)Delegate.Combine(fbChain, fb3);
      Counter(1, 2, fbChain);
 
      Console.WriteLine();
      fbChain = (Feedback)Delegate.Remove(fbChain, new Feedback(FeedbackToMsgBox));
      Counter(1, 2, fbChain);
  }
  private static void ChainDelegateDemo2(Program di)
  {
      Console.WriteLine("----- Chain Delegate Demo 2 -----");
      Feedback fb1 = new Feedback(FeedbackToConsole);
      Feedback fb2 = new Feedback(FeedbackToMsgBox);
      Feedback fb3 = new Feedback(di.FeedbackToFile);
 
      Feedback fbChain = null;
      fbChain += fb1;
      fbChain += fb2;
      fbChain += fb3;
      Counter(1, 2, fbChain);
 
      Console.WriteLine();
      fbChain -= new Feedback(FeedbackToMsgBox);
      Counter(1, 2, fbChain);
  }
 
  private static void Counter(Int32 from, Int32 to, Feedback fb)
  {
      for (Int32 val = from; val <= to; val++)
      {
          // 若是指定了任何回调,就能够调用它
          if (fb != null)
              // 4.调用委托
              fb(val);
      }
  }
 
  // 2.声明签名相同的方法
  private static void FeedbackToConsole(Int32 value)
  {
      Console.WriteLine("Item=" + value);
  }
 
  // 2.声明签名相同的方法
  private static void FeedbackToMsgBox(Int32 value)
  {
      Console.WriteLine("Item=" + value);
  }
 
  // 2.声明签名相同的方法
  private void FeedbackToFile(Int32 value)
  {
      StreamWriter sw = new StreamWriter("Status", true);
      sw.WriteLine("Item=" + value);
      sw.Close();
  }
 }
}

从表面看起来,使用一个委托彷佛很容易:先用C#的delegate关键字声明一个委托类型,再定义一个要执行的签名一致的方法,而后用熟悉的new操做符构造委托实例,最后用熟悉的方法调用语法来调用先前定义的方法。

事实上,编译器在幕后作了大量的工做来隐藏了没必要要的复杂性。首先,让咱们从新认识一下下面的委托类型定义代码:

internal delegate void Feedback(Int32 value);

当编译器看到这行代码时,实际上会生成像下面一个完整的类:

internal class Feedback: System.MulticastDelegate {
    // 构造器
    public Feedback(object @object, IntPtr method);
    // 这个方法和源代码指定的原型同样
    public virtual void Invoke(Int32 value);
    // 如下方法实现了对回调方法的异步回调
    public virtual IAsyncResult BeginInvoke(Int32 value, AsyncCallback callback, object @object);
    // 如下方法获取了回调方法的返回值
    public virtual void EndInvoke(IAsyncResult result);
 }

编译器定义的类有4个方法:一个构造器、Invoke、BeginInvoke和EndInvoke。

如今重点解释构造器和Invoke,BeginInvoke和EndInvoke看留到后面讲解。

事实上,可用.Net Reflector查看生成的程序集,验证编译器是否真的会自动生成相关代码,以下图所示:

在这个例子中,编译器定义了一个名为Feedback的类,该类派生自FCL定义的System.MulticastDelegate类型(全部委托类型都派生自System.MulticastDelegate类型)。

提示:System.MulticastDelegate类派生自System.Delegate,后则又派生自System.Object。之因此有两个委托类,是有历史缘由的。

从图中可知Feedback的可访问性是private,由于委托在源代码中声明为internal类。若是源代码改为使用public可见性,编译器生成的类也会是public类。要注意,委托类便可嵌套在一个类型中定义,也能够在全局范围中定义。简单地说,因为委托是类,因此凡是可以定义类的地方,都能定义委托。
因为全部委托类型都派生自MulticastDelegate,因此它们继承了MulticastDelegate的字段、属性和方法。在这些成员中,有三个非公共字段是最重要的。

字段 类型 说明
_target System.Object 当委托对象包装一个静态方法时,这个字段为null。当委托对象包装一个实例方法时,这个字段引用的是回调方法要操做的对象。换言之,这个字段指出了要传给实例方法的隐式参数this的值
_methodPtr System.IntPtr 一个内部的整数值,CLR用它来标识要回调的方法
_invocationList System.Object 该字段一般为null。构造一个委托链时,它能够引用一个委托数组。

注意,全部委托都有一个构造器,它要获取两个参数:一个是对象引用,另外一个是引用回调方法的一个整数。然而,若是仔细看下签名的源代码,会发现传递的是Program.FeedbackToConsole和p.FeedbackToFile这样的值,还少一个intPtr类型的参数,这彷佛不可能经过编译吧?

然而,C#编译器知道要构造的是委托,因此会分析源代码来肯定引用的是哪一个对象和方法。对象引用被传给构造器的object参数,标识了方法的一个特殊IntPtr值(从MethodDef或MemberRef元数据token得到)被传给构造器的method参数。对于静态方法,会为object参数传递null值。在构造器内部,这两个实参分别保存在_target和_methodPtr私有字段中。除此以外,构造器还将_invocationList字段设为null,对这个字段的讨论推迟到后面。

因此,每一个委托对象实际都是一个包装器,其中包装了一个方法和调用该方法时要操做的一个对象。例如,在执行如下两行代码以后:

Feedback fbStatic = new Feedback(Program.FeedbackToConsole);
  Feedback fbInstance = new Feedback(new Program.FeedbackToFile());

fbStatic和fbInstance变量将引用两个独立的,初始化好的Feedback委托对象,以下图所示。

Delegate类定义了两个只读的公共实例属性:Target和Method。给定一个委托对象的引用,可查询这些属性。Target属性返回一个引用,它指向回调方法要操做的对象。简单的说,Target属性返回保存在私有字段_target中的值。若是委托对象包装的是一个静态方法,Target将返回null。Method属性返回一个System.Reflection.MethodInfo对象的引用,该对象标识了回调方法。简单地说,Method属性有一个内部转换机制,能将私有字段_methodPtr中的值转换为一个MethodInfo对象并返回它。

可经过多种方式利用这些属性。例如,可检查委托对象引用是否是一个特定类型中定义的实例方法:

Boolean DelegateRefersToInstanceMethodOfType(MulticastDelegate d ,Type type) {
    return ((d.Target != null) && d.Target.GetType() == type);
}

还能够写代码检查回调方法是否有一个特定的名称(好比FeedbackToMsgBox):

Boolean DelegateRefersToInstanceMethodOfName(MulticastDelegate d ,String methodName) {
    return (d.Method.Name == methodName);
}

知道了委托对象如何构造并了解其内部结构以后,在来看看回调方法是如何调用的。为方便讨论,下面重复了Counter方法的定义:

private static void Counter(Int32 from, Int32 to, Feedback fb) {
    for (Int32 val = from; val <= to; val++) {
    // 若是指定了任何回调,就调用它们
        if(fb != null ){
            fb(val); //调用委托
        }
    }
}

注意注释下方的那一行代码。if语句首先检查fb是否为null。若是不为null,下一行代码调用回调方法。

这段代码看上去是在调用一个名为fb的函数,并向它传递一个参数(val)。但事实上,这里没有名为fb的函数。再次提醒你注意,由于编译器知道fb是引用了一个委托对象的变量,因此会生成代码调用该委托对象的Invoke方法。也就是说,编译器看到如下代码时:

fb(val);

将生成如下代码,好像源代码原本就是这么写的:

fb.Invoke(val);

其实,彻底能够修改Counter方法来显式调用Invoke方法,以下所示:

private static void Counter(Int32 from, Int32 to, Feedback fb) {
    for (Int32 val = from; val <= to; val++) {
        // 若是指定了任何回调,就调用它们
        if(fb != null ){
            fb.Invoke(val);
        }
    }
}

前面说过,编译器是在定义Feedback类时定义Invoke的。因此Invoke被调用时,它使用私有字段_target和_methodPtr在指定对象上调用包装好的回调方法。注意,Invoke方法的签名与委托的签名是匹配的。因为Feedback委托要获取一个Int32参数,并返回void,因此编译器生成的Invoke方法也要获取一个Int32参数,并返回void。

6.委托链

1. 委托链初印象

委托实例实际有一个操做列表与之关联。这称为委托实例的调用列表。System.Delegate类型的静态方法Combine和Remove负责建立新的委托实例。其中,Combine负责将两个委托实例的调用列表链接在一块儿,而Remove负责从一个委托实例中删除另外一个的委托列表。

委托是不易变的。建立一个委托实例后,有关它的一切就不能改变。这样一来,就能够安全地传递委托实例,并把它们与其余委托实例合并,同时没必要担忧一致性、线程安全性或者是否其余人视图更改它的操做。这一点,委托实例和string是同样的。

但不多在C#中看到对Delegate.Combine的显式调用,通常都是使用+和+=操做符。
图中展现了转换过程,其中x和y都是兼容委托类型的变量。全部转换都是由C#编译器完成的。

能够看出,这是一个至关简单的转换过程,但它使得代码变得整洁多了。

除了能合并委托实例,还可使用Delegate.Rmove方法从一个实例中删除另外一个实例的调用列表。对应的C#简化操做为-和-=。Delegate.Remove(source,value)将建立一个新的委托实例,其调用列表来自source,value中的列表则被删除。若是结果有一个空的调用列表,就返回null。

一个委托实例调用时,它的全部操做都顺序执行。若是委托的签名具备一个非void的返回值类型,则Invoke的返回值是最后一个操做的返回值。

若是调用列表中的任何操做抛出一个异常,都会阻止执行后续的操做。

2. 深刻委托链

委托自己就已经至关有用了,再加上对委托链的支持,它的用处就更大了!委托链是由委托对象构成的一个集合。利用委托链,可调用集合中的委托所表明的所有方法。为了理解这一点,请参考上面示例代码中的ChainDelegateDemo1方法。在这个方法中,在Console.WriteLine语句以后,构造了三个委托对象并让变量fb一、fb2和fb3引用每个对象,以下图所示:

随后,我定义了指向Feedback委托对象的引用变量fbChain,并打算让它引用一个委托链或者一个委托对象集合,这些对象包装了能够回调的方法。fbChain被初始化为null,代表目前没有回调的方法。使用Delegate类的公共静态方法Combine,能够将一个委托添加到链中:

Feedback fbChain = null;
fbChain = (Feedback)Delegate.Combine(fbChain, fb1);

执行以上代码时,Combine方法会视图合并null和fb1。在内部,Combine直接返回fb1中的值,因此fbChain变量如今引用的就是fb1变量引用的那个委托对象。以下图所示:

为了在链中添加第二个委托,再次调用了Combine方法:

fbChain = (Feedback)Delegate.Combine(fbChain, fb2);

在内部,Combine方法发现fbChain已经引用了一个委托对象,因此Combine会构造一个新的委托对象。这个新的委托对象对它的私有字段_target和_methodPtr进行初始化,具体值对目前讨论的来讲并不重要。重要的是,_invocationList字段被初始化为引用一个委托对象数组。这个数组的第一个元素(索引为0)被初始化为引用包装了FeedbackToConsole方法的委托。数组的第二个元素(索引为1)被初始化为引用包装了FeedbackToMsgBox方法的委托。最后,fnChain被设为引用新建的委托对象,以下图所示:

为了在链中添加第三个委托,再次调用了Combine方法:

fbChain = (Feedback)Delegate.Combine(fbChain, fb3);

一样的,Combine方法会发现fbChain已经引用了一个委托对象,因而又Combine会构造一个新的委托对象。这个新的委托对象对它的私有字段_target和_methodPtr进行初始化,具体值对目前讨论的来讲并不重要。重要的是,_invocationList字段被初始化为引用一个委托对象数组。这个数组的第一个元素(索引为0)被初始化为引用包装了FeedbackToConsole方法的委托,数组的第二个元素(索引为1)被初始化为引用包装了FeedbackToMsgBox方法的委托,数组的第三个元素(索引为2)被初始化为引用包装了FeedbackToFile方法的委托。最后,fnChain被设为引用新建的委托对象。注意以前新建的委托以及_invocationList字段引用的数组已经被垃圾回收器回收了。以下图所示:

在ChainDelegateDemo1方法中,用于设置委托链的全部代码已经执行完毕,我将fnChain变量交给Counte方法:

Counter(1, 2, fbChain);

Counter方法内部的代码会在Feedback委托对象上隐式调用Invoke方法,这在前面已经讲过了。在fnChain引用的委托上调用Invoke时,该委托发现私有字段_invocationList不为null,因此会执行一个循环来遍历数组中的全部元素,并依次调用每一个委托包装的方法。在本例中,首先调用的是FeedbackToConsole,而后是FeedbackToMsgBox,最后是FeedbackToFile。

以伪代码的方式,Feedback的Invoke的基本上是向下面这样实现的:

public void Invoke(Int32 value) {
    Delegate[] delegateSet = _invocationList as Delegate[];
        if (delegateSet != null) {
            foreach(var d in delegateSet)
                d(value);// 调用委托
            }else{//不然,不是委托链
            _methodPtr.Invoke(value);
        }    
}

注意,还可使用Delegate公共静态方法Remove从委托链中删除委托,以下所示。

fbChain = (Feedback)Delegate.Remove(fbChain, new Feedback(FeedbackToMsgBox));

Remove方法被调用时,它扫描的第一个实参(本例是fbChain)所引用的那个委托对象内部维护的委托数组(从末尾向索引0扫描)。Remove查找的是其_target和_methodPtr字段与第二个实参(本例是新建的Feedback委托)中的字段匹配的委托。若是找匹配的委托,而且(在删除以后)数组中只剩下一个数据项,就返回那个数据项。若是找到匹配的委托,而且数组中还剩余多个数据项,就新建一个委托对象——其中建立并初始化_invocationList数组将引用原始数组中的全部数据项(删除的数据项除外),并返回对这个新建委托对象的引用。若是从链中删除了仅有的一个元素,Remove会返回null。注意,每次Remove方法调用只能从链中删除一个委托,它不会删除有匹配的_target和_methodPtr字段的全部委托。

前面展现的例子中,委托返回值都是void。可是,彻底能够向下面这样定义Feedback委托:

public delegate Int32 Feedback (Int32 value);

若是这样定义,那么该委托的Invoke方法就应该向下面这样(伪代码形式):

public Int32 Invoke(Int32 value) {
    Int32 result;
    Delegate[] delegateSet = _invocationList as Delegate[];
    if (delegateSet != null) {
        foreach(var d in delegateSet)
            result = d(value);// 调用委托
        }else{//不然,不是委托链
        result = _methodPtr.Invoke(_target,value);
        }
  return result;    
}

1.C#对委托链的支持

为方便C#开发人员,C#编译器自动为委托类型的实例重载了+=和-=操做符。这些操做符分别调用了Delegate.Combine和Delegate.Remove。使用这些操做符,可简化委托链的构造。

好比下面代码:

Feedback fbChain = null;
fbChain += fb1;
fbChain += fb2;
fbChain += fb3;

2.取得对委托链调用更多控制

如今咱们已经理解了如何建立一个委托对象链,以及如何调用链中的全部对象。链中的全部项都会被调用,由于委托类型的Invoke方法包含了对数组中的全部项进行变量的代码。由于Invoke方法中的算法就是遍历,过于简单,显然,这有很大的局限性,除了最后一个返回值,其它全部回调方法的返回值都会被丢弃。还有吗若是被调用的委托中有一个抛出一个或阻塞至关长的时间,咱们又无能为力。显然,这个算法还不够健壮。
因为这个算法的局限,因此MulticastDelegate类提供了一个GetInvocationList,用于显式调用链中的每个委托,同时又能够自定义符合本身须要的任何算法:

public abstract class MulticastDelegate :Delegate {
  // 建立一个委托数组,其中每一个元素都引用链中的一个委托
  public sealed override Delegate[] GetInvocationList();
}

GetInvocationList方法操做一个从MulticastDelegate派生的对象,返回一个有Delegate组成的数组,其中每个引用都指向链中的一个委托对象。
下面是代码演示:

public static class GetInvocationList
  {
      // 定义一个 Light 组件
      private sealed class Light
      {
          // 该方法返回 light 的状态
          public String SwitchPosition()
          {
              return "The light is off";
          }
      }
 
      // 定义一个 Fan 组件
      private sealed class Fan
      {
          // 该方法返回 fan 的状态
          public String Speed()
          {
              throw new InvalidOperationException("The fan broke due to overheating");
          }
      }
 
      // 定义一个 Speaker 组件
      private sealed class Speaker
      {
          // 该方法返回 speaker 的状态
          public String Volume()
          {
              return "The volume is loud";
          }
      }
 
      // 定义委托
      private delegate String GetStatus();
 
      public static void Go()
      {
          // 声明一个为null的委托
          GetStatus getStatus = null;
 
          // 构造三个组件,将它们的状态方法添加到委托链中
          getStatus += new GetStatus(new Light().SwitchPosition);
          getStatus += new GetStatus(new Fan().Speed);
          getStatus += new GetStatus(new Speaker().Volume);
 
          // 输出该委托链中,每一个组件的状态
          Console.WriteLine(GetComponentStatusReport(getStatus));
      }
 
      // 该方法用户查询几个组件的状态
      private static String GetComponentStatusReport(GetStatus status)
      {
 
          // 若是委托链为null,则不进行任何操做
          if (status == null) return null;
 
          // 用StringBuilder来记录建立的状态报告
          StringBuilder report = new StringBuilder();
 
          // 获取委托链,其中的每一个数据项都是一个委托
          Delegate[] arrayOfDelegates = status.GetInvocationList();
 
          // 遍历数组中的每个委托
          foreach (GetStatus getStatus in arrayOfDelegates)
          {
 
              try
              {
                  // 获取一个组件的状态报告,将它添加到StringBuilder中
                  report.AppendFormat("{0}{1}{1}", getStatus(), Environment.NewLine);
              }
              catch (InvalidOperationException e)
              {
                  // 在状态报告中生成一条错误记录
                  Object component = getStatus.Target;
                  report.AppendFormat(
                     "Failed to get status from {1}{2}{0} Error: {3}{0}{0}",
                     Environment.NewLine,
                     ((component == null) ? "" : component.GetType() + "."),
                     getStatus.Method.Name, e.Message);
              }
          }
 
          // 返回遍历后的报告
          return report.ToString();
      }
  }

执行结果为:

The light is off

Failed to get status from ConsoleTest.GetInvocationList+Fan.Speed
Error: The fan broke due to overheating

The volume is loud

7.小结

  • 委托封装了包含特殊返回类型和一组参数的行为,相似包含单一方法的接口。
  • 委托类型声明中所描述的类型签名决定了哪一个方法可用于建立委托实例,同时决定了调用的签名。
  • 为了建立委托实例,须要一个方法以及(对于实例方法来讲)调用方法的目标。
  • 委托实例是不易变的。
  • 每一个委托实例都包含一个调用列表——一个操做列表。
  • 委托实例能够合并到一块儿,也能够从一个委托实例中删除一个。
  • 事件不是委托实例——只是成对的add/remove方法。

2.C# 2

2.1 方法组转换

在C#1中,若是要建立一个委托实例,就必须同时指定委托类型和要采起的操做。以下所示:

Processor proc1,proc2;
   proc1 = new Processor(StaticMethods. PrintString)   //静态方法,类直接调用
   InstanceMethods instance = new InstanceMethods();
   proc2 = new Processor (instance. PrintString)        //方法,经过类的实例调用

为了简化编程,C#2支持从方法组到一个兼容委托类型的隐式转换。所谓"方法组"(method group),其实就是一个方法名。

如今咱们可使用以下代码,效果和上面的代码如出一辙。

Processor proc1,proc2;
   proc1 = StaticMethods.PrintString   //静态方法,类直接调用
   InstanceMethods instance = new InstanceMethods();
   proc2 = instance.PrintString        //方法,经过类的实例调用

2.2 协变性和逆变性

在前面已经说过C#2.0后,将一个方法绑定到一个委托时,C#和CLR都容许引用类型的协变性和逆变性。

协变性是指方法能返回从委托的返回类型派生的一个类型。逆变性是指方法获取的参数能够是委托的参数类型的基类。

2.3 使用匿名方法的内联委托

1.使用匿名方法

Action

在C#1中,可能一些参数不一样,须要建立一个或多个很小的方法,而这些细粒度的方法管理起来又十分不便。在C#2中引入的匿名方法很好的解决了这个问题。

.NET2.0引入了一个泛型委托类型Action ,它的签名很是简单:

public delegate void Action<T>

Action 就是对T的一个实例执行某些操做。例如:

Action<string> printAction1 = delegate(string text){
    char[] chars = text.ToCharArray();
    Array.Reverse(chars);
    Console.WriteLine(new string(chars));
};
Action<int> printAction2 = delegate(int s)
{
    Console.WriteLine(Math.Sqrt(s));
};
private Action printAction3 = delegate
{
    Console.WriteLine("没有参数");
};
printAction1("asd");
printAction2(4);
printAction3();

上述代码展现了匿名方法的几个不一样特性。首先是匿名方法的语法:先是delegate关键字,再是参数(若是有的话),随后是一个代码块,其中包含了对委托实例的操做行定义的代码。值得注意的是,逆变性不适用于匿名方法:必须指定和委托类型彻底匹配的参数类型。

说到实现,咱们在IL中为源代码中的每一个匿名方法都建立了一个方法:编译器将在已知类(匿名方法所在的类)的内部生成一个方法,并使用建立委托实例时的行为,就像它是一个普通的方法同样。以下图所示:

2.匿名方法的返回值

Predicate

Action 委托的返回类型是void,因此没必要从匿名方法返回任何东西。但在须要返回值的状况下怎么办呢,这就要使用.NET2.0中的Predicate 委托类型。下面是它的签名:

public delegate bool Predicate<T>(T obj)

从签名中能够看到,这个委托返回的是bool类型,如今演示一下,建立一个Predicate 的一个实例,其返回值指出传入的实参是奇数仍是偶数。

Predicate<int> isEven = delegate (int x) { return x % 2 == 0;};
Console.WriteLine(isEven(1));
Console.WriteLine(isEven(4));

注意:从匿名方法返回一个值时,它始终从匿名函数中返回,而不是从委托实例的方法中返回。

Comparison

Comparison 委托,表示比较同一类型的两个对象的方法。下面是它的签名:
public delegate int Comparison (T x,T y)

从签名中能够看到,这个委托返回的是int 类型。Comparison 是在.NET2.0中常见的委托类型,可用它来对集合排序,它是IComparer 接口的委托版。一般,一种状况下只须要一个特定的排列顺序,因此采起内联的方式指定彻底是合理的,不须要在其他类的内部添加一个独立的方法来指定该顺序。此委托由 Array 类的 Sort (T[], Comparison ) 方法重载和 List 类的 Sort(Comparison )方法重载使用,用于对数组或列表中的元素进行排序。

internal class Program
      {
          private static void Main(string[] args)
          {
              Program p = new Program();

              SortAndShowFiles("Sorted by name:",delegate (FileInfo f1,FileInfo f2)
              {
                  return f1.Name.CompareTo(f2.Name);
              });

              SortAndShowFiles("Sorted by lenth:", delegate(FileInfo f1, FileInfo f2)
              {
                  return f1.Length.CompareTo(f2.Length);
              });

              Console.Read();
          }


          static void SortAndShowFiles(string title, Comparison<FileInfo> sortOrder)
          {
              FileInfo[] files = new DirectoryInfo(@"C:\").GetFiles();
              Array.Sort(files,sortOrder);
              foreach (var fileInfo in files)
              {
                  Console.WriteLine("{0} ({1} byte)",fileInfo.Name,fileInfo.Length);
              }
          }
      }

3.忽略委托参数

在少数状况下,你实现的委托可能不依赖于它的参数值。你可能想写一个事件处理程序,它的行为只适用于一个事件,而不依赖事件的实际参数。以下面的例子中,能够彻底省略参数列表,只须要使用一个delegate关键字,后跟做为方法的操做使用的代码块.

Button button = new Button();
  button.Test = "Click me";
  button.Click += delegate{ Console.WriteLine("LogClick");};
  button.KeyParess+= delegate{ Console.WriteLine("LogKey");};

通常状况下,咱们必须像下面这样写:

button.Click += delegate (object sender, EventArgs e){.....};

那样会无谓地浪费大量空间——由于咱们根本不须要参数的值,因此编译器如今容许彻底省略参数。

4.在匿名方法中捕捉变量

1.定义闭包和不一样的变量类型

闭包的基本概念是:一个函数除了能经过提供给它的参数与环境互动以外,还能同环境进行更大程度的互动,这个定义过于抽象,为了真正理解它的应用状况,还须要理解另外两个术语:
外部变量:指其做用域包括一个函数方法的局部变量或参数(ref和out参数除外)。在可使用匿名方法的地方,this引用也被认为是一个外部变量。
被捕捉的外部变量:一般简称为被捕获的变量,它在匿名方法内部使用的外部变量。

从新看一下"闭包"的定义,其中所说的"函数"是指匿名方法,而与之互动的"环境"是指由这个匿名方法捕捉到的变量集合。

它主要强调的是,匿名方法能使用在声明该匿名方法的方法内部定义的局部变量。

void EnclosingMethod()
{
    int outervariable = 5;   //外部变量 未捕获
    string capturedVariable = "captured"; //被匿名方法捕获的外部变量
    Action x = delegate()
    {
      string anonLocal = "local to anonymous method "; //匿名方法的局部变量
      Console.WriteLine(anonLocal + capturedVariable); //捕获外部遍历
    };
    x();
}

下面描述了从最简单到最复杂的全部变量:
anonLocal:它是匿名方法的局部变量,但不是EnclosingMethod的局部变量
outervariable:它是外部变量,由于在它的做用域内声明了一个匿名方法。可是,匿名方法没有引用它,因此它未被捕获。
capturedVariable:它是一个外部变量,由于在它的做用域内声明了一个匿名方法。可是,匿名方法内部引用引用了它,因此它成为了一个被捕获的变量。

2.测试被捕获的变量的行为
void EnclosingMethod(){
     string captured = "在x以前建立";

     Action x = delegate{
       Console.WriteLine(captured);
       captured = "被x改变了";
     };

     captured = "在x第一次调用以前";
     x();

     Console.WriteLine(captured);

     captured = "在x第二次调用以前";
     x();
}

输出结果:
在x第一次调用以前
被x改变了
在x第二次调用以前

3.捕获变量有什么用

简单的说,捕获变量能简化编程,避免专门建立一些类来存储一个委托须要处理的信息(做为参数传递的信息除外)。

举个例子,假定有一个任务列表,并但愿写一个方法来返回包含低于特定年龄的全部人的另外一个列表。其中,咱们知道List 有一个方法能返回一个新列表,这个方法就是FindAll。可是,在匿名方法和捕获变量问世以前,List .FindAll的存在并无多大意义,由于建立一个适合的委托是在太麻烦了。可是在C#2中,这个操做变量很是简单:

List<Person> Find(List<Person> people,int limit){
  return people.FindAll(delegate(Person person){
     return person.Age < limit; //limit是被捕获的外部变量
  });
}
4.捕获变量的延长生命周期

对于一个被捕捉的变量,只要还有任何委托实例在引用它,它就会一直存在。
被捕捉的变量存在于编译器建立的一个额外的类中,相关的方法会引用该类的实例。

5.局部变量实例化

当一个变量被捕捉时,捕捉的变量的"实例"。若是在循环内捕捉变量,第一循环迭代的变量将有别于第二次循环时捕获的变量,以此类推。

6.捕获变量的使用规则和小结
使用规则
  • 若是用或不用捕获变量时的代码一样简单,那就不用
  • 捕捉由for或foreach语句声明的变量以前,思考你的委托是否须要在循环迭代结束以后延续,以及是否想让它看到那个变量的后续值。不然的话,就在循环内另建一个变量,用来复制你想要的值。
  • 若是建立多个委托实例,并且捕获了变量,思考一下是否但愿它们捕获同一个变量
  • 若是捕获的变量不会发生改变,那就不要这么多担忧。
小结
  • 捕获的变量的生命周期变长了,至少和捕捉它的委托同样长。
  • 多个委托能够捕获同一个变量
  • 在循环内部,同一个变量声明实际会引用不一样的变量"实例"
  • 在for/foreach循环的声明中建立的变量仅在循环持续期间有效
  • 必要时建立额外的类型来保存捕获的变量

    5.小结

    C# 2根本性地改变了委托的建立方式,这样咱们就能在.NET Framework的基础上采起一种更函数化的编程风格。

C# 3

1. 做为委托的Lambda表达式

1.Func<T, TResult>

Func<T, TResult> 委托,封装一个具备一个参数并返回 TResult 参数指定的类型值的方法。下面是它的签名:

public delegate TResult Func<in T, out TResult>(T arg)

从签名中能够看到,这个委托返回的是TResult类型。可使用此委托表示一种能以参数形式传递的方法,而不用显式声明自定义委托。
封装的方法必须与此委托定义的方法签名相对应。也就是说,封装的方法必须具备一个经过值传递给它的参数,而且必须返回值。
在使用 Func<T, TResult>委托时,没必要显式定义一个封装只有一个参数的方法的委托。
例如,如下代码显式声明了一个名为 ConvertMethod 的委托,并将对UppercaseString方法的引用分配给其委托实例。

using System;
delegate string ConvertMethod(string inString);
public class DelegateExample
{
 public static void Main()
 {
   // Instantiate delegate to reference UppercaseString method
   ConvertMethod convertMeth = UppercaseString;
   string name = "Dakota";
   // Use delegate instance to call UppercaseString method
   Console.WriteLine(convertMeth(name));
 }
 private static string UppercaseString(string inputString)
 {
   return inputString.ToUpper();
 }
}

如下示例简化了此代码,它所用的方法是实例化 Func<T, TResult> 委托,而不是显式定义一个新委托并将命名方法分配给该委托。

public class GenericFunc
{
 public static void Main()
 {
    // Instantiate delegate to reference UppercaseString method
    Func<string, string> convertMethod = UppercaseString;
    string name = "Dakota";
    // Use delegate instance to call UppercaseString method
    Console.WriteLine(convertMethod(name));
 }

 private static string UppercaseString(string inputString)
 {
    return inputString.ToUpper();
 }
}

您也能够按照如下示例所演示的那样在 C# 中将 Func<T, TResult> 委托与匿名方法一块儿使用。

public class Anonymous
{
 public static void Main()
 {
    Func<string, string> convert = delegate(string s)
       { return s.ToUpper();}; 

    string name = "Dakota";
    Console.WriteLine(convert(name));   
 }
}

2.第一次转换成Lambda表达式

用一个匿名方法来建立委托实例,如:

Func<string,int> returnLength;
returnLength = delegate (string text) { return text.Length; };
Console.WriteLine(returnLength("Hello"));

最终的结果为"5"这是意料之中的事。值得注意的是,returnLength的声明和赋值是分开的,不然一行可能放不下,这样还有利于代码的理解。
匿名方法是加粗的一部分,也是打算转换成Lambda表达式的部分。
Lambda表达式最冗长的形式是:
(显式类型参数列表) => {语句}
=>部分是C#3新增的,他告诉编译器咱们正在使用一个Lambda表达式。Lambda表达式大多数时候都和一个返回非void的委托类型配合使用——若是不返回结果,语法就不像如今这样一目了然了。这标志着C#1和C#3在用法习惯上的另外一个区别。在C#1中,委托通常用于事件,不多会返回什么。在LINQ中,它们一般被视为数据管道的一部分,接收输入并返回结果,或者判断某项是否符合当前的筛选器等等。
这个版本包含了显式参数列表,并将语句放到大括号中,他看起来和匿名方法很是类似,代码以下:

Func<string,int> returnLength;
returnLength = (string text) => { return text.Length; };
Console.WriteLine(returnLength("Hello"));

一样的,加粗的那一部分是用于建立委托实例的表达式。在阅读Lambda表达式时,能够将=>部分看错"goes to"。
匿名方法中控制返回语句的规则赞成适用于lambda表达式:若是返回值是void,就不能从Lambda表达式返回一个值;若是有一个非void的返回值类型,那么每一个代码路径都必须返回一个兼容的值。

3.用单一表达式做为主题

大多数时候,均可以用一个表达式来表示整个主体,该表达式的值是Lambda的结构。在这些状况下,能够只指定哪一个表达式,不使用大括号,不使用return语句,也不添加分号。格式以下:
(显示类型的参数列表) => 表达式
在这个例子中,Lambda表达式变成了:

(string text) => text.Length

4.隐式类型的参数列表

编译器大多数状况下都能猜出参数类型,不须要你显式声明它们。在这些状况下,能够将Lambda表达式写成:
(隐式类型的参数列表) => 表达式
隐式类型的参数列表就是以一个逗号分隔的名称列表,没有类型。但隐式和显式类型的参数不能混合使用——要么全面是显式类型参数,要么所有是隐式类型参数。除此以外,若是有任何out或ref参数,就只能使用显式类型。在咱们的例子中,还能够简化成:

(text) => text.Length

5.单一参数的快捷语法

若是Lambda表达式只须要一个参数,并且这个参数能够隐式指定类型,就能够省略小括号。这种格式的Lambda表达式是:
参数名 => 表达式
所以,咱们例子中Lambda表达式最红形式是:

text => text.Length

值得注意的是,若是愿意,能够用小括号将整个Lambda表达式括起来。

6.从匿名方法到Lambda表达式

相关文章
相关标签/搜索