《CLR Via C# 第3版》笔记之(二十四) - 委托
2013-04-18 20:52
471 查看
委托是.net中实现回调机制的一种重要技术,尤其在编写服务端程序的时候,更是频繁使用委托。
主要内容:
委托本质
委托链
动态委托
如下定义个委托:
用ILSpy可以发现对应的IL代码如下:
从中我们可以看出
委托是一个类,继承于System.MulticastDelegate。
委托类中有3个方法和一个构造函数
委托构造函数中需传入2个参数
为了更好的了解委托的类,下面写了个简单的例子先来了解一下委托的构造函数:
注意代码的注释,我们看出C#中实例化委托的写法与IL中看到的委托的构造函数不一致,
其实这只是C#的语法糖,为了方便代码编写,将上面的代码编译后,用ILSpy查看IL代码:
我们发现,实际运行时,还是构造了2个参数传给了委托的构造函数,一个null,还有一个是函数Add的地址。
这里的第一个参数object为null的原因是,Add函数是static,不用实例化一个object来调用它。
下面构造一个非static的函数Add2来看看object的值是否还为null。
Sum s2 = new Sum((new Program()).Add2); 这句对应的IL如下:
委托的Invoke函数就是我们实际运行委托时调用的函数,至于BeginInvoke和EndInvoke则是用于异步的情况。
s(5,7) 就是 s.Invoke(5,7) 的简化写法。
这个委托链中应该还可以动态追加和减少回调函数。
实验代码如下:
运行的结果也预期相符,其实其中的 s += s1; 和 s -= s2; 也是C#的语法糖,实际调用的是System.Delegate的Combine方法和Remove方法。
感兴趣的话,可以看编译后的IL代码。
使用动态委托主要就是使用 生成委托实例的 CreateDelegate 方法和 调用委托的 DynamicInvoke 方法。
根据编译成的程序集(我的程序集名称是delegate_test.exe),在命令行中分别输入如下命令来分别调用不同的delegate:
主要内容:
委托本质
委托链
动态委托
1. 委托本质
委托本质其实就是一个类,基本上在可以定义类的地方都可以定义委托,C#中委托的写法其实只是C#的语法糖。如下定义个委托:
using System; namespace delegate_test { internal delegate int Sum(int a, int b); }
用ILSpy可以发现对应的IL代码如下:
.class private auto ansi sealed delegate_test.Sum extends [mscorlib]System.MulticastDelegate { // Methods .method public hidebysig specialname rtspecialname instance void .ctor ( object 'object', native int 'method' ) runtime managed { } // end of method Sum::.ctor .method public hidebysig newslot virtual instance int32 Invoke ( int32 a, int32 b ) runtime managed { } // end of method Sum::Invoke .method public hidebysig newslot virtual instance class [mscorlib]System.IAsyncResult BeginInvoke ( int32 a, int32 b, class [mscorlib]System.AsyncCallback callback, object 'object' ) runtime managed { } // end of method Sum::BeginInvoke .method public hidebysig newslot virtual instance int32 EndInvoke ( class [mscorlib]System.IAsyncResult result ) runtime managed { } // end of method Sum::EndInvoke } // end of class delegate_test.Sum
从中我们可以看出
委托是一个类,继承于System.MulticastDelegate。
委托类中有3个方法和一个构造函数
委托构造函数中需传入2个参数
为了更好的了解委托的类,下面写了个简单的例子先来了解一下委托的构造函数:
using System; namespace delegate_test { internal delegate int Sum(int a, int b); class Program { public static void Main(string[] args) { Console.WriteLine("Hello World!"); // 实例化委托,这里和委托的IL代码中构造函数不一致 // IL代码中 Sum 的构造函数需要传入2个参数(一个object 类型,一个 native int类型) /* .method public hidebysig specialname rtspecialname instance void .ctor ( object 'object', native int 'method' ) runtime managed { } // end of method Sum::.ctor */ Sum s = new Sum(Add); Console.Write("Press any key to continue . . . "); Console.ReadKey(true); } public static int Add(int a, int b) { return a+b; } } }
注意代码的注释,我们看出C#中实例化委托的写法与IL中看到的委托的构造函数不一致,
其实这只是C#的语法糖,为了方便代码编写,将上面的代码编译后,用ILSpy查看IL代码:
IL_0000: nop IL_0001: ldstr "Hello World!" IL_0006: call void [mscorlib]System.Console::WriteLine(string) IL_000b: nop IL_000c: ldnull IL_000d: ldftn int32 delegate_test.Program::Add(int32, int32) IL_0013: newobj instance void delegate_test.Sum::.ctor(object, native int)
我们发现,实际运行时,还是构造了2个参数传给了委托的构造函数,一个null,还有一个是函数Add的地址。
这里的第一个参数object为null的原因是,Add函数是static,不用实例化一个object来调用它。
下面构造一个非static的函数Add2来看看object的值是否还为null。
using System; namespace delegate_test { internal delegate int Sum(int a, int b); class Program { public static void Main(string[] args) { Console.WriteLine("Hello World!"); // 实例化委托,这里和委托的IL代码中构造函数不一致 // IL代码中 Sum 的构造函数需要传入2个参数(一个object 类型,一个 native int类型) /* .method public hidebysig specialname rtspecialname instance void .ctor ( object 'object', native int 'method' ) runtime managed { } // end of method Sum::.ctor */ Sum s = new Sum(Add); // 非static函数 Add2 Sum s2 = new Sum((new Program()).Add2); Console.Write("Press any key to continue . . . "); Console.ReadKey(true); } public static int Add(int a, int b) { return a+b; } public int Add2(int a, int b) { return a+b; } } }
Sum s2 = new Sum((new Program()).Add2); 这句对应的IL如下:
IL_0019: newobj instance void delegate_test.Program::.ctor() IL_001e: ldftn instance int32 delegate_test.Program::Add2(int32, int32) IL_0024: newobj instance void delegate_test.Sum::.ctor(object, native int)
委托的Invoke函数就是我们实际运行委托时调用的函数,至于BeginInvoke和EndInvoke则是用于异步的情况。
using System; namespace delegate_test { internal delegate int Sum(int a, int b); class Program { public static void Main(string[] args) { Console.WriteLine("Hello World!"); // 实例化委托,这里和委托的IL代码中构造函数不一致 // IL代码中 Sum 的构造函数需要传入2个参数(一个object 类型,一个 native int类型) /* .method public hidebysig specialname rtspecialname instance void .ctor ( object 'object', native int 'method' ) runtime managed { } // end of method Sum::.ctor */ Sum s = new Sum(Add); Sum s2 = new Sum((new Program()).Add2); // 调用委托的代码 s(5,7) 其实就是调用委托类的 Invoke方法 Console.WriteLine("5+7="+ s(5,7)); // 剥去语法外衣,也可以写成 Console.WriteLine("5+7="+ s.Invoke(5,7)); Console.Write("Press any key to continue . . . "); Console.ReadKey(true); } public static int Add(int a, int b) { return a+b; } public int Add2(int a, int b) { return a+b; } } }
s(5,7) 就是 s.Invoke(5,7) 的简化写法。
2. 委托链
委托既然是用于回调函数,那就应该可以一次回调多个函数,形成一个委托链。这个委托链中应该还可以动态追加和减少回调函数。
实验代码如下:
using System; namespace delegate_test { internal delegate void Print_Sum(int a, int b); class Program { public static void Main(string[] args) { Console.WriteLine("Hello World!"); Print_Sum s = null; Print_Sum s1 = new Print_Sum(Add1); Print_Sum s2 = new Print_Sum((new Program()).Add2); Print_Sum s3 = new Print_Sum((new Program()).Add3); s += s1; s += s2; s += s3; s(1,2); // 委托链中删除Add2 s -= s2; s(1,2); Console.Write("Press any key to continue . . . "); Console.ReadKey(true); } public static void Add1(int a, int b) { Console.WriteLine("function Add1.... result is " + (a+b).ToString()); } public void Add2(int a, int b) { Console.WriteLine("function Add2.... result is " + (2*(a+b)).ToString()); } public void Add3(int a, int b) { Console.WriteLine("function Add3.... result is " + (3*(a+b)).ToString()); } } }
运行的结果也预期相符,其实其中的 s += s1; 和 s -= s2; 也是C#的语法糖,实际调用的是System.Delegate的Combine方法和Remove方法。
感兴趣的话,可以看编译后的IL代码。
3. 动态委托
动态委托其实应用的并不多,它其实是和反射结合使用的。只有在你无法确定所调用的委托所定义的参数个数和参数类型时,才需要用到动态委托。使用动态委托主要就是使用 生成委托实例的 CreateDelegate 方法和 调用委托的 DynamicInvoke 方法。
using System; using System.Reflection; internal delegate void print_sum(int a, int b); internal delegate void print_string(string a); public class Dynamic_Delegate { public static void Main(string[] args) { // 假设要调用的delegate名称,方法名称,方法的参数都是由 args传入的。 // Main方法中并不知道要调用哪个委托 // 调用委托print_sum时: // delegate_test.exe print_sum Sum 2 3 // 调用委托print_string时: // delegate_test.exe print_string Str "hello delegate!" // 获取各个参数 string delegate_name = args[0]; string method_name = args[1]; // 由于2种委托的参数不同,所以参数可能是一个,可能是两个 object[] method_args = new object[args.Length - 2]; for (int i = 0; i<args.Length-2; i++) { // print_sum的参数需要转换成int型 if (delegate_name.Equals("print_sum")) method_args[i] = int.Parse(args[2+i]); else method_args[i] = args[2+i]; } // 获取委托类型 Type delegate_type = Type.GetType(delegate_name); // 获取方法信息 MethodInfo mi = typeof(Dynamic_Delegate).GetMethod(method_name, BindingFlags.NonPublic | BindingFlags.Static); // 根据获取的委托类型和方法信息创建一个delegate Delegate d = Delegate.CreateDelegate(delegate_type, mi); // 动态调用委托 d.DynamicInvoke(method_args); Console.Write("Press any key to continue . . . "); Console.ReadKey(true); } private static void Sum(int a, int b) { Console.WriteLine("delegate print_sum....... result is : " + (a+b).ToString()); } private static void Str(string a) { Console.WriteLine("delegate print_string.... result is : " + a); } }
根据编译成的程序集(我的程序集名称是delegate_test.exe),在命令行中分别输入如下命令来分别调用不同的delegate:
delegate_test.exe print_sum Sum 2 3 delegate_test.exe print_string Str "hello delegate!"
相关文章推荐
- 《CLR Via C# 第3版》笔记之(十五) - 接口
- 《CLR Via C# 第3版》笔记之(二) - 响应文件
- 《CLR Via C# 第3版》笔记之(六) - IL中的call和callvirt
- 《CLR Via C# 第3版》笔记之(二十二) - APM和EAP
- 《CLR Via C# 第3版》笔记之(四) - 类中字段的默认赋值
- 《CLR Via C# 第3版》笔记之(七) - const和readonly
- 《CLR Via C# 第3版》笔记之(三) - 程序集和模块
- 《CLR Via C# 第3版》笔记之(十九) - 任务(Task)
- CLR Via C#系列学习笔记之委托
- 《CLR Via C# 第3版》笔记之(十四) - 泛型高级
- 《CLR Via C# 第3版》笔记之(五) - C#中的伪Union类型
- 《CLR Via C# 第3版》笔记之(八) - 类型的转换构造器和方法
- 《CLR Via C# 第3版》笔记之(十) - 可选参数和可变数量参数
- 《CLR Via C# 第3版》笔记之(一) - CLR版本及编译平台
- 委托的杂七杂八---《clr via c#》笔记
- 《CLR Via C# 第3版》笔记之(二十三) - 线程锁和线程安全的概念
- 《CLR Via C# 第3版》笔记之(十八) - 线程池
- 《CLR Via C# 第3版》笔记之(九) - 扩展方法和分部方法
- 《CLR Via C# 第3版》笔记之(二十) - 计时器及伪共享
- 《CLR Via C# 第3版》笔记之(十二) - 事件