了解C#中的事件和事件处理程序

我理解事件的目的,特别是在创build用户界面的情况下。 我认为这是创造一个事件的原型:

public void EventName(object sender, EventArgs e); 

事件处理程序做了什么,为什么需要它,我该如何创build一个?

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

代表的核心概念是其签名或形状。 这是(1)返回types和(2)input参数。 例如,如果我们创build一个委托void MyDelegate(object sender, EventArgs e) ,它只能指向返回void方法,并且获取一个objectEventArgs 。 有点像一个方孔和一个方形钉。 所以我们说这些方法和委托具有相同的签名或形状。

因此,知道如何创build对方法的引用,让我们考虑一下事件的目的:当系统中某处发生某事时,我们想要引发一些代码 – 或者“处理事件”。 为此,我们为要执行的代码创build特定的方法。 事件和要执行的方法之间的胶水是代表。 事件必须在内部存储一个指向方法的“列表”,当事件被引发时。*当然,为了能够调用一个方法,我们需要知道传递给它的参数。 我们使用委托作为事件与所有将被调用的特定方法之间的“契约”。

所以默认的EventHandler (和许多像它一样)表示方法特定形状 (同样,void / object-EventArgs)。 当你声明一个事件时,通过指定一个委托来说明事件将调用哪种forms的方法 (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); 

(*这是在.NET中事件的关键,剥离了“魔力” – 事件真的只是一个相同的“形状”的方法列表,列表存储在事件发生的地方。事件是“引发的”,它实际上只是“遍历这个方法列表并调用每一个方法,使用这些值作为参数”。分配事件处理程序只是将方法添加到这个方法列表中的更漂亮,更简单的方法被称为)。

C#知道两个术语, delegateevent 。 我们从第一个开始。

代表

delegate是对方法的引用。 就像您可以创build对实例的引用一样:

 MyClass instance = myFactory.GetInstance(); 

您可以使用委托来创build对方法的引用:

 Action myMethod = myFactory.GetInstance; 

现在您已经有了对方法的引用,您可以通过引用调用方法:

 MyClass instance = myMethod(); 

但你为什么? 你也可以直接调用myFactory.GetInstance() 。 在这种情况下,你可以。 但是,有很多情况下可以考虑不希望应用程序的其余部分知道myFactory或直接调用myFactory.GetInstance()

显而易见的是,如果您希望能够从一个中心位置(也称为工厂方法模式 )将myFactory.GetInstance()replace为myOfflineFakeFactory.GetInstance() )。

工厂方法模式

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

 TheOtherClass toc; //... toc.SetFactory(myFactory); class TheOtherClass { public void SetFactory(MyFactory factory) { // set here } } 

如果您使用委托,您不必公开我的工厂的types:

 TheOtherClass toc; //... Action factoryMethod = myFactory.GetInstance; toc.SetFactoryMethod(factoryMethod); class TheOtherClass { public void SetFactoryMethod(Action factoryMethod) { // set here } } 

因此,你可以给一个委托给一些其他的类来使用,而不会把你的types暴露给他们。 你唯一要公开的是你的方法的签名(你有多less个参数等等)。

“我的方法签名”,我从哪里听到的? 哦,是的,接口! 接口描述整个类的签名。 把代表想象成只描述一种方法的签名!

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

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

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

事件

事件只是属性(如get; set;属性到实例字段),这些属性向其他对象公开订阅。 但是这些属性不支持get; set ;. 相反,他们支持添加;删除;

所以你可以有:

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

用户界面(WinForms)

所以,现在我们知道一个委托是对一个方法的引用,并且我们可以有一个事件让世界知道他们可以给我们的方法来从我们的委托中引用,而我们是一个UIbutton,那么:我们可以询问任何对我是否被点击感兴趣的人,向我们注册他们的方法(通过我们公开的事件)。 我们可以使用所有提供给我们的方法,并由我们的代表引用它们。 然后,我们将等待,直到用户来到并点击该button,然后我们将有足够的理由来调用委托。 而且由于委托引用了所有给我们的方法,所有这些方法都会被调用。 我们不知道这些方法是做什么的,也不知道哪个类实现了这些方法。 我们所关心的只是有人对我们被点击感兴趣,并给了我们一个符合我们想要的签名的方法的参考。

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(); } } } 

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

 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!"); } 

每当FooclosuresMyEvent ,那么你的OnMyEvent处理程序将被调用。

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

只是添加到现有的伟大的答案在这里 – build立在接受的代码,它使用delegate void MyEventHandler(string foo)

由于编译器知道SomethingHappened事件的委托types,因此:

 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!"); 

我对事件的理解是;

代表:

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

创build和调用事件的步骤:

  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(); } } } } 

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

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

因此,当事件发生在发布者时,委托会收到一些事件参数(数据等),但是发布者不知道这些数据会发生什么。 订阅者可以在自己的类中创build方法来响应发布者类中的事件,以便订阅者可以响应发布者的事件。

 //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); 

我同意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(); } } } }