.NET基础 (15)委托
委托
1 请解释委托的基本原理
2 委托回调静态方法和实例方法有何区别
3 什么是链式委托
4 链式委托的执行顺序是怎么样的
5 可否定义拥有返回值的方法的委托链
6 委托通常可以应用在哪些场合
委托是一个钟数据类型,用来传递方法。委托类型继承自System.Delegate,自定义委托类型都直接继承自System.NulticastDelegate,System.NulticastDelegate又继承自System.Delegate。每个委托至少包含一个指向某个方法的指针,该方法可以是实例方法,也可以是静态方法。委托实现了回调方法的机制,能够帮助程序员设计出更加简洁优美的面向对象程序。
示例:
class SimpleDelegate { /// <summary> /// 定义的委托。 /// </summary> /// <param name="i">接受一个整型参数</i> public delegate void TestDelegate(int i); static void Main(string[] args) { //调用委托方法 TestDelegate d = new TestDelegate(PrintMessage1); //或者直接写TestDelegate d = PrintMessage1; d(0); d(1); Console.Read(); } /// <summary> /// 一个静态方法,符合TestDelegate的定义 /// </summary> /// <param name="i">整型参数</param> static void PrintMessage1(int i) { Console.WriteLine("第" + i + "个方法"); } }
输出:
第0个方法
第1个方法
本质上,委托的调用就是执行了定义在委托所生成的Invoke方法。
完全可以这样调用:
d.Invoke(0);
d.Invoke(1);
当一个实例方法被调用时,需要通过实例对象来访问,绑定一个实例方法到委托必须同时让委托得到实例的方法的代码段和实例对象的信息,这样委托在被回调的时候.NET才能成功地执行实例方法。
用Reflector查看的部分System.Delegate代码:
_target是一个指向目标实例的引用。当绑定一个实例方法给委托时,该参数会被设置为该方法所在类型的一个实例对象。而当绑定一个静态方法给委托时,该参数则被设置为null。
_mathodPtr是一个指向绑定方法代码段的指针。绑定静态方法还是实例方法在这个成员的设置上并没有不同。
也叫多播委托。所有自定义委托都直接继承自System.MulticastDelegate类型。这个类型即是为链表委托而设计的。
链式委托是指一个由委托串成的链表,当链表中的一个委托被回调时,所有链表上该委托的后续委托都将会被顺序执行。
示例:
class MulticastDelegate { /// <summary> /// 定义的委托。 /// </summary> public delegate void TestMultiDelegate(); static void Main(string[] args) { //申明一个委托变量,并绑定第一个方法 TestMultiDelegate handler = PrintMessage1; //绑定第二个方法 handler += PrintMessage2; //绑定第三个方法 handler += PrintMessage3; //检查结果 handler(); Console.Read(); } static void PrintMessage1() { Console.WriteLine("第一个方法"); } static void PrintMessage2() { Console.WriteLine("第二个方法"); } static void PrintMessage3() { Console.WriteLine("第三个方法"); } }
输出:
第一个方法
第二个方法
第三个方法
用Reflector查看编译后的Main方法:
private static void Main(string[] args) { TestMultiDelegate handler = new TestMultiDelegate(Program.PrintMessage1); handler = (TestMultiDelegate) Delegate.Combine(handler, new TestMultiDelegate(Program.PrintMessage2)); handler = (TestMultiDelegate) Delegate.Combine(handler, new TestMultiDelegate(Program.PrintMessage3)); handler(); Console.Read(); }
调用Delegate.Combine方法 详细内容可查看GitHub中Delegate源码
public static Delegate Combine(Delegate a, Delegate b) { if ((Object)a == null) // cast to object for a more efficient test return b; return a.CombineImpl(b); }
在调用CombineImpl方法
protected virtual Delegate CombineImpl(Delegate d) { throw new MulticastNotSupportedException(Environment.GetResourceString("Multicast_Combine")); }
MulticastDelegate重写了这个方法:详细内容可查看GitHub中MulticastDelegate源码
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 // This method will combine this delegate with the passed delegate 2 // to form a new delegate. 3 [System.Security.SecuritySafeCritical] // auto-generated 4 protected override sealed Delegate CombineImpl(Delegate follow) 5 { 6 if ((Object)follow == null) // cast to object for a more efficient test 7 return this; 8 9 // Verify that the types are the same... 10 if (!InternalEqualTypes(this, follow)) 11 throw new ArgumentException(Environment.GetResourceString("Arg_DlgtTypeMis")); 12 13 MulticastDelegate dFollow = (MulticastDelegate)follow; 14 Object[] resultList; 15 int followCount = 1; 16 Object[] followList = dFollow._invocationList as Object[]; 17 if (followList != null) 18 followCount = (int)dFollow._invocationCount; 19 20 int resultCount; 21 Object[] invocationList = _invocationList as Object[]; 22 if (invocationList == null) 23 { 24 resultCount = 1 + followCount; 25 resultList = new Object[resultCount]; 26 resultList[0] = this; 27 if (followList == null) 28 { 29 resultList[1] = dFollow; 30 } 31 else 32 { 33 for (int i = 0; i < followCount; i++) 34 resultList[1 + i] = followList[i]; 35 } 36 return NewMulticastDelegate(resultList, resultCount); 37 } 38 else 39 { 40 int invocationCount = (int)_invocationCount; 41 resultCount = invocationCount + followCount; 42 resultList = null; 43 if (resultCount <= invocationList.Length) 44 { 45 resultList = invocationList; 46 if (followList == null) 47 { 48 if (!TrySetSlot(resultList, invocationCount, dFollow)) 49 resultList = null; 50 } 51 else 52 { 53 for (int i = 0; i < followCount; i++) 54 { 55 if (!TrySetSlot(resultList, invocationCount + i, followList[i])) 56 { 57 resultList = null; 58 break; 59 } 60 } 61 } 62 } 63 64 if (resultList == null) 65 { 66 int allocCount = invocationList.Length; 67 while (allocCount < resultCount) 68 allocCount *= 2; 69 70 resultList = new Object[allocCount]; 71 72 for (int i = 0; i < invocationCount; i++) 73 resultList[i] = invocationList[i]; 74 75 if (followList == null) 76 { 77 resultList[invocationCount] = dFollow; 78 } 79 else 80 { 81 for (int i = 0; i < followCount; i++) 82 resultList[invocationCount + i] = followList[i]; 83 } 84 } 85 return NewMulticastDelegate(resultList, resultCount, true); 86 } 87 }
顺着内核的源码就可以一步步看到多播委托是如何执行的了。
按照委托链上的顺序从当前委托开始依次往后执行,如果有需要,可通过GetInvocationList()方法来获得委托链上所有需要执行的委托,并且按照任何希望的顺序执行它们。
委托可以是带有返回值的方法,但多于一个带返回值的方法被添加到委托链中时,程序员需要手动调用委托链上的每一个方法,否则委托使用者只能得到委托链上最后一个被执行方法的返回值。
示例:
public delegate String GetStringDelegate(); class DelegateReturn { static void Main(string[] args) { //GetSelfDefinedString最后被添加 GetStringDelegate _myDelegate1 = new GetStringDelegate(GetTimeString); _myDelegate1 += GetTypeName; _myDelegate1 += GetSelfDefinedString; Console.WriteLine(_myDelegate1()); //GetTimeString被最后添加 GetStringDelegate _myDelegate2 = new GetStringDelegate(GetSelfDefinedString); _myDelegate2 += GetTypeName; _myDelegate2 += GetTimeString; Console.WriteLine(_myDelegate2()); //GetTypeName被最后添加 GetStringDelegate _myDelegate3 = new GetStringDelegate(GetSelfDefinedString); _myDelegate3 += GetTimeString; _myDelegate3 += GetTypeName; Console.WriteLine(_myDelegate3()); Console.WriteLine(); GetStringDelegate _myDelegate4 = GetTimeString; _myDelegate4 += GetTypeName; _myDelegate4 += GetSelfDefinedString; foreach (Delegate d in _myDelegate4.GetInvocationList()) { Console.WriteLine(d.DynamicInvoke()); } Console.Read(); } static String GetTimeString() { return DateTime.Now.ToString(); } static String GetTypeName() { return typeof(DelegateReturn).ToString(); } static String GetSelfDefinedString() { return "我是字符串。"; } }
输出:
我是字符串。
2015/9/10 22:05:52
MyTest.DelegateReturn
2015/9/10 22:05:52
MyTest.DelegateReturn
我是字符串。
委托的应用场合通常是任务的执行者把细节工作进行再分配,执行者确切地知道什么工作将被执行,但把执行细节委托给其他组件、方法或者程序集。
委托比接口更加灵活,知道返回值类型和参数类型就可以定义一个委托。
举一个日志读写的例子:
日志子系统的使用者所有希望的都以单一的方法,传入日志内容和类型,而日志系统会根据具体情况来进行日志动作。对于日志系统的设计者来说,写一条日志可能需要包含一系列的工作,而日志系统决定把这些工作进行适当的分派,这时就需要一个委托成员。下面是简单的实现方式:
首先定义日志类型使用的书写日志委托类型,并且定义代表日志类别的枚举类型:
/// <summary> /// Log的类别 /// </summary> public enum LogType { Debug, Trace, Info, Warn, Error } /// <summary> /// Log委托类型,由日志使用者直接执行来完成写日志的工作 /// </summary> /// <param name="content">日志内容</param> /// <param name="type">日志类别</param> public delegate void Log(String content,LogType type);
定义日志管理类型的基本成员、构造方法、析构方法:
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 /// <summary> 2 /// 日志管理类型,基本成员和构造方法 3 /// </summary> 4 public sealed partial class LogManager:IDisposable 5 { 6 private Type _componentType; 7 private String _logfile; 8 private FileStream _fs; 9 public Log WriteLog; //用来写日志的委托 10 //锁 11 private static object mutext = new object(); 12 //严格控制无参的构造方法 13 private LogManager() 14 { 15 WriteLog = new Log(PrepareLogFile); 16 WriteLog += OpenStream; //打开流 17 WriteLog += AppendLocalTime; //添加本地时间 18 WriteLog += AppendSeperator; //添加分隔符 19 WriteLog += AppendComponentType;//添加模块类别 20 WriteLog += AppendSeperator; //添加分隔符 21 WriteLog += AppendType; //添加日志类别 22 WriteLog += AppendSeperator; //添加分隔符 23 WriteLog += AppendContent; //添加内容 24 WriteLog += AppendNewLine; //添加回车 25 WriteLog += CloseStream; //关闭流 26 } 27 /// <summary> 28 /// 构造方法 29 /// </summary> 30 /// <param name="type">使用该日志的类型</param> 31 /// <param name="file">日志文件全路径</param> 32 public LogManager(Type type, String file):this() 33 { 34 _logfile = file; 35 _componentType = type; 36 37 } 38 /// <summary> 39 /// 释放FileStream对象 40 /// </summary> 41 public void Dispose() 42 { 43 if (_fs != null) 44 _fs.Dispose(); 45 GC.SuppressFinalize(this); 46 } 47 ~LogManager() 48 { 49 if (_fs != null) 50 _fs.Dispose(); 51 } 52 53 }
委托链上的方法:
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 /// <summary> 2 /// 委托链上的方法(和日志文件有关的操作) 3 /// </summary> 4 public sealed partial class LogManager:IDisposable 5 { 6 /// <summary> 7 /// 如果日志文件不存在,则新建日志文件 8 /// </summary> 9 private void PrepareLogFile(String content, LogType type) 10 { 11 //只允许单线程创建日志文件 12 lock(mutext) 13 { 14 if (!File.Exists(_logfile)) 15 using (FileStream fs = File.Create(_logfile)) 16 { } 17 } 18 } 19 /// <summary> 20 /// 打开文件流 21 /// </summary> 22 private void OpenStream(String content, LogType type) 23 { 24 _fs = File.Open(_logfile, FileMode.Append); 25 } 26 /// <summary> 27 /// 关闭文件流 28 /// </summary> 29 private void CloseStream(String content, LogType type) 30 { 31 _fs.Close(); 32 _fs.Dispose(); 33 } 34 } 35 /// <summary> 36 /// 委托链上的方法(和日志时间有关的操作) 37 /// </summary> 38 public sealed partial class LogManager : IDisposable 39 { 40 /// <summary> 41 /// 为日志添加当前UTC时间 42 /// </summary> 43 private void AppendUTCTime(String content, LogType type) 44 { 45 String time=DateTime.Now.ToUniversalTime().ToString(); 46 Byte[] con = Encoding.Default.GetBytes(time); 47 _fs.Write(con, 0, con.Length); 48 } 49 /// <summary> 50 /// 为日志添加本地时间 51 /// </summary> 52 private void AppendLocalTime(String content, LogType type) 53 { 54 String time = DateTime.Now.ToLocalTime().ToString(); 55 Byte[] con = Encoding.Default.GetBytes(time); 56 _fs.Write(con, 0, con.Length); 57 } 58 } 59 /// <summary> 60 /// 委托链上的方法(和日志内容有关的操作) 61 /// </summary> 62 public sealed partial class LogManager : IDisposable 63 { 64 /// <summary> 65 /// 添加日志内容 66 /// </summary> 67 private void AppendContent(String content, LogType type) 68 { 69 Byte[] con = Encoding.Default.GetBytes(content); 70 _fs.Write(con, 0, con.Length); 71 } 72 /// <summary> 73 /// 为日志添加组件类型 74 /// </summary> 75 private void AppendComponentType(String content, LogType type) 76 { 77 Byte[] con = Encoding.Default.GetBytes(type.ToString()); 78 _fs.Write(con, 0, con.Length); 79 } 80 /// <summary> 81 /// 添加日志类型 82 /// </summary> 83 private void AppendType(String content, LogType type) 84 { 85 String typestring = String.Empty; 86 switch (type) 87 { 88 case LogType.Debug: 89 typestring = "Debug"; 90 break; 91 case LogType.Error: 92 typestring = "Error"; 93 break; 94 case LogType.Info: 95 typestring = "Info"; 96 break; 97 case LogType.Trace: 98 typestring = "Trace"; 99 break; 100 case LogType.Warn: 101 typestring = "Warn"; 102 break; 103 default: 104 typestring = ""; 105 break; 106 } 107 Byte[] con = Encoding.Default.GetBytes(typestring); 108 _fs.Write(con, 0, con.Length); 109 } 110 } 111 /// <summary> 112 /// 委托链上的方法(和日志的格式控制有关的操作) 113 /// </summary> 114 public sealed partial class LogManager : IDisposable 115 { 116 117 /// <summary> 118 /// 添加分隔符 119 /// </summary> 120 private void AppendSeperator(String content, LogType type) 121 { 122 Byte[] con = Encoding.Default.GetBytes(" | "); 123 _fs.Write(con, 0, con.Length); 124 } 125 /// <summary> 126 /// 添加换行符 127 /// </summary> 128 private void AppendNewLine(String content, LogType type) 129 { 130 Byte[] con = Encoding.Default.GetBytes("\r\n"); 131 _fs.Write(con, 0, con.Length); 132 } 133 } 134 /// <summary> 135 /// 修改所使用的时间类型 136 /// </summary> 137 public sealed partial class LogManager : IDisposable 138 { 139 /// <summary> 140 /// 设置使用UTC时间 141 /// </summary> 142 public void UseUTCTime() 143 { 144 WriteLog = new Log(PrepareLogFile); 145 WriteLog += OpenStream; 146 WriteLog += AppendUTCTime; 147 WriteLog += AppendSeperator; 148 WriteLog += AppendComponentType; 149 WriteLog += AppendSeperator; 150 WriteLog += AppendType; 151 WriteLog += AppendSeperator; 152 WriteLog += AppendContent; 153 WriteLog += AppendNewLine; 154 WriteLog += CloseStream; 155 } 156 /// <summary> 157 /// 设置使用本地时间 158 /// </summary> 159 public void UseLocalTime() 160 { 161 WriteLog = new Log(PrepareLogFile); 162 WriteLog += OpenStream; 163 WriteLog += AppendLocalTime; 164 WriteLog += AppendSeperator; 165 WriteLog += AppendComponentType; 166 WriteLog += AppendSeperator; 167 WriteLog += AppendType; 168 WriteLog += AppendSeperator; 169 WriteLog += AppendContent; 170 WriteLog += AppendNewLine; 171 WriteLog += CloseStream; 172 } 173 }
调用:
class UseLog { /// <summary> /// 使用日志管理类型来记录日志 /// </summary> static void Main(string[] args) { //使用日志 using(LogManager logmanager= new LogManager(Type.GetType("NET.MST.Sixth.DelegateLog.UseLog"),"C:\\TestLog.txt")) { logmanager.WriteLog("新建了日志", LogType.Debug); logmanager.WriteLog("写数据", LogType.Debug); logmanager.UseUTCTime(); logmanager.WriteLog("现在是UTC时间", LogType.Debug); logmanager.UseLocalTime(); logmanager.WriteLog("回到本地时间", LogType.Debug); logmanager.WriteLog("发生错误", LogType.Error); logmanager.WriteLog("准备退出", LogType.Info); } } }
日志文件输出:
2015/9/10 22:41:14 | Debug | Debug | 新建了日志
2015/9/10 22:43:56 | Debug | Debug | 写数据
2015/9/10 14:43:56 | Debug | Debug | 现在是UTC时间
2015/9/10 22:43:56 | Debug | Debug | 回到本地时间
2015/9/10 22:43:56 | Error | Error | 发生错误
2015/9/10 22:43:56 | Info | Info | 准备退出
转载请注明出处: