//From the .NET Core library //Define the delegate type: public delegate int Comparison<in T>(T left,T right);
//inside a class definition //Declare an instance of that type: public Comparison<T> comparator;
int result= comparator(left,right);
private static int CompareLength(string left,string right)=> left.Length.CompareTo(right,Length);
phrases.Sort(CompareLength);
Comparison<string> comparer=CompareLength; phrases.Sort(comparer);
Comparison<string> comparer=(left,right)=>left.Length.CompareTo(ritht.Length); phrases.Sort(comparer);
public delegate void Action(); public delegate void Action<in T> (T arg); public delegate void Action<in T1,in T2>(T1 arg1,T2 arg2); //Other variations removed for brevity.
public delegate TResult Func<out TResult>(); public delegate TResult Func<in T1,out TResult>(T1 arg); pulbic delegate TResult Func<in T1,in T2,out TResult>(T1 arg1,T2 arg2);
public delegate bool Predicate<in T>(T obj);
Func<string ,bool>TestForString; Predicate<string>AnotherTestForString;
- 通过委托生成自己得组件
- 首次实现
- 设置输出格式
- 生成第二个输出引擎
- 显示另外2个
public static class Logger { public static Action<string>?WriteMessage; public static void LogMessage(string msg) { if(WriteMessage is not null) WriteMessage(msg); } }
public static class LoggingMethods { public static void LogToConsole(string message) { Console.Error.WriteLine(message); } }
Logger.WriteMessage+=LoggingMethods.LogToConsole;
public enum Severity { Verbose, Trace, Information, Waring, Error, Critical }
public static class Logger { public static Action<string>?WriteMessage; public static void LogMessage(Severity s,string component,string msg) { var outputMsg=$"{DateTime.Now}\t{s}\t{component}\t{msg}" ; if(WriteMessage is not null) WriteMessage(outputMsg); } }
接下来,使用Severity参数来筛选发送到日志输出的消息。
public static class Logger { public static Action<string> WriteMessage; public static Severity LogLevel { get; set; } = Severity.Warning; public static void LogMessage(Severity s, string component, string msg) { if (s < LogLevel) { return; } var outputMsg = $"{DateTime.Now}\t{s}\t{component}\t{msg}"; if (WriteMessage != null) WriteMessage(outputMsg); } }
实践:
已向日志记录基础结构添加了新功能。由于记录器组件极其松散地耦合到输出机制,因此可在不影响代码实现记录器托管的情况下添加新功能。
继续构建,你会看到更多的示例,其中显示这种松散的耦合度在更新站点部件方面实现了很高的灵活性,而不会对其他位置做出更改。实际上,在更大的应用程序中,记录器输出类可能位于不同的程序集中,甚至不需要重新生成。
5、生成第二个输出引擎:
还将附带日志组件。我们再添加一个将消息记录到文件的输出引擎。这将是一个更为普及的输出引擎。它将是一个封装文件操作的类,并确保文件在每次写入后始终处于关闭状态。这可以确保生成每条消息后将所有数据刷新到磁盘。
下面是基于文件的记录器:
public class FileLogger { private readonly string logPath; public FileLogger(string path) { logPath = path; Logger.WriteMessage += LogMessage; } public void DetachLog() => Logger.WriteMessage -= LogMessage; //make sure this can't throw. private void LogMessage(string msg) { try { using(var log = File.AppendText(logPath)) { log.WriteLine(msg); log.Flush(); } } catch (Exception) { //Hmm . We caught an exception while //logging . We can't really log the //problem (since it's the log that's failing). //So , while normally, catching an exception //and doing nothing isn't wise,it's really the //only reasonable option here. throw; } } }
创建此类后,可将它实例化,然后它会将LogMessage方法附加到记录器组件中:
var file = new FileLogger("log.txt");
这两项并不互相排斥。你可以附加这两种日志方法并生成要发送到控制台和文件的消息:
var fileOutput = new FileLogger("log.txt"); Logger.WriteMessage += LoggingMethods.LogToConsole; // LoggingMethods is the static class we utilized earlier
以后,即使在同一个应用程序中,也可在不对系统产生任何其他问题的情况下删除其中一个委托
Logger.WriteMessage -= LoggingMethods.LogToConsole;
实践:
现在,你已添加日志记录子系统的第二个输出处理程序。 这需要更多的基础结构来正确支持文件系统。 此委托为实例方法。 其为私有方法。 由于委托基础结构可以连接委托,因此不需要太高的可访问性。
其次,基于委托的设计可实现多种输出方法,且无需额外的代码。 无需生成任何其他基础结构来支持多种输出方法。 它们将变为调用列表上的另一种方法。
需要特别注意文件日志记录输出方法中的代码。 对其进行编码以确保不引发任何异常。 虽然不是绝对必要,但这通常是很好的做法。 如果任意一种委托方法引发异常,将不会调用该调用中剩余的其他委托。
最后请注意,文件记录器必须通过打开和关闭每条日志消息上的文件来管理其资源。 可以选择让文件保持打开状态,并在完成后执行 IDisposable
以关闭文件。 这两种方法各有利弊。 两者都在类之间创建了更高的耦合度。
为了支持这两种方案,Logger
类中的代码都不需要更新
6、处理NULL委托
最后,更新LogMessage方法,从而在没有选择输出机制的情况下更加可靠。WriteMessage委托没有附加调用列表时,当前实现将引发NullReferenceException。你可能更需要在没有附加方法时自行继续的设计。将null条件运算符与Delegate.Invoke()方法结合使用时,很容易实现该目标:
public static void LogMessage(string msg) { WriteMessage?.Invoke(msg); }
当左操作数(本例中为WriteMessage)为null时,null条件运算符(?.)会短路,这意味着不会尝试记录消息。
不会在System.Delegate或System.MulticasDelegate的文档中列出Invoke()方法。编译器将为声明的所有委托类型生成类型安全的Invoke方法。在此示例中,这意味着Invoke只需要一个string参数,并且有一个无效返回类型。
六、事件介绍:
1、事件的简介:
和委托类似,事件时后期绑定机制。实际上,事件时建立在对委托的语言支持之上的。
事件是对象用于(向系统中的所有相关组件)广播已发生的一种方式。任何其他组件都可以订阅事件,并在事件引发时得到通知。
你可能已在某些编程中使用过事件。许多图形系统都具有用于报告用户交互的事件模型。这些事件会报告鼠标移动、按钮点击和类似的交互。这是使用事件最常见情景之一,但并非唯一的情景。
可以定义应针对类引发的事件。使用事件时,需要注意的一点是特定事件可能没有任何注册的对象。必须编写代码,以确保在未配置侦听器时不会引发事件。
通过订阅事件,还可在两个对象(事件源和事件接收器)之间创建耦合。需要确保当不再对事件感兴趣时,事件接收器将从事件源取消订阅。
2、事件支持的设计目标
事件的语言设计针对这些目标:
- 在事件源和事件接收器之间启用非常小的耦合。这两个组件可能不会由同一个组织编写,甚至可能会通过完全不同的计划进行更新。
- 订阅事件并从同一事件取消订阅应该非常简单。
- 事件源应支持多个事件订阅服务器。它还应支持不附加任何事件订阅服务器。
3、事件的语言支持:
用于定义事件以及订阅或取消订阅事件的语法是对委托语法的扩展。
定义使用event关键字的事件:
public event EventHandler<FileListArgs> Progress;
该事件(在此示例中,为EventHandler<FileListArgs>)的类型必须为委托类型。声明事件时,应遵循许多约定。通常情况下,事件委托类型具有无效的返回。事件声明应为谓词或谓词短语。当事件报告已发生的事情时,请使用过去时。使用现在时谓词(例如Closing)报告将要发生的事情。通常,使用现在时表示类支持某种类型的自定义行为。最常见的方案之一是支持取消。例如,Closing事件可能包括指示是否应继续执行关闭操作的参数。其他方案可能会允许调用方通过更新事件参数的属性来修改行为。你可以引发一个事件以指示算法将采取的建议的下一步操作。事件处理程序可以通过修改事件参数的属性授权不同的操作。
想要引发事件时,使用委托调用语法调用事件处理程序:
Progress?.Invoke(this, new FileListArgs(file));
如委托部分中所介绍的那样,?.运算符可以轻松确保在事件没有订阅服务器时不引发事件。
通过使用+=运算符订阅事件:
EventHandler<FileListArgs> onProgress = (sender, eventArgs) =>
Console.WriteLine(eventArgs.FoundFile);
fileLister.Progress += onProgress;
处理程序方法通常前缀”On“,后跟事件名称,如上所示。
使用-=运算符取消订阅:
fileLister.Progress -= onProgress;
请务必为表示事件处理程序的表达式声明局部变量。 这将确保取消订阅删除该处理程序。 如果使用的是 lambda 表达式的主体,则将尝试删除从未附加过的处理程序,此操作为无效操作
七、事件和委托的区分:
对不熟悉 .NET Core 平台的开发人员而言,在基于 delegates
的设计和基于 events
的设计之间做出选择是困难的。 委托或事件的选择通常比较难,因为这两种语言功能很相似。 事件甚至是使用委托的语言支持构建的。
它们都提供了一个后期绑定方案:在该方案中,组件通过调用仅在运行时识别的方法进行通信。 它们都支持单个和多个订阅服务器方法。 这称为单播和多播支持。 二者均支持用于添加和删除处理程序的类似语法。 最后,引发事件和调用委托使用完全相同的方法调用语法。 它们甚至都支持与 ?.
运算符一起使用的相同的 Invoke()
方法语法。
鉴于所有这些相似之处,很难确定何时使用何种语法。
侦听事件是可选的
在确定要使用的语言功能时,最重要的考虑因素为是否必须具有附加的订阅服务器。 如果代码必须调用订阅服务器提供的代码,则在需要实现回调时,应使用基于委托的设计。 如果你的代码在不调用任何订阅服务器的情况下可完成其所有工作,则应使用基于事件的设计。
请考虑本部分中生成的示例。 必须为使用 List.Sort()
生成的代码提供 comparer 函数,以便对元素进行正确排序。 必须与委托一起提供 LINQ 查询,以便确定要返回的元素。 二者均使用与委托一起生成的设计。
请考虑 Progress
事件。 它会报告任务进度。 无论是否具有侦听器,该任务将继续进行。 FileSearcher
是另一个示例。 即使没有附加事件订阅服务器,它仍将搜索和查找已找到的所有文件。 即使没有任何订阅服务器侦听事件,UX 控件仍正常工作。 它们都使用基于事件的设计。
返回值需要委托
另一个注意事项是委托方法所需的方法原型。 如你所见,用于事件的委托均具有无效的返回类型。 你还看到,存在创建事件处理程序的惯用语,该事件处理程序通过修改事件参数对象的属性将信息传回到事件源。 虽然这些惯用语可发挥作用,但它们不像从方法返回值那样自然。
请注意,这两种试探法可能经常同时存在:如果委托方法返回值,则可能会以某种方式影响算法。
事件具有专用调用
包含事件的类以外的类只能添加和删除事件侦听器;只有包含事件的类才能调用事件。 事件通常是公共类成员。 相比之下,委托通常作为参数传递,并存储为私有类成员(如果它们全部存储)。
事件侦听器通常具有较长的生存期
事件侦听器通常具有较长的生存期的这一理由不太充分。 但是,你可能会发现,当事件源将在很长一段时间内引发事件时,基于事件的设计会更加自然。 可以在许多系统上看到基于事件的 UX 控件设计示例。 订阅事件后,事件源可能会在程序的整个生存期内引发事件。 (当不再需要事件时,可以取消订阅事件。)
将其与许多基于委托的设计(其中委托用作方法的参数,且在返回该方法后不再使用此委托)进行比较。
仔细评估
以上考虑因素并非固定不变的规则。 相反,它们代表可帮助决定针对特定使用情况的最佳选择的指南。 因为两者类似,所以甚至可以将两者作为原型,并考虑使用更加自然的一种。 两者均能很好地处理后期绑定方案。 使用能与设计进行最佳通讯的一种。