C#中的代表和事件

本文的翻译是专门为“ C#Developer”课程的学生准备的




C#中的事件是什么?


事件可用于提供通知。 如果需要这些通知,可以订阅该事件。 您还可以创建自己的事件,这些事件将通知您发生了您感兴趣的事情。 .NET Framework提供了可用于创建事件的内置类型。 使用委托,lambda表达式和匿名方法,您可以方便地创建和使用事件。

了解C#中的代表


在C#中,委托构成事件的基本构建块。 委托是一种定义方法签名的类型。 例如,在C ++中,可以使用函数指针完成此操作。 在C#中,您可以创建一个指向另一个方法的委托实例。 您可以通过委托实例调用此方法。

下面是声明委托并通过委托调用方法的示例。

在C#中使用委托


}class Program { public delegate double MathDelegate(double value1, double value2); public static double Add(double value1, double value2) { return value1 + value2; } public static double Subtract(double value1, double value2) { return value1 - value2; } Console.ReadLine(); public static void Main() { MathDelegate mathDelegate = Add; var result = mathDelegate(5, 2); Console.WriteLine(result); // : 7 mathDelegate = Subtract; result = mathDelegate(5, 2); Console.WriteLine(result); // : 3 } 

如您所见,我们使用委托关键字告诉编译器我们正在创建委托类型。

实例化代表以及自动创建新的代表类型很容易。

您还可以使用new关键字创建委托。

MathDelegate mathDelegate = new MathDelegate(Add) ;

实例化的委托是一个对象; 您也可以使用它并将其作为参数传递给其他方法。

C#中的多播代表


代表的另一个重要功能是可以将它们组合在一起。 这称为多播。 您可以使用+或+ =运算符将另一个方法添加到现有委托实例的调用列表中。 同样,您也可以使用减量分配运算符(-或-=)从调用列表中删除方法。 此功能用作C#中事件的基础。 以下是多播委托的示例。

 class Program { static void Hello(string s) { Console.WriteLine(" Hello, {0}!", s); } static void Goodbye(string s) { Console.WriteLine(" Goodbye, {0}!", s); } delegate void Del(string s); static void Main() { Del a, b, c, d; //   a    Hello: a = Hello; //   b    Goodbye: b = Goodbye; //    a  b - c: c = a + b; //  a    c,   d,       Goodbye: d = c - a; Console.WriteLine("Invoking delegate a:"); a("A"); Console.WriteLine("Invoking delegate b:"); b("B"); Console.WriteLine("Invoking delegate c:"); c("C"); Console.WriteLine("Invoking delegate d:"); d("D"); /* : Invoking delegate a: Hello, A! Invoking delegate b: Goodbye, B! Invoking delegate c: Hello, C! Goodbye, C! Invoking delegate d: Goodbye, D! */ Console.ReadLine(); } } 

这是可能的,因为委托从System.MulticastDelegate类继承,而System.MulticastDelegate类又从System.Delegate继承。 您可以将这些基类中定义的成员用于您的委托。

例如,要找出多播委托将调用多少种方法,可以使用以下代码:

 int invocationCount = d.GetInvocationList().GetLength(0); 

C#中的协方差和协方差


当您将方法分配给委托时,方法签名不必与委托完全匹配。 这称为协方差和逆方差。 协方差允许一种方法具有比委托中定义的返回类型更多的派生返回类型。 协方差允许具有比委托中的类型更少的参数类型的方法。

委托协方差


这是协方差的一个例子,

 class Program { public delegate TextWriter CovarianceDel(); public static StreamWriter MethodStream() { return null; } public static StringWriter MethodString() { return null; } static void Main() { CovarianceDel del; del = MethodStream; del = MethodString; Console.ReadLine(); } } 

由于StreamWriterStringWriter都继承自TextWriter ,因此您可以同时使用CovarianceDel和这两种方法。

代表的矛盾


以下是一个逆方差示例。

 class Program { public static void DoSomething(TextWriter textWriter) { } public delegate void ContravarianceDel(StreamWriter streamWriter); static void Main() { ContravarianceDel del = DoSomething; Console.ReadLine(); } } 

由于DoSomething方法可以与TextWriter ,因此它当然可以与StreamWriter 。 由于矛盾,您可以调用委托并将StreamWriter的实例传递给DoSomething方法。

您可以在此处了解有关此概念的更多信息。

C#中的Lambda表达式


有时,整个方法签名可能需要比方法本身更多的代码。 在某些情况下,您需要创建整个方法才能将其用作委托。

对于这些情况,Microsoft在C#中添加了一些新功能,例如2.0中的匿名方法。 在C#3.0中,添加了lambda表达式后,情况会更好。 Lambda表达式是编写新代码时的首选方式。

以下是最新的lambda语法的示例。

 class Program { public delegate double MathDelegate(double value1, double value2); public static void Main() { MathDelegate mathDelegate = (x,y) => x + y; var result = mathDelegate(5, 2); Console.WriteLine(result); // : 7 mathDelegate = (x, y) => x - y; ; result = mathDelegate(5, 2); Console.WriteLine(result); // : 3 Console.ReadLine(); } } 

要阅读此代码,需要在特殊的lambda语法的上下文中使用“跟随”一词。 例如,以上示例中的第一个lambda表达式显示为“ x和y跟随x和y的相加”。

与方法不同,lambda函数没有特定名称。 因此,lambda被称为匿名函数。 您也不需要显式指定返回值的类型。 编译器会从您的lambda中自动假定它。 并且在上述示例的情况下,也未明确指定参数类型x和y。

您可以创建跨多个运算符的lambda。 您可以通过在组成lambda的语句周围添加花括号来做到这一点,如下例所示。

 MathDelegate mathDelegate = (x,y) => { Console.WriteLine("Add"); return x + y; }; 

有时某个事件的代表宣布似乎有些麻烦。 因此,.NET Framework具有几种内置的委托类型,您可以在声明委托时使用它们。 在MathDelegate示例中,您使用了以下委托:

 public delegate double MathDelegate(double value1, double value2); 

您可以使用内置类型之一,即Func <int, int, int>替换此委托。

像那样

 class Program { public static void Main() { Func<int, int, int> mathDelegate = (x,y) => { Console.WriteLine("Add"); return x + y; }; var result = mathDelegate(5, 2); Console.WriteLine(result); // : 7 mathDelegate = (x, y) => x - y; ; result = mathDelegate(5, 2); Console.WriteLine(result); // : 3 Console.ReadLine(); } } 

在系统名称空间中可以找到Func <...>类型。 它们代表返回类型并接受0到16个参数的委托。 所有这些类型都是从System.MulticaseDelegate继承的,因此您可以将多个方法添加到调用列表中。

如果需要不返回值的委托类型,则可以使用System.Action类型。 它们也可以采用0到16个参数,但不返回值。

这是使用动作类型的示例,

 class Program { public static void Main() { Action<int, int> mathDelegate = (x,y) => { Console.WriteLine(x + y); }; mathDelegate(5, 2); // : 7 mathDelegate = (x, y) => Console.WriteLine(x - y) ; mathDelegate(5, 2); // : 3 Console.ReadLine(); } } 

您可以在此处了解有关.NET内置委托的更多信息。

当您的lambda函数开始引用在lambda表达式之外声明的变量或对此进行引用时,事情变得复杂起来。 通常,当控件离开变量的范围时,该变量将变为无效。 但是,如果委托引用了局部变量该怎么办。 为了解决这个问题,编译器生成的代码将延长捕获的变量的寿命,至少可以延长寿命最长的代理的寿命。 这称为关闭。

您可以在此处了解有关闭包的更多信息。

C#中的事件


考虑一种流行的开发模式-发布者-订阅者(pub / sub)。 您可以订阅事件,然后在事件发布者发起新事件时收到通知。 此系统用于在应用程序的组件之间建立弱通信。

委托构成了C#中事件系统的基础。

事件是一种特殊类型的委托,可促进面向事件的编程。 事件是类的成员,无论访问说明符如何,事件都不能在类外部调用。 因此,例如,声明为public的事件将允许其他类对该事件使用+ =和-=,但是仅在包含该事件的类中才允许触发事件(即,调用委托)。 我们来看一个例子

 // -  Pub public class Pub { // OnChange    callback-  public event Action OnChange = delegate { }; public void Raise() { // OnChange OnChange(); } } 

然后,另一个类中的方法可以通过将其方法之一添加到事件委托中来订阅事件:

以下是显示类如何提供开放委托并生成事件的示例。

 class Program { static void Main(string[] args) { //   pub Pub p = new Pub(); //  Subscriber 1   OnChange p.OnChange += () => Console.WriteLine("Subscriber 1!"); //  Subscriber 2   OnChange p.OnChange += () => Console.WriteLine("Subscriber 2!"); //  p.Raise(); //   Raise()   callback-     Console.WriteLine("Press enter to terminate!"); Console.ReadLine(); } } 

即使事件被声明为public ,也不能在事件所在的类中的任何地方直接触发它。

使用event关键字,编译器可以保护我们的字段免受不必要的访问。

还有

它不允许使用=(直接委托分配)。 因此,通过使用=而不是+ =,现在可以保护您的代码免受删除以前的订阅者的风险。

此外,您可能会注意到空委托(例如, delegate { }的特殊OnChange字段初始化语法。 这样可以确保我们的OnChange字段永远不会为null。 因此,如果没有其他类成员将其取消为null,则可以在调用该事件之前将其取消。

当您运行上述程序时,您的代码将创建一个Pub的新实例,以两种不同的方式订阅该事件,并通过调用p.Raise生成一个事件。 Pub类完全不知道任何订阅者。 它只是生成一个事件。

您也可以阅读我的文章《 C#发布者-订阅者设计模式》,以更深入地了解这个概念。

好,仅此而已。 希望您能明白。 感谢您阅读这篇文章。 请在下面的评论中告诉我是否有任何错误或更改。 预先感谢!

有用的链接


www.c-sharpcorner.com/blogs/c-sharp-generic-delegates-func-action-and-predicate
docs.microsoft.com/zh-CN/dotnet/csharp/programming-guide/concepts/covariance-contravariance
https://web.archive.org/web/20150707082707/http://diditwith.net/PermaLink,guid,235646ae-3476-4893-899d-105e4d48c25b.aspx

Source: https://habr.com/ru/post/zh-CN472094/


All Articles