理解c#中的事件和事件处理程序

我理解事件的目的,特别是在创建用户界面的上下文中。我认为这是创建事件的原型:

public void EventName(object sender, EventArgs e);

事件处理程序做什么,为什么需要它们,以及如何创建一个?

427098 次浏览

这实际上是一个事件处理程序的声明——一个在触发事件时被调用的方法。要创建一个事件,你可以这样写:

public class Foo
{
public event EventHandler MyEvent;
}

然后你可以像这样订阅这个事件:

Foo foo = new Foo();
foo.MyEvent += new EventHandler(this.OnMyEvent);

OnMyEvent()的定义如下:

private void OnMyEvent(object sender, EventArgs e)
{
MessageBox.Show("MyEvent fired!");
}

Foo触发MyEvent时,将调用你的OnMyEvent处理程序。

你不必总是使用EventArgs的实例作为第二个参数。如果你想包含额外的信息,你可以使用一个派生自EventArgs的类(按照惯例EventArgs是基类)。例如,如果你查看WinForms中定义在Control或WPF中定义在FrameworkElement上的一些事件,你可以看到将额外信息传递给事件处理程序的事件示例。

要理解事件处理程序,你需要理解代表。在c#中,可以将委托看作指向方法的指针(或引用)。这很有用,因为指针可以作为值传递。

委托的核心概念是它的签名或形状。即(1)返回类型和(2)输入参数。例如,如果我们创建委托void MyDelegate(object sender, EventArgs e),它只能指向返回void的方法,并接受objectEventArgs。有点像一个方洞和一个方钉子。因此,我们说这些方法与委托具有相同的签名或形状。

在了解了如何创建对方法的引用之后,让我们考虑一下事件的目的:当系统中其他地方发生某些事情时,我们想要导致一些代码被执行——或者“处理事件”。为此,我们为想要执行的代码创建特定的方法。事件和要执行的方法之间的粘合剂是委托。事件必须在内部存储一个指针“列表”,这些指针指向在引发事件时要调用的方法。*当然,为了能够调用一个方法,我们需要知道传递给它什么参数!我们使用委托作为事件和将被调用的所有特定方法之间的“契约”。

所以默认的EventHandler(和许多类似的)表示一个方法的具体形态(同样是void/object-EventArgs)。当你声明一个事件时,你说的是该事件将调用的哪种形式的方法 (EventHandler),通过指定一个委托:

//This delegate can be used to point to methods
//which return void and take a string.
public delegate void MyEventHandler(string foo);


//This event can cause any method which conforms
//to MyEventHandler to be called.
public event MyEventHandler SomethingHappened;


//Here is some code I want to be executed
//when SomethingHappened fires.
void HandleSomethingHappened(string foo)
{
//Do some stuff
}


//I am creating a delegate (pointer) to HandleSomethingHappened
//and adding it to SomethingHappened's list of "Event Handlers".
myObj.SomethingHappened += new MyEventHandler(HandleSomethingHappened);


//To raise the event within a method.
SomethingHappened("bar");

(*这是。net中事件的关键,它揭开了“魔法”的面纱——一个事件实际上只是一组具有相同“形状”的方法。列表存储在事件所在的位置。当事件被“引发”时,它实际上只是“遍历这个方法列表并调用每个方法,使用这些值作为参数”。分配事件处理程序只是将您的方法添加到要调用的方法列表的一种更漂亮、更简单的方式)。

c#中有两个术语,delegateevent。让我们从第一个开始。

委托

delegate是一个方法的引用。就像你可以创建一个实例的引用:

MyClass instance = myFactory.GetInstance();

你可以使用委托来创建一个方法的引用:

Action myMethod = myFactory.GetInstance;

现在你有了一个方法的引用,你可以通过引用调用这个方法:

MyClass instance = myMethod();

但你为什么要这么做?你也可以直接调用myFactory.GetInstance()。在这种情况下你可以。然而,在许多情况下,你不希望应用程序的其余部分了解myFactory或直接调用myFactory.GetInstance()

一个明显的例子是,如果你希望能够从一个中心位置(也就是工厂方法模式)将myFactory.GetInstance()替换为myOfflineFakeFactory.GetInstance()

工厂方法模式

所以,如果你有一个TheOtherClass类,并且它需要使用myFactory.GetInstance(),这是没有委托的代码的样子(你需要让TheOtherClass知道你的myFactory的类型):

TheOtherClass toc;
//...
toc.SetFactory(myFactory);




class TheOtherClass
{
public void SetFactory(MyFactory factory)
{
// set here
}


}

如果你使用委托,你不需要暴露我的工厂的类型:

TheOtherClass toc;
//...
Action factoryMethod = myFactory.GetInstance;
toc.SetFactoryMethod(factoryMethod);




class TheOtherClass
{
public void SetFactoryMethod(Action factoryMethod)
{
// set here
}


}

因此,您可以将委托交给其他类使用,而无需向它们暴露您的类型。你唯一要暴露的是你的方法的签名(你有多少参数等等)。

“我的方法签名”,我以前在哪儿听过?哦,是的,接口!!接口描述了整个类的签名。把委托看作是只描述一个方法的签名!

接口和委托之间的另一个很大的区别是,当你编写类时,你不必对c#说“这个方法实现了那种类型的委托”。对于接口,你确实需要说“这个类实现了那种类型的接口”。

此外,一个委托引用可以(有一些限制,见下文)引用多个方法(称为MulticastDelegate)。这意味着当您调用委托时,将执行多个显式附加的方法。一个对象引用总是只能引用一个对象。

MulticastDelegate的限制是(方法/委托)签名不应该有任何返回值(void),并且关键字outref没有在签名中使用。显然,您不能调用两个返回一个数字的方法,并期望它们返回相同的数字。一旦签名符合要求,委托就自动成为MulticastDelegate

事件

事件只是属性(就像get;set;属性到实例字段),这将从其他对象向委托公开订阅。但是,这些属性不支持get;set;。相反,它们支持add;删除;

所以你可以有:

    Action myField;


public event Action MyProperty
{
add { myField += value; }
remove { myField -= value; }
}

在UI中的使用(WinForms,WPF,UWP等等)

所以,现在我们知道委托是一个方法的引用,我们可以有一个事件来让世界知道他们可以给我们他们的方法来从我们的委托中引用,我们是一个UI按钮,然后:我们可以让任何对我是否被点击感兴趣的人向我们注册他们的方法(通过我们暴露的事件)。我们可以使用所有给我们的方法并由委托引用它们。然后,我们就等啊等....直到用户来点击那个按钮,我们才有足够的理由调用委托。因为委托引用了所有给我们的方法,所有这些方法都会被调用。我们不知道这些方法做什么,也不知道哪个类实现了这些方法。我们所关心的只是有人对我们被点击感兴趣,并给了我们一个方法的引用,这个方法符合我们想要的签名。

Java

像Java这样的语言没有委托。相反,他们使用接口。他们这样做的方式是让任何对“我们被点击”感兴趣的人来实现某个接口(使用我们可以调用的某个方法),然后给我们实现该接口的整个实例。我们保留了实现该接口的所有对象的列表,并在我们被单击时调用它们的“我们可以调用的特定方法”。

下面是一个代码示例,可能会有所帮助:

using System;
using System.Collections.Generic;
using System.Text;


namespace Event_Example
{
// First we have to define a delegate that acts as a signature for the
// function that is ultimately called when the event is triggered.
// You will notice that the second parameter is of MyEventArgs type.
// This object will contain information about the triggered event.


public delegate void MyEventHandler(object source, MyEventArgs e);


// This is a class which describes the event to the class that receives it.
// An EventArgs class must always derive from System.EventArgs.


public class MyEventArgs : EventArgs
{
private string EventInfo;


public MyEventArgs(string Text) {
EventInfo = Text;
}


public string GetInfo() {
return EventInfo;
}
}


// This next class is the one which contains an event and triggers it
// once an action is performed. For example, lets trigger this event
// once a variable is incremented over a particular value. Notice the
// event uses the MyEventHandler delegate to create a signature
// for the called function.


public class MyClass
{
public event MyEventHandler OnMaximum;


private int i;
private int Maximum = 10;


public int MyValue
{
get { return i; }
set
{
if(value <= Maximum) {
i = value;
}
else
{
// To make sure we only trigger the event if a handler is present
// we check the event to make sure it's not null.
if(OnMaximum != null) {
OnMaximum(this, new MyEventArgs("You've entered " +
value.ToString() +
", but the maximum is " +
Maximum.ToString()));
}
}
}
}
}


class Program
{
// This is the actual method that will be assigned to the event handler
// within the above class. This is where we perform an action once the
// event has been triggered.


static void MaximumReached(object source, MyEventArgs e) {
Console.WriteLine(e.GetInfo());
}


static void Main(string[] args) {
// Now lets test the event contained in the above class.
MyClass MyObject = new MyClass();
MyObject.OnMaximum += new MyEventHandler(MaximumReached);
for(int x = 0; x <= 15; x++) {
MyObject.MyValue = x;
}
Console.ReadLine();
}
}
}

只是在这里添加现有的伟大答案-在已接受的代码的基础上构建,它使用delegate void MyEventHandler(string foo)

因为编译器知道SomethingHappened事件的委托类型,所以:

myObj.SomethingHappened += HandleSomethingHappened;

完全等价于:

myObj.SomethingHappened += new MyEventHandler(HandleSomethingHappened);

处理程序也可以是未注册的-=,如下所示:

// -= removes the handler from the event's list of "listeners":
myObj.SomethingHappened -= HandleSomethingHappened;

为了完整起见,只能在拥有该事件的类中,像这样触发事件:

//Firing the event is done by simply providing the arguments to the event:
var handler = SomethingHappened; // thread-local copy of the event
if (handler != null) // the event is null if there are no listeners!
{
handler("Hi there!");
}

处理程序的线程本地副本是必要的,以确保调用是线程安全的——否则,线程可以在检查它是否为null后立即注销事件的最后一个处理程序,我们将在那里有一个“有趣的”NullReferenceException


c# 6为这个模式引入了一个很好的简写。它使用空传播操作符。

SomethingHappened?.Invoke("Hi there!");

我对这些事件的理解是;

委托:

保存要执行的方法/方法的引用的变量。这使得像传递变量一样传递方法成为可能。

创建和调用事件的步骤:

  1. 事件是委托的实例

  2. 由于事件是委托的实例,因此我们必须首先定义委托。

  3. 指定在事件触发时执行的方法/方法(调用委托)

  4. 触发事件(调用委托)

例子:

using System;


namespace test{
class MyTestApp{
//The Event Handler declaration
public delegate void EventHandler();


//The Event declaration
public event EventHandler MyHandler;


//The method to call
public void Hello(){
Console.WriteLine("Hello World of events!");
}


public static void Main(){
MyTestApp TestApp = new MyTestApp();


//Assign the method to be called when the event is fired
TestApp.MyHandler = new EventHandler(TestApp.Hello);


//Firing the event
if (TestApp.MyHandler != null){
TestApp.MyHandler();
}
}


}


}

我同意KE50的观点,除了我认为'event'关键字是'ActionCollection'的别名,因为事件包含了要执行的操作的集合(例如。委托)。

using System;


namespace test{


class MyTestApp{
//The Event Handler declaration
public delegate void EventAction();


//The Event Action Collection
//Equivalent to
//  public List<EventAction> EventActions=new List<EventAction>();
//
public event EventAction EventActions;


//An Action
public void Hello(){
Console.WriteLine("Hello World of events!");
}
//Another Action
public void Goodbye(){
Console.WriteLine("Goodbye Cruel World of events!");
}


public static void Main(){
MyTestApp TestApp = new MyTestApp();


//Add actions to the collection
TestApp.EventActions += TestApp.Hello;
TestApp.EventActions += TestApp.Goodbye;


//Invoke all event actions
if (TestApp.EventActions!= null){
//this peculiar syntax hides the invoke
TestApp.EventActions();
//using the 'ActionCollection' idea:
// foreach(EventAction action in TestApp.EventActions)
//     action.Invoke();
}
}


}


}
//This delegate can be used to point to methods
//which return void and take a string.
public delegate void MyDelegate(string foo);


//This event can cause any method which conforms
//to MyEventHandler to be called.
public event MyDelegate MyEvent;


//Here is some code I want to be executed
//when SomethingHappened fires.
void MyEventHandler(string foo)
{
//Do some stuff
}


//I am creating a delegate (pointer) to HandleSomethingHappened
//and adding it to SomethingHappened's list of "Event Handlers".
myObj.MyEvent += new MyDelegate (MyEventHandler);

发布者:事件发生的地方。发布者应该指定类使用哪个委托,并生成必要的参数,将这些参数和自身传递给委托。

订阅者:响应发生的地方。订阅者应指定响应事件的方法。这些方法应该采用与委托相同类型的参数。订阅者然后将此方法添加到发布者的委托。

因此,当事件在publisher中发生时,delegate将收到一些事件参数(数据等),但publisher不知道所有这些数据将会发生什么。订阅者可以在自己的类中创建方法来响应发布者类中的事件,这样订阅者就可以响应发布者的事件。

文章中有很棒的技术答案!我没有从技术上讲添加到那。

在语言和软件中出现新功能的主要原因之一是市场营销或公司政治!:-)这绝不能被低估!

我认为这也适用于代表和活动!我发现它们很有用,为c#语言增加了价值,但另一方面,Java语言决定不使用它们!他们认为无论你用委托解决什么问题,你都可以用语言的现有特性来解决,比如接口。

2001年左右,微软发布了。net框架和c#语言,作为Java的竞争解决方案,所以拥有Java没有的新功能是件好事。

我最近做了一个如何在c#中使用事件的例子,并发布在我的博客上。我用了一个简单的例子,尽量把它讲清楚。为了帮助任何人,这里是:http://www.konsfik.com/using-events-in-csharp/

它包括描述和源代码(带有大量注释),主要关注事件和事件处理程序的正确使用(类似模板)。

一些要点是:

  • 事件就像“委托的子类型”,只是更有约束(以一种好的方式)。事实上,事件的声明总是包含一个委托(EventHandlers是一种委托)。

  • 事件处理程序是特定类型的委托(您可以将其视为模板),它强制用户创建具有特定“签名”的事件。签名的格式为:(object sender, EventArgs eventarguments)。

  • 您可以创建自己的EventArgs子类,以便包括事件需要传递的任何类型的信息。在使用事件时,没有必要使用EventHandlers。你可以完全跳过它们,在它们的地方使用你自己的委托。

  • 使用事件和使用委托之间的一个关键区别是,事件只能从声明它们的类中调用,即使它们可以声明为公共的。这是一个非常重要的区别,因为它允许暴露您的事件,以便它们“连接”到外部方法,同时保护它们免受“外部滥用”。

还有一件事要知道,在某些情况下,当你需要耦合程度低 !

如果你想要在应用程序的多个地方使用一个组件,你需要创建一个低耦合级别的组件,并且特定的不关心的逻辑必须委托给组件的 !这确保了您有一个解耦的系统和更清晰的代码。

固体原则中,这是“D”,(D依赖反转原则)。

也称为“国际奥委会”,控制反转

你可以用事件,代表和DI(依赖注入)来创建"国际奥委会"。

在子类中访问方法很容易。但是从子类中访问父类中的方法更加困难。你必须把父引用传递给子引用!(或使用DI接口)

委托/事件允许我们在没有引用的情况下从子对象通信到父对象!

enter image description here

在上面的图中,我不使用委托/事件和父组件A的父组件B 必须要有证明人吗执行方法A中不关心的业务逻辑(高级别耦合)。

使用这种方法,我将不得不放置所有使用组件B的组件的所有引用!:(

enter image description here

在上面的图中,我使用委托/事件和组件B不需要知道a(低级别耦合)

你可以使用你的组件b__abc0 !

委托,事件(事件处理程序/事件监听器),概念(多播/广播),动作&函数

这将是一个很长的问题,但它是最简单的解释,问题是这是一个如此讨厌的话题,因为人们只是用不同的词来解释同一件事

首先,你应该知道一些事情

delegate:它只是一个方法列表,为什么要创建一个列表呢?因为当你的代码被执行时,这个列表会被一个一个地执行每个方法,不要听教科书上的定义使用这个,你就会没事的

也作:

  • 指向函数的指针
  • 方法的包装器,可以像变量一样发送和接收方法

要创建委托

[[access modifier] delegate [return type] [delegate name]([parameters])]


example: public delegate int demo(int a);

现在要执行所有这些存储在一个名为委托的列表中的方法

1. demo.invoke(a);
2. demo(a);   ..... both are valid

在使用beginInvoke的异步编程中,使用点和显式地声明invoke非常出色,但这超出了本主题的范围

还有一个叫做“创建委托对象/实例化委托”;这和它听起来差不多,但为了避免混淆,它是这样的(对于上面的例子)

example : demo del = new demo(); (or) Public demo del = null;

要将任何方法添加到名为delegate的列表中,请执行+=,并且一旦方法的“要求达到”,您还需要删除它。你走-=

(满足方法的要求意味着你不再需要该方法是活跃的或“监听”)如果你不删除它,它可能会导致“内存泄漏”;这意味着你的电脑内存将被活生生地吃掉,技术上分配的内存将不会被释放

例子:说有一个方法

public int calculate (int c)


to add this method to delegate you go


1.  del = calculate;
2.  del += calculate; .... all are valid


to remove


del -= calculate

首先,请注意委托和方法之间的相似之处,返回类型(输出)和输入/参数是相同的,这是一个规则,你不能在委托中添加任何随机或一堆方法,它需要遵循输入-输出规则

为什么一件事有两种不同的方式,唯一不同的是赋值操作符(+,=),这引入了一个新的主题叫做

事件

这只是一个委托的约束版本,它仍然是一个方法列表不要混淆当人们解释这些术语时,他们改变了名字,所以坚持这个来理解

约束条件是什么?你不能这样做del = calculate; 这有什么坏处呢,比如说一堆方法被添加到Delegate(List)中,你这样做👆所有的方法都被删除了,只有一个方法" calculatequot;仍然存在,因此为了防止使用事件, 事件语法< / p >

公共事件演示del = null;

你不能用事件做的另一件事是像demo.invoke那样直接调用委托,因为它是公共的,可以访问和调用,但对于事件,它不能

现在只需将方法添加到事件(一种特殊类型的委托)

什么时候使用事件和委托,取决于你的情况,但实际上事件是流行的

更多关键词

MULTICASTING:只是向委托添加一个以上的方法 广播:在事件

中添加多个方法

PUBLISHER:执行方法(广播中使用的术语)的PUBLISHER,仅为单个实体 订阅者:正在执行的方法可以是多个

LISTENER:与订阅者相同,但这个术语用于多播

EVENT HANDLER:和订阅者/事件监听器一样,有什么区别?这基本上是一样的,有人说事件监听器检测事件发生事件处理程序处理事件;或者执行代码,实际上是一样的!

Action和func只是已经创建和实例化的委托所以一个字里有两行代码,区别就在于返回类型

ACTION:当输入0或1个以上时不返回任何东西

FUNC:返回一个东西并接受参数

如果你不擅长阅读,这里有关于这个话题的最好的视频

https://www.youtube.com/playlist?list=PLFt_AvWsXl0dliMtpZC8Qd_ru26785Ih_