委托

本文介绍 .NET 中支持委托的类以及这些类映射到 delegate 关键字的方式。
一.定义委托类型
我们从“delegate”关键字开始,因为这是你在使用委托时会使用的主要方法。 编译器在你使用 delegate 关键字时生成的代码会映射到调用 Delegate 和 MulticastDelegate 类的成员的方法调用。
可使用类似于定义方法签名的语法来定义委托类型。 只需向定义添加 delegate 关键字即可。
我们继续使用 List.Sort() 方法作为示例。 第一步是为比较委托创建类型:
// From the .NET Core library 
// Define the delegate type: 
public delegate int Comparison<in T>(T left, T right);
二.声明委托的实例 
定义委托之后,可以创建该类型的实例。 与 C# 中的所有变量一样,不能直接在命名空间中或全局命名空间中声明委托实例。
// inside a class definition: 
// Declare an instance of that type:
public Comparison<T> comparator;
变量的类型是 Comparison<T> (前面定义的委托类型)。 变量的名称是 comparator 。
上面的代码片段在类中声明了一个成员变量。 还可以声明作为局部变量或方法参数的委托变量。
三.调用委托 
可通过调用某个委托来调用处于该委托调用列表中的方法。 在 Sort() 方法内部,代码会调用比较方法以确定
放置对象的顺序: 
int result = comparator(left, right);
四.分配、添加和删除调用目标
这是委托类型的定义方式,以及声明和调用委托实例的方式。
要使用 List.Sort() 方法的开发人员需要定义签名与委托类型定义匹配的方法,并将它分配给排序方法使用的
委托。 此分配会将方法添加到该委托对象的调用列表。
假设要按长度对字符串列表进行排序。 比较函数可能如下所示:
private static int CompareLength(string left, string right) 
=> left.Length.CompareTo(right.Length);
通过将该方法传递给 List.Sort() 方法来创建该关系: 
phrases.Sort(CompareLength);
还可以通过声明“ Comparison<string> ”类型的变量并进行分配来显式执行操作: 
Comparison<string> comparer = CompareLength; 
phrases.Sort(comparer);
在用作委托目标的方法是小型方法的用法中,经常使用 lambda 表达式语法来执行分配:
Comparison<string> comparer = (left, right) => left.Length.CompareTo(right.Length); 
phrases.Sort(comparer);
五.委托和 MulticastDelegate 类
上面介绍的语言支持可提供在使用委托时通常需要的功能和支持。 这些功能采用 .NET CoreFramework 中的两
个类进行构建:Delegate 和 MulticastDelegate。
System.Delegate 类及其单个直接子类 System.MulticastDelegate 可提供框架支持,以便创建委托、将方法注册为委托目标以及调用注册为委托目标的所有方法。有趣的是, System.Delegate 和 System.MulticastDelegate 类本身不是委托类型。 它们为所有特定委托类型提供基础。
相同的语言设计过程要求不能声明派生自 Delegate 或 MulticastDelegate 的类。 C# 语言规则禁止这样做。
相反,C# 编译器会在你使用 C# 语言关键字声明委托类型时,创建派生自 MulticastDelegate 的类的实例。 
六.强类型委托
抽象的 Delegate 类提供用于松散耦合和调用的基础结构。 通过包含和实施添加到委托对象的调用列表的方法的类型安全性,具体的委托类型将变得更加有用。 使用 delegate 关键字并定义具体的委托类型时,编译器将生成这些方法。
实际上,无论何时需要不同的方法签名,这都会创建新的委托类型。 一段时间后此操作可能变得繁琐。 每个新功能都需要新的委托类型。幸运的是,没有必要这样做。 .NET Core 框架包含几个在需要委托类型时可重用的类型。 这些是泛型定义,因此需要新的方法声明时可以声明自定义。
第一个类型是 Action 类型和一些变体:
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.
Action 委托的变体可包含多达 16 个参数,如
Action<T1,T2,T3,T4,T5,T6,T7,T8,T9,T10,T11,T12,T13,T14,T15,T16>。 重要的是这些定义对每个委托参数使用不同
的泛型参数:这样可以具有最大的灵活性。 方法参数不需要但可能是相同的类型。
对任何具有 void 返回类型的委托类型使用一种 Action 类型。
此框架还包括几种可用于返回值的委托类型的泛型委托类型: 
public delegate TResult Func<out TResult>(); 
public delegate TResult Func<in T1, out TResult>(T1 arg); 
public delegate TResult Func<in T1, in T2, out TResult>(T1 arg1, T2 arg2); 
// Other variations removed for brevity
Func 委托的变体可包含多达 16 个输入参数,如
Func<T1,T2,T3,T4,T5,T6,T7,T8,T9,T10,T11,T12,T13,T14,T15,T16,TResult>。 按照约定,结果的类型始终是所有
Func 声明中的最后一个类型参数。
对任何返回值的委托类型使用一种 Func 类型。
还有一种专门的 Predicate<T> 委托类型,可返回单个值的测试结果
public delegate bool Predicate<in T>(T obj);
你可能会注意到对于任何 Predicate 类型,均存在一个在结构上等效的 Func 类型,例如:
Func<string, bool> TestForString; 
Predicate<string> AnotherTestForString;

七.委托的常⻅模式 

委托提供了一种机制,可实现涉及组件间最小耦合度的软件设计。
此类设计的出色示例为 LINQ。 LINQ 查询表达式模式依赖于其所有功能的委托。 请考虑此简单示例: 
var smallNumbers = numbers.Where(n => n < 10);
 Where 方法的原型是: 
 
 public static IEnumerable<TSource> Where<TSource> (this IEnumerable<TSource> source, Func<TSource, bool>
  predicate); 
 
八.通过委托生成自己的组件
基于此示例,通过使用依赖于委托的设计来创建组件,从而进行生成。
定义一个可用于大型系统中日志消息的组件。 库组件可以在多种不同的环境中和多个不同的平台上使用。 管理日志的组件中有很多常用功能。 它需要接受来自系统中任何组件的消息。 这些消息将具有不同的优先级(核心组件可进行管理)。 消息应当具有其最终存档形式的时间戳。 对于更高级的方案,你可以按源组件筛选消息。此功能有一个方面会经常发生变化:写入消息的位置。 在某些环境中,它们可能会写入到错误控制台。 在其他环境中,可能会写入一个文件。 其他可能性包括数据库存储、操作系统事件日志或其他文档存储。
还有可能用于不同方案的输出组合。 建议你将消息写入控制台和文件。
基于委托的设计将提供极大的灵活性,从而轻松支持可能在以后添加的存储机制。
基于此设计,主日志组件可以是非虚拟,甚至是密封的类。 你可以插入任何委托集,将消息写入不同的存储介
质。
对多播委托的内置支持有助于支持必须将消息写入多个位置(文件和控制台)的情况。 
public static class Logger { 
public static Action<string> WriteMessage;
public static void LogMessage(string msg) 
 { WriteMessage(msg); } 
}
上面的静态类是可以发挥作用的最简单的类。 我们需要编写将消息写入控制台的方法的单个实现: 
public static class LoggingMethods { public static void LogToConsole(string message) { Console.Error.WriteLine(message); } }
 最后,你需要通过将委托附加到记录器中声明的 WriteMessage 委托来进行挂钩: 
 Logger.WriteMessage += LoggingMethods.LogToConsole; 
九.设置输出格式
让第一个版本更加可靠,然后开始创建其他日志记录机制。
然后,向 LogMessage() 方法添加一些参数,以便日志类创建更多结构化消息:
public enum Severity 
{ Verbose, Trace, Information, Warning, 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}";
    WriteMessage(outputMsg);
  }
}

 接下来,使用 Severity 参数来筛选发送到日志输出的消息。 
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}";
WriteMessage(outputMsg);
}}

十.事件委托签名 

 

.NET 事件委托的标准签名是:
void OnEventRaised(object sender, EventArgs args);
下面是找到查找的文件时的初始事件参数声明:
public class FileFoundArgs : EventArgs 
{ 
public string FoundFile { get; } 
public FileFoundArgs(string fileName) 
{ FoundFile = fileName; } 
}
让我们通过填充 FileSearcher 类来搜索与模式匹配的文件,并在发现匹配时引发正确的事件。
public class FileSearcher { 

public event EventHandler<FileFoundArgs> FileFound;

public void Search(string directory, string searchPattern)
{
foreach (var file in Directory.EnumerateFiles(directory, searchPattern))
{ FileFound?.Invoke(this, new FileFoundArgs(file));
}
}
}
定义并引发类似字段的事件
要将事件添加到类,最简单的方式是将该事件声明为公共字段,如上面的示例中所示: 
public event EventHandler<FileFoundArgs> FileFound;
看起来它像在声明一个公共字段,这似乎是一个面向对象的不良实践。 你希望通过属性或方法来保护数据访问。
虽然这可能看起来是糟糕的做法,但编译器生成的代码确实会创建包装器,以便事件对象只能通过安全的方式进
行访问。 类似字段的事件上唯一可用的操作是添加处理程序: 
EventHandler<FileFoundArgs> onFileFound = (sender, eventArgs)
=> {
Console.WriteLine(eventArgs.FoundFile
);
filesFound++;
};
fileLister.FileFound += onFileFound;
和删除处理程序: 
fileLister.FileFound -= onFileFound;
异步事件订阅者
worker.StartWorking += async (sender, eventArgs) => { try { await DoWorkAsync(); }

catch (Exception e) {

//Some form of logging. Console.WriteLine($"Async task failure: {e.ToString()}"); // Consider gracefully, and quickly exiting.
}
};

 

 

 

posted @ 2022-04-08 09:31  Tammytan  阅读(104)  评论(0编辑  收藏  举报