CLR via C#, 4th -- 【基本类型】 -- 第17章委 托

Microsoft.NET Framework 通过委托来提供回调函数机制.

17.1 初识委托

NET Framework的回调函数和非托管Windows编程环境的回调函数一样有用,一样普遍。但是,.NET Framework提供了称为委托的类型安全机制。

using System;  
using System.Windows.Forms;   
using System.IO;  
 
// Declare a delegate type; instances refer to a method that   
// takes an Int32 parameter and returns void.   
internal delegate void Feedback(Int32 value);   
 
public sealed class Program {  
   public static void Main() {   
      StaticDelegateDemo();   
      InstanceDelegateDemo();  
      ChainDelegateDemo1(new Program());   
      ChainDelegateDemo2(new Program());   
   }  
 
   private static void StaticDelegateDemo() {   
      Console.WriteLine("­­­­­ Static Delegate Demo ­­­­­");  
      Counter(1, 3, null);  
      Counter(1, 3, new Feedback(Program.FeedbackToConsole));  
      Counter(1, 3, new Feedback(FeedbackToMsgBox)); // "Program." is optional  
      Console.WriteLine();  
   }  
 
   private static void InstanceDelegateDemo() {   
      Console.WriteLine("­­­­­ Instance Delegate Demo ­­­­­");  
      Program p = new Program();   
      Counter(1, 3, new Feedback(p.FeedbackToFile));   
 
      Console.WriteLine();  
   }  
   
   private static void ChainDelegateDemo1(Program p) {  
      Console.WriteLine("­­­­­ Chain Delegate Demo 1  ­­­­­");  
      Feedback fb1 = new Feedback(FeedbackToConsole);  
      Feedback fb2 = new Feedback(FeedbackToMsgBox);   
      Feedback fb3 = new Feedback(p.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 p) {  
      Console.WriteLine("­­­­­ Chain Delegate Demo 2  ­­­­­");  
      Feedback fb1 = new Feedback(FeedbackToConsole);  
      Feedback fb2 = new Feedback(FeedbackToMsgBox);   
      Feedback fb3 = new Feedback(p.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 any callbacks are specified, call them  
         if (fb != null)    
            fb(val);   
      }   
   }  
 
   private static void FeedbackToConsole(Int32 value) {   
      Console.WriteLine("Item=" + value);  
   }  
 
   private static void FeedbackToMsgBox(Int32 value) {  
      MessageBox.Show("Item=" + value);  
   }  
 
   private void FeedbackToFile(Int32 value) {   
      using (StreamWriter sw = new StreamWriter("Status", true)) {  
         sw.WriteLine("Item=" + value);  
      }   
   }  
}
   

17.2 用委托回调静态方法

在一个类型中通过委托来调用另一个类型的私有成员,只要委托对象是由具有足够安全性/可访问性的代码创建的,便没有问题。
将方法绑定到委托时,C#和CLR都允许引用类型的协变性(covariance)逆变性(contravariance),协变性是指方法能返回从委托的返回类型派生的一个类型。逆变性是指方法获取的参数可以是委托的参数类型的基类。
只有引用类型才支持协变性与逆变性,值类型或void不支持。值类型和void之所以不支持,是因为它们的存储结构是变化的,而引用类型的存储结构始终是一个指针。

17.3 用委托回调实例方法

委托除了能调用静态方法,还能为具体的对象调用实例方法。

17.4 委托揭秘

从表面看,委托似乎很容易使用:用C#的delegate关键字定义,用熟悉的new操作符构造委托实例,用熟悉的方法调用语法来调用回调函数(用引用了委托对象的变量替代方法名)。

internal delegate void Feedback(Int32 value);

看到这行代码后,编译器实际会像下面这样定义一个完整的类:

internal class Feedback : System.MulticastDelegate {   
   // Constructor   
   public Feedback(Object @object, IntPtr method);   
 
   // Method with same prototype as specified by the source code  
   public virtual void Invoke(Int32 value);  
 
   // Methods allowing the callback to be called asynchronously  
   public virtual IAsyncResult BeginInvoke(Int32 value,   
      AsyncCallback callback, Object @object);  
   public virtual void EndInvoke(IAsyncResult result);  
}

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

System.MulticastDelegate派生自System.Delegate,后者又派生自System.Object.
委托类既可嵌套在一个类型中定义,也可在全局范围中定义。简单地说,由于委托是类,所以凡是能够定义类的地方,都能定义委托。

由于所有委托类型都派生自MulticastDelegate,所以它们继承了MulticastDelegate的字段、属性和方法。在所有这些成员中,有三个非公共字段是最重要的。

Field

Type

Description

_target

System.Object

When the delegate object wraps a static method, this field is null. When the delegate objects wraps an instance method, this field refers to the object that should be operated on when the callback method is called. In other words, this field indicates the value that should be passed for the instance method’s implicit this parameter.

_methodPtr

System.IntPtr

An internal integer the CLR uses to identify the method that is to be called back.

_invocationList

System.Object

This field is usually null. It can refer to an array of delegates when building a delegate chain (discussed later in this chapter).


所有委托都有一个构造器,它获取两个参数:一个是对象引用,另一个是引用了回调方法的整数。
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);

private static void Counter(Int32 from, Int32 to, Feedback fb) {  
   for (Int32 val = from; val <= to; val++) {   
      // If any callbacks are specified, call them   
      if (fb != null)    
         fb(val);   
   }  
}

因为编译器知道1是引用了委托对象的变量,所以会生成代码调用该委托对象的Invoke方法。也就是说,编译器在看到以下代码时:

fb(val);

它将生成以下代码,好像源代码本来就是这么写的一样:

fb.Invoke(val);

在Invoke被调用时,它使用私有字段target和_methodPtr在指定对象上调用包装好的回调方法。注意,Invoke方法的签名和委托的签名匹配。

17.5 用委托回调多个方法(委托链)

委托链是委托对象的集合。
使用Delegate类的公共静态方法Combine将委托添加到链中。

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

执行这行代码时,Combine方法发现试图合并的是null和t1。在内部,Combine直接返回t1中的值,所以bChain变量现在引用1b1变量所引用的委托对象。
再次调用Combine方法在链中添加第二个委托:

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

在内部,Combine方法发现tbChain已引用了一个委托对象,所以Combine会构造一个新的委托对象。新委托对象对它的私有字段target和_methodPtr进行初始化,具体的值对于目前的讨论来说并不重要。重要的是,_invocationList字段被初始化为引用一个委托对救组。

数组的第一个元素(索引0)被初始化为引用包装了FeedbackToConsole方法的委托
(也就是fbChain目前引用的委托)。数组的第二个元素(索引1)被初始化为引用包装了FeedbackToMsgBox方法的委托(也就是fb2引用的委托)。最后,DChain被设为引用新建的委托对象。

 

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

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

之前新建的委托及其_invocationList字段引用的数组现在可以进行垃圾回收。

public void Invoke(Int32 value) {  
   Delegate[] delegateSet = _invocationList as Delegate[];  
   if (delegateSet != null) {  
      // This delegate's array indicates the delegates that should be called
      foreach (Feedback d in delegateSet)  
         d(value);   // Call each delegate   
   } else {  
      // This delegate identifies a single method to be called back   
      // Call the callback method on the specified target object.   
      _methodPtr.Invoke(_target, value);   
      // The preceding line is an approximation of the actual code.   
      // What really happens cannot be expressed in C#.   
   }  
}

还可调用Delegate的公共静态方法Remove从链中删除委托。

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

Remove查找的是其target和methodPtr字段与第二个实参(本例是新建的Feedback委托)中的字段匹配的委托。如果找到匹配的委托,并且(在删除之后)数组中只剩余一个数据项,就返回那个数据项。如果找到匹配的委托,并且数组中还剩余多个数据项,就新建一个委托对象-其中创建并初始化的invocationList数组将引用原始数组中的所有数据项,当然被删除的数据项除外-并返回对这个新建委托对象的引用。如果从链中删除了仅有的一个元素,Remove会返回null.注意,每次Remove方法调用只能从链中删除一个委托,它不会删除有匹配的_target和methodPtr字段的所有委托。

public Int32 Invoke(Int32 value) {  
   Int32 result;  
   Delegate[] delegateSet = _invocationList as Delegate[];  
   if (delegateSet != null) {  
      // This delegate's array indicates the delegates that should be called  
      foreach (Feedback d in delegateSet)  
         result = d(value);   // Call each delegate  
   } else {  
      // This delegate identifies a single method to be called back   
      // Call the callback method on the specified target object.   
      result = _methodPtr.Invoke(_target, value);  
      // The preceding line is an approximation of the actual code.   
      // What really happens cannot be expressed in C#.   
   }  
   return result;   
}

数组中的每个委托被调用时,其返回值被保存到result变量中。循环完成后,result变量只包含调用的最后一个委托的结果(前面的返回值会被丢弃),该值返回给调用Invoke的代码。

17.5.1 C#对委托链的支持

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

17.5.2 取得对委托链调用的更多控制

链中的所有项都会被调用,因为委托类型的Invoke方法包含了对数组中的所有项进行遍历的代码。这是一个很简单的算法。尽管这个简单的算法足以应付很多情形,但也有它的局限性。例如,除了最后一个返回值,其他所有回调方法的返回值都会被丢弃。但局限并不止于此。如果被调用的委托中有一个抛出了异常或阻塞了相当长一段时间,会出现什么情况呢?由于这个简单的算法是顺序调用链中的每一个委托,所以一个委托对象出现问题,链中后续的所有对象都调用不了。
由于这个算法有的时候不胜其任,所以MulticastDelegate类提供了一个实例方法GetInvocationList,用于显式调用链中的每一个委托,并允许你使用需要的任何算法

public abstract class MulticastDelegate : Delegate {   
   // Creates a delegate array where each element refers    
   // to a delegate in the chain.  
   public sealed override Delegate[] GetInvocationList();   
}

GetInvocationList方法操作从MulticastDelegate派生的对象,返回包含Delegate引用的个数组,其中每个引用都指向链中的一个委托对象。在内部,GetInvocationList构造并初始化一个数组,让它的每个元素都引用链中的一个委托,然后返回对该数组的引用。如果_invocationList字段为null,返回的数组就只有一个元素,该元素引用链中唯一的委托,即委托实例本身。

using System;  
using System.Reflection;  
using System.Text;  
 
// Define a Light component.  
internal sealed class Light {  
   // This method returns the light's status.   
   public String SwitchPosition() {   
      return "The light is off";   
   }  
}  
 
// Define a Fan component.  
internal sealed class Fan {   
   // This method returns the fan's status.  
   public String Speed() {  
      throw new InvalidOperationException("The fan broke due to overheating");  
   }  
}  
 
// Define a Speaker component.   
internal sealed class Speaker {  
   // This method returns the speaker's status.   
   public String Volume() {   
      return "The volume is loud";  
   }  
}  
 
public sealed class Program {  
 
    // Definition of delegate that allows querying a component's status.  
   private delegate String GetStatus();  
 
   public static void Main() {   
      // Declare an empty delegate chain.  
      GetStatus getStatus = null;  
 
      // Construct the three components, and add their status methods    
      // to the delegate chain.  
      getStatus += new GetStatus(new Light().SwitchPosition);  
      getStatus += new GetStatus(new Fan().Speed);   
      getStatus += new GetStatus(new Speaker().Volume);   
 
      // Show consolidated status report reflecting    
      // the condition of the three components.   
      Console.WriteLine(GetComponentStatusReport(getStatus));  
   }  
 
   // Method that queries several components and returns a status report   
   private static String GetComponentStatusReport(GetStatus status) {  
 
      // If the chain is empty, there is nothing to do.   
      if (status == null) return null;  
 
      // Use this to build the status report.   
      StringBuilder report = new StringBuilder();  
 
      // Get an array where each element is a delegate from the chain.   
      Delegate[] arrayOfDelegates = status.GetInvocationList();  
 
      // Iterate over each delegate in the array.    
      foreach (GetStatus getStatus in arrayOfDelegates) {   
 
  
      try {  
            // Get a component's status string, and append it to the report.  
            report.AppendFormat("{0}{1}{1}", getStatus(), Environment.NewLine);  
         }   
         catch (InvalidOperationException e) {  
            // Generate an error entry in the report for this component.   
            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.GetMethodInfo().Name,    
               e.Message);  
         }   
      }   
 
      // Return the consolidated report to the caller.  
      return report.ToString();  
   }  
}
View Code

17.6委托定义不要太多(泛型委托)

.NET Framework现在提供了17个Action委托,它们从无参数到最多16个参数。除了Action委托,NET Framework还提供了17个Func函数,允许回调方法返回值. 建议尽量使用这些委托类型,而不是在代码中定义更多的委托类型。这样可减少系统中的类型数量,同时简化编码。然而,如需使用ref或out关键字以传引用的方式传递参数,就可能不得不定义自己的委托。
如果委托要通过C#的params关键字获取数量可变的参数,要为委托的任何参数指定默认值,或者要对委托的泛型类型参数进行约束,也必须定义自己的委托类型。

17.7 C#为委托提供的简化语法

17.7.1 简化语法1:不需要构造委托对象

C#允许指定回调方法的名称,不必构造委托对象包装器。

internal sealed class AClass {   
   public static void CallbackWithoutNewingADelegateObject() {   
      ThreadPool.QueueUserWorkItem(SomeAsyncTask, 5);  
   }  
 
   private static void SomeAsyncTask(Object o) {  
      Console.WriteLine(o);   
   }  
}

17.7.2 简化语法2:不需要定义回调方法(lambda表达式)

C#允许以内联(直接嵌入)的方式写回调方法的代码,不必在它自己的方法中写。

internal sealed class AClass {   
   public static void CallbackWithoutNewingADelegateObject() {   
      ThreadPool.QueueUserWorkItem(  obj => Console.WriteLine(obj), 5);   
   }  
}

编译器选择的方法名以<符号开头,这是因为在C#中,标识符是不能包含<符号的;这就确保了你不会碰巧定义一个编译器自动选择的名称。

匿名函数被标记为private,禁止非类型内定义的代码访问(尽管反射能揭示出方法确实存在)。另外,匿名函数被标记为static,因为代码没有访问任何实例成员。不过,代码可引用类中定义的任何静态字段或静态方法。

internal sealed class AClass {   
   private static String sm_name;  // A static field   
 
   public static void CallbackWithoutNewingADelegateObject() {   
      ThreadPool.QueueUserWorkItem(   
         // The callback code can reference static members.  
         obj =>Console.WriteLine(sm_name + ": " + obj),   
         5);   
   }  
}

匿名函数的代码就可以包含对实例成员的引用。不包含实例成员引用,编译器仍会生成静态匿名函数,因为它的效率比实例方法高。之所以更高效,是因为不需要额外的this参数。但是,如果匿名函数的代码确实引用了实例成员,编译器就会生成非静态匿名函数。

internal sealed class AClass {   
   private String m_name;  // An instance field   
 
   // An instance method  
   public void CallbackWithoutNewingADelegateObject() {   
      ThreadPool.QueueUserWorkItem(   
         // The callback code can reference instance members.  
         obj => Console.WriteLine(m_name + ": " + obj),   
         5);   
   }  
}
// If the delegate takes no arguments, use () 
Func<String> f = () => "Jeff"; 
 
// If the delegate takes 1+ arguments, you can explicitly specify the types 
Func<Int32, String> f2 = (Int32 n) => n.ToString();  
Func<Int32, Int32, String> f3 = (Int32 n1, Int32 n2) => (n1 + n2).ToString(); 
 
// If the delegate takes 1+ arguments, the compiler can infer the types  
Func<Int32, String> f4 = (n) => n.ToString(); 
Func<Int32, Int32, String> f5 = (n1, n2) => (n1 + n2).ToString(); 
 
// If the delegate takes 1 argument, you can omit the ()s 
Func<Int32, String> f6 = n => n.ToString();  
 
// If the delegate has ref/out arguments, you must explicitly specify ref/out and the type 
Bar b = (out Int32 n) => n = 5;

lambda表达式的主要优势在于,它从你的源代码中移除了一个“间接层”(a level of indirection),或者说避免了迂回。正常情况下,必须写一个单独的方法,命名该方法,再在需要委托的地方传递这个方法名。方法名提供了引用代码主体的一种方式,如果要在多个地方引用同一个代码主体,单独写一个方法并命名确实是理想的方案。但如果只需在代码中引用这个主体一次,那么lambda表达式允许直接内联那些代码,不必为它分配名称,从而提高了编程效率。

17.7.3 简化语法3:局部变量不需要手动包装到类中即可传给回调方法

有时还希望回调代码引用存在于义方法中的局部参数或变量。

internal sealed class AClass {   
   public static void UsingLocalVariablesInTheCallbackCode(Int32 numToDo) {   
      // Some local variables  
      Int32[] squares = new Int32[numToDo];  
      AutoResetEvent done = new AutoResetEvent(false);  
 
      // Do a bunch of tasks on other threads   
      for (Int32 n = 0; n < squares.Length; n++) {   
         ThreadPool.QueueUserWorkItem(  
            obj => {   
               Int32 num = (Int32) obj;    
 
                // This task would normally be more time consuming   
               squares[num] = num * num;   
                  
               // If last task, let main thread continue running  
               if (Interlocked.Decrement(ref numToDo) == 0)    
                  done.Set();    
            },    
            n);   
      }   
 
      // Wait for all the other threads to finish  
      done.WaitOne();  
 
      // Show the results   
      for (Int32 n = 0; n < squares.Length; n++)   
         Console.WriteLine("Index {0}, Square={1}", n, squares[n]);   
   }  
}

现在,想象lambda表达式主体中的代码在一个单独的方法中(确实如此,这是CLR要求的)。变量的值如何传给这个单独的方法?唯一的办法是定义一个新的辅助类,这个类要为打算传给回调代码的每个值都定义一个字段。此外,回调代码还必须定义成辅助类中的实例方法。然后,包含lambda表达式主体的方法必须构造辅助类的实例,用方法定义的局部变量的值来初始化该实例中的字段。然后,构造绑定到辅助对象/实例方法的委托对象。

当lambda表达式造成编译器生成一个类,而且参数/局部变量被转变成该类的字段后,变量引用的对象的生存期被延长了。正常情况下,在方法中最后一次使用参数/局部变量之后,这个参数局部变量就会“离开作用域”,结束其生命期。但是,将变量转变成字段后,只要包含字段的那个对象不“死”,字段引用的对象也不会“死”。这在大多数应用程序中不是大问题,但有时要注意一下。
如果需要在回调方法中包含3行以上的代码,就不使用lambda表达式。相反,我会手动写一个方法,并为其分配自己的名称。但如果使用得当,匿名方法确实能显著提高开发人员的效率和代码的可维护性。

17.8 委托和反射

System.Delegate.MethodInfo提供了一个CreateDelegate方法,允许在编译时不知道委托的所有必要信息的前提下创建委托。

public abstract class MethodInfo : MethodBase {   
   // Construct a delegate wrapping a static method.   
   public virtual Delegate CreateDelegate(Type delegateType);  
 
   // Construct a delegate wrapping an instance method; target refers to the ‘this’ argument.  
   public virtual Delegate CreateDelegate(Type delegateType, Object target);  
}

创建好委托后,用DelegateDynamicInvoke方法调用它。

public abstract class Delegate {   
   // Invoke a delegate passing it parameters   
   public Object DynamicInvoke(params Object[] args);  
}

使用反射API(参见第23章“程序集加载和反射”),首先必须获取引用了回调方法的一个Methodinfo对象。然后,调用CreateDelegate方法来构造由第一个参数delegateType所标识的Delegate派生类型的对象。如果委托包装了实例方法,还要向CreateDelegate传递一个 target参数,指定作为this参数传给实例方法的对象。

System.Delegate的DynamicInvoke方法允许调用委托对象的回调方法,传递一组在运行时确定的参数。调用DynamicInvoke时,它会在内部保证传递的参数与回调方法期望的参数兼容。如果兼容,就调用回调方法;否则抛出ArgumentException异常。DynamicInvoke返回回调方法所返回的对象。

using System;  
using System.Reflection;  
using System.IO;  
 
 
// Here are some different delegate definitions   
internal delegate Object TwoInt32s(Int32 n1, Int32 n2);   
internal delegate Object OneString(String s1);  
 
 
public static class DelegateReflection {   
   public static void Main(String[] args) {  
      if (args.Length < 2) {  
         String usage =  
            @"Usage:" +  
            "{0} delType methodName [Arg1] [Arg2]" + 
            "{0}   where delType must be TwoInt32s or OneString" +  
                        "{0}   if delType is TwoInt32s, methodName must be Add or Subtract" +  
            "{0}   if delType is OneString, methodName must be NumChars or Reverse" + 
            "{0}" + 
            "{0}Examples:" +  
            "{0}   TwoInt32s Add 123 321" +  
            "{0}   TwoInt32s Subtract 123 321" +  
            "{0}   OneString NumChars \"Hello there\"" +  
            "{0}   OneString Reverse  \"Hello there\""; 
         Console.WriteLine(usage, Environment.NewLine); 
         return;  
      } 
 
      // Convert the delType argument to a delegate type  
      Type delType = Type.GetType(args[0]);  
      if (delType == null) {  
         Console.WriteLine("Invalid delType argument: " + args[0]); 
         return;  
      } 
 
      Delegate d; 
      try {  
         // Convert the Arg1 argument to a method 
         MethodInfo mi = typeof(DelegateReflection).GetTypeInfo().GetDeclaredMethod(args[1]);
 
         // Create a delegate object that wraps the static method 
         d = mi.CreateDelegate(delType); 
      } 
      catch (ArgumentException) {  
         Console.WriteLine("Invalid methodName argument: " + args[1]); 
         return;  
      } 
  
      // Create an array that that will contain just the arguments  
      // to pass to the method via the delegate object 
      Object[] callbackArgs = new Object[args.Length  ­ 2]; 
 
      if (d.GetType() == typeof(TwoInt32s)) { 
         try { 
            // Convert the String arguments to Int32 arguments 
            for (Int32 a = 2; a < args.Length; a++)  
               callbackArgs[a ­ 2] = Int32.Parse(args[a]); 
         } 
         catch (FormatException) { 
            Console.WriteLine("Parameters must be integers."); 
            return; 
         } 
      } 
 
      if (d.GetType() == typeof(OneString)) { 
         // Just copy the String argument  
         Array.Copy(args, 2, callbackArgs, 0, callbackArgs.Length); 
      } 
      
      try {  
         // Invoke the delegate and show the result  
         Object result = d.DynamicInvoke(callbackArgs); 
         Console.WriteLine("Result = " + result); 
      } 
      catch (TargetParameterCountException) { 
         Console.WriteLine("Incorrect number of parameters specified."); 
      } 
   }  
 
 
   // This callback method takes 2 Int32 arguments   
   private static Object Add(Int32 n1, Int32 n2) {   
      return n1 + n2;  
   }  
 
   // This callback method takes 2 Int32 arguments   
   private static Object Subtract(Int32 n1, Int32 n2) {   
      return n1  ­ n2;  
   }  
 
   // This callback method takes 1 String argument   
   private static Object NumChars(String s1) {  
      return s1.Length;  
   }  
 
   // This callback method takes 1 String argument   
   private static Object Reverse(String s1) {   
      return new String(s1.Reverse().ToArray());  
   }  
}
View Code
posted @ 2019-12-16 23:19  FH1004322  阅读(104)  评论(0)    收藏  举报