生活中的例子:
你早上要吃包子作为早饭,那么你可能让你爸爸或者妈妈帮你做,那你就会调用 爸爸.要包子() 或妈妈.要包子() 返回包子对象。
但是如果你爸妈不在家的时候,你只能去街上买,问题是你根本不知道街上这些人的名字,那你怎么调用呢?
所以你就要用到委托了,委托就是帮助你定义一个协议(或需求),比如delegate 包子 要包子();
你只需要知道调用要包子()返回一个包子就可以了,根本不需要知道是谁,怎么给你做的包子,就好象你到大街上买是人家自己做的还是工厂做的还是昨天剩下的你都不清楚。包子店就提供了这样一个委托的实现,你只需要到包子店调用要包子()就可以拿到包子了。
类似的例子其实还很多,如打官司,找医生看病等。
不错的讲解:http://blog.csdn.net/starryheavens/article/details/8852352
http://www.cnblogs.com/IAmBetter/archive/2012/02/08/2342443.html
总结下:
委托适合用在某时或某种情况执行某些事,而这些事你事先并不知道是啥。
一、委托初窥:一个拥有方法的对象
(1)本质:持有一个或多个方法的对象;委托和典型的对象不同,执行委托实际上是执行它所“持有”的方法。
(2)如何使用委托?
①声明委托类型(delegate关键字)
②使用该委托类型声明一个委托变量
③为委托类型增加方法
④调用委托执行方法
(3)委托的恒定性:
组合委托、为委托+=增加方法以及为委托-=移除方法让我们看起来像是委托被修改了,其实它们并没有被修改。事实上,委托是恒定的。
在为委托增加和移除方法时实际发生的是创建了一个新的委托,其调用列表是增加和移除后的方法结果。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
|
delegate void PrintFunction(); class Test { public void Print1() //实例方法 { Console.WriteLine( "Print1 -- instance" ); } public static void Print2() //静态方法 { Console.WriteLine( "Print2 -- static" ); } } class Program { static void Main() { Test t = new Test(); PrintFunction pf; pf = t.Print1; pf += Test.Print2; pf += t.Print1; pf += Test.Print2; if ( pf != null ) { pf(); } else { Console.WriteLine( "Delegate is empty" ); } } } |
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
|
补充:链式委托写法1 // 申明委托并绑定第一个方法 TestMulticastDelegate tmd = new TestMulticastDelegate(PrintMessage1); // 绑定第二个方法 tmd += new TestMulticastDelegate(PrintMessage2); // 绑定第三个方法 tmd += new TestMulticastDelegate(PrintMessage3); // 调用委托 tmd(); 链式委托写法2 如上的例子 链式委托写法3 TestMulticastDelegate tmd1 = new TestMulticastDelegate(PrintMessage1); TestMulticastDelegate tmd2 = new TestMulticastDelegate(PrintMessage2); TestMulticastDelegate tmd3 = new TestMulticastDelegate(PrintMessage3); // 核心本质:将三个委托串联起来 TestMulticastDelegate tmd = tmd1 + tmd2 + tmd3; tmd.Invoke(); 在实际开发中经常使用第二种方法,但是却不能不了解方法三,它是链式委托的本质所在。<br> //http://www.cnblogs.com/edisonchou/p/4827578.html 链式委托的讲解 |
二、匿名方法
在委托所持有的方法中,如果某个方法只被使用一次,这种情况下,除了创建委托语法的需要,没有必要创建独立的具名方法。匿名方法应运而生。
匿名方法是在初始化委托时内联(inline)声明的方法。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
using System; class Program { delegate int OtherDel( int InParam); static void Main() { OtherDel del = delegate ( int x) { return x + 20; }; Console.WriteLine( "{0}" , del(5)); Console.WriteLine( "{0}" , del(6)); } } |
三、Lambda表达式:匿名方法的另一种形式,更易阅读
(1)本质:简化语法的”语法糖“;
(2)要点:
①Lambda表达式中的参数列表(参数数量、类型和位置)必须与委托相匹配;
②表达式中的参数列表不一定需要包含类型,除非委托有ref或out关键字(此时必须显示声明);
③如果没有参数,必须使用一组空的圆括号;
(3)语法:
所有Lambda表达式都使用Lambda运算符=>,该运算符读作"goes to"。Lambda运算符的左边是输入参数(如果有),右边是表达式或语句块。
事件
事件是对象发送的消息,以发信号通知操作的发生。操作可能是由用户交互(例如鼠标单击)引起的,也可能是由某些其他的程序逻辑触发的。
引发事件的对象称为事件发送方。捕获事件并对其作出响应的对象叫做事件接收方。
在事件通信中,事件发送方类不知道哪个对象或方法将接收到(处理)它引发的事件。所需要的是在源和接收方之间存在一个媒介(或类似指针的机制)。
.NET Framework 定义了一个特殊的类型(Delegate),该类型提供函数指针的功能。
Microsoft的产品文档定义的事件:事件是一种使对象或类能够提供通知的成员。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
|
public class ConsoleEventArgs : EventArgs { // 控制台输出的消息 private string message; public string Message { get { return message; } } public ConsoleEventArgs() : base () { this .message = string .Empty; } public ConsoleEventArgs( string message) : base () { this .message = message; } } /// <summary> /// 管理控制台,在输出前发送输出事件 /// </summary> public class ConsoleManager { // 定义控制台事件成员对象 public event EventHandler<ConsoleEventArgs> ConsoleEvent; /// <summary> /// 控制台输出 /// </summary> public void ConsoleOutput( string message) { // 发送事件 ConsoleEventArgs args = new ConsoleEventArgs(message); SendConsoleEvent(args); // 输出消息 Console.WriteLine(message); } /// <summary> /// 负责发送事件 /// </summary> /// <param name="args">事件的参数</param> protected virtual void SendConsoleEvent(ConsoleEventArgs args) { // 定义一个临时的引用变量,确保多线程访问时不会发生问题 EventHandler<ConsoleEventArgs> temp = ConsoleEvent; if (temp != null ) { temp( this , args); } } } /// <summary> /// 日志类型,负责订阅控制台输出事件 /// </summary> public class Log { // 日志文件 private const string logFile = @"C:TestLog.txt" ; public Log(ConsoleManager cm) { // 订阅控制台输出事件 cm.ConsoleEvent += this .WriteLog; } /// <summary> /// 事件处理方法,注意参数固定模式 /// </summary> /// <param name="sender">事件的发送者</param> /// <param name="args">事件的参数</param> private void WriteLog( object sender, EventArgs args) { // 文件不存在的话则创建新文件 if (!File.Exists(logFile)) { using (FileStream fs = File.Create(logFile)) { } } FileInfo fi = new FileInfo(logFile); using (StreamWriter sw = fi.AppendText()) { ConsoleEventArgs cea = args as ConsoleEventArgs; sw.WriteLine(DateTime.Now.ToString( "yyyy-MM-dd HH:mm:ss" ) + "|" + sender.ToString() + "|" + cea.Message); } } } class Program { static void Main( string [] args) { // 控制台事件管理者 ConsoleManager cm = new ConsoleManager(); // 控制台事件订阅者 Log log = new Log(cm); cm.ConsoleOutput( "测试控制台输出事件" ); cm.ConsoleOutput( "测试控制台输出事件" ); cm.ConsoleOutput( "测试控制台输出事件" ); Console.ReadKey(); } } |
事件和委托有神马联系?
经常听人说,委托本质是一个类型,而事件本质是一个特殊的委托类型的实例。最好的办法莫过于通过查看原代码和编译后的IL代码进行分析。
① 回顾刚刚的代码,在ConsoleManager类中定义了一个事件成员
public event EventHandler<ConsoleEventArgs> ConsoleEvent;
EventHandler是.NET框架中提供的一种标准的事件模式.
② 下面通过Reflector来查看一下事件ConsoleEvent的IL代码(中间代码),可以更方便地看到这一点:
首先,查看EventHandler的IL代码,可以看到在C#编译器编译delegate代码时,编译后是成为了一个class。
其次当C#编译器编译event代码时首先为类型添加一个EventHandler<T>的委托实例对象,然后为其增加一对add/remove方法用来实现从委托链中添加和移除方法的功能。
通过查看add_ConsoleEvent的IL代码,可以清楚地看到订阅事件的本质是调用Delegate的Combine方法将事件处理方法绑定到委托链中。
下面如题,我们来讲委托。
Delegate委托,在.NET中应用的非常广泛。会涉及到Lambda表达式,事件,匿名方法等(请关注后续博文)。
那么何为委托?
通俗的来讲,委托除了必须指定delegate关键字和没有方法实体之外,和指定方法没有更多区别。你可以当它是一个占位符,比如你在写代码的时候并不知道你将要处理的是什么。你只需要知道你将要引入的参数类型和输出类型是什么并定义它即可。这就是书本上所传达的方法签名必须相同的意思。
下面我们来定义一个基本的委托:
- public class Test
- {
- //定义委托
- public delegate void D_Math(int a, int b);
- public void Add(int a, int b)
- {
- Console.WriteLine("Add方法结果:{0}", a + b);
- }
- public void Cut(int a, int b)
- {
- Console.WriteLine("Cut方法结果:{0}", a - b);
- }
- }
- [TestClass]
- public class UnitTest1
- {
- [TestMethod]
- public void TestMethod1()
- {
- Test t = new Test();
- Test.D_Math D = new Test.D_Math(t.Add);//委托实例化,也可Test.D_Math D =t.Add;
- D += t.Cut;//委托可以以队列方式执行多个方法,以+=运算符或者-=来增加或者取消队列中的方法
- D(5, 6);
- }
- }
执行结果:
以上看出来委托实用的地方了吗?即委托可以执行任何引入参数类型相同且返回类型相同的方法,甚至可以执行签名相同的方法队列。
那么我们的方法签名(即引入参数和输出参数)真的必须与委托完全一致吗?答:不是的,我们不能忽略协变与逆变。
我们这里简单介绍一下协变与逆变的知识。
“协变”是指能够使用与原始指定的派生类型相比,派生程度更大的类型。
“逆变”则是指能够使用派生程度更小的类型。
那么,我们的委托也是接受协变与逆变的。
意思是,如果定义一个delegate,那么不仅仅签名完全相同的方法可以赋值给delegate变量。
如果一个方法的参数表符合delegate声明,但返回的类型是(delegate声明返回类型)的派生类,那也可以将这个方法赋值给这个delegate变量。
如果一个方法的返回类型符合delegate的声明,但参数是(delegate声明参数类型)的祖先类,那也可以将这个方法赋值给这个delegate变量。
如果一个方法的参数和返回类型都符合上面两行的假设,那也可以将这个方法赋值给这个delegate变量。
以下以两个简单示例解释协变与逆变:
协变:
- public class A { }
- public class B:A { }//B继承自A
- public class Test
- {
- //定义委托
- public delegate A D_Math();
- public B Add()
- {
- return new B();
- }
- public A Add2()
- {
- return new A();
- }
- }
- [TestClass]
- public class UnitTest1
- {
- [TestMethod]
- public void TestMethod1()
- {
- Test.D_Math d = new Test.D_Math(new Test().Add);//委托返回A,而Add方法返回B,此为协变。
- }
- }
逆变:
- public class A { }
- public class B:A { }//B继承自A
- public class Test
- {
- //定义委托
- public delegate void D_Math(B b);
- public void Add(B b)
- {
- }
- public void Add2(A a)
- {
- }
- }
- [TestClass]
- public class UnitTest1
- {
- [TestMethod]
- public void TestMethod1()
- {
- Test.D_Math d = new Test.D_Math(new Test().Add2);//委托引入参数B,而Add方法参数为A类型,此为协逆变。
- }
- }
以上是本篇对于委托的讲解,欢迎大家补充与修正。