我了解事件的目的,尤其是在创建用户界面的上下文中。我认为这是创建事件的原型:
public void EventName(object sender, EventArgs e);
事件处理程序做什么,为什么需要它们,以及如何创建事件处理程序?
我了解事件的目的,尤其是在创建用户界面的上下文中。我认为这是创建事件的原型:
public void EventName(object sender, EventArgs e);
事件处理程序做什么,为什么需要它们,以及如何创建事件处理程序?
Answers:
要了解事件处理程序,您需要了解委托。在C#中,您可以将委托视为方法的指针(或引用)。这很有用,因为指针可以作为值传递。
代表的中心概念是其签名或形状。即(1)返回类型和(2)输入参数。例如,如果我们创建一个委托void MyDelegate(object sender, EventArgs e)
,则它只能指向return void
并采用object
and的方法EventArgs
。有点像方孔和方钉。因此,我们说这些方法与委托具有相同的签名或形状。
因此,了解了如何创建对方法的引用之后,我们来考虑事件的目的:当系统中其他地方发生事件时,我们希望使某些代码得以执行-或“处理事件”。为此,我们为要执行的代码创建特定的方法。事件和要执行的方法之间的粘合是委托。该事件必须在内部存储指向引发该事件时要调用的方法的指针的“列表”。*当然,要能够调用方法,我们需要知道要传递给它的参数!我们使用委托作为事件与将要调用的所有特定方法之间的“契约”。
因此,默认值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#知道两个术语,delegate
和event
。让我们从第一个开始。
A 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
)。这意味着,当您调用委托时,将执行多个显式附加的方法。对象引用始终只能引用一个对象。
对a的限制MulticastDelegate
是(方法/委托)签名不应具有任何返回值(void
)和关键字out
,ref
并且不能在签名中使用。显然,您不能调用两个返回数字并期望它们返回相同数字的方法。签名符合后,委托将自动为MulticastDelegate
。
事件只是属性(例如get; set;实例字段的属性),它公开了其他对象对委托的订阅。但是,这些属性不支持get; set;。相反,他们支持添加;去掉;
因此,您可以:
Action myField;
public event Action MyProperty
{
add { myField += value; }
remove { myField -= value; }
}
因此,现在我们知道委托是对方法的引用,并且我们可以有一个事件让全世界知道他们可以向我们提供要从委托中引用的方法,我们是一个UI按钮,然后:可以要求对我是否被点击感兴趣的任何人(通过我们公开的事件)向我们注册他们的方法。我们可以使用所有提供给我们的方法,并由我们的代表引用它们。然后,我们将等待,直到用户来单击该按钮,然后我们才有足够的理由来调用委托。并且由于该委托引用了提供给我们的所有那些方法,因此将调用所有这些方法。我们不知道这些方法的作用,也不知道哪个类实现了这些方法。我们只关心有人对我们的点击感兴趣,
像Java这样的语言没有委托。他们改用接口。他们这样做的方法是,请对“我们被点击”感兴趣的任何人实现一个特定的接口(使用可以调用的特定方法),然后为我们提供实现该接口的整个实例。我们保留实现此接口的所有对象的列表,并在单击时可以调用它们的“可以调用的某些方法”。
event
仅仅是语法糖,仅此而已。
这实际上是事件处理程序的声明-事件触发时将调用该方法。要创建事件,您需要编写以下内容:
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
按惯例是基础)。例如,如果您查看Control
WinForms或FrameworkElement
WPF中定义的某些事件,则可以看到将附加信息传递给事件处理程序的事件示例。
OnXXX
为事件处理程序使用命名模式。(愚蠢的是,OnXXX在MFC中被表示为“ handle XXX”,而在.net中则被称为“ raise XXX”,因此,其含义尚不清楚且令人困惑- 有关详细信息,请参见此文章)。首选名称是RaiseXXX
引发事件,HandleXXX
或Sender_XXX
用于事件处理程序。
这是一个可能有帮助的代码示例:
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();
}
}
}
OnMaximum?.Invoke(this,new MyEventArgs("you've entered..."));
只是为了增加现有的好答案-在接受的代码的基础上,使用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!");
我对这些事件的理解是:
代表:
用于保存对要执行的方法的引用的变量。这样就可以传递变量之类的方法。
创建和调用事件的步骤:
该事件是委托的实例
由于事件是委托的实例,因此我们必须首先定义委托。
分配触发事件时要执行的方法(调用委托)
触发事件(呼叫代表)
例:
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();
}
}
}
}
//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();
}
}
}
}
我最近举了一个如何在c#中使用事件的示例,并将其发布在我的博客上。我试图通过一个非常简单的示例使它尽可能清晰。万一它可以帮助任何人,请访问:http : //www.konsfik.com/using-events-in-csharp/
它包括描述和源代码(带有大量注释),并且主要关注事件和事件处理程序的正确使用(类似于模板)。
一些关键点是:
事件就像“子类型的代表”,只是受到更多限制(以一种很好的方式)。实际上,事件的声明始终包含委托(EventHandlers是委托的一种)。
事件处理程序是特定类型的委托人(您可能将它们视为模板),它迫使用户创建具有特定“签名”的事件。签名的格式为:(对象发送者,EventArgs eventarguments)。
您可以创建自己的EventArgs子类,以包括事件需要传达的任何类型的信息。使用事件时不必使用EventHandlers。您可以完全跳过它们,并使用自己的代理代替它们。
使用事件和委托之间的主要区别在于,即使可以将事件声明为公共事件,也只能从声明它们的类中调用事件。这是一个非常重要的区别,因为它允许您公开事件,以便将它们“连接”到外部方法,同时保护它们免受“外部滥用”。
要了解的另一件事,在某些情况下,当您需要低级别的耦合时必须使用“委托/事件”!
如果要在应用程序中的多个位置使用组件,则需要使组件具有较低的耦合级别,并且必须将特定的无关LOGIC委托给外部您的组件!这样可以确保您具有解耦的系统和更简洁的代码。
按照SOLID原理,这是“ D ”,(D依赖倒置原则)。
也称为“ IoC ”,控制反转。
您可以使用事件,代表和DI来创建“ IoC ”(依赖注入)。
在子类中访问方法很容易。但是从子级访问父类中的方法更加困难。您必须将父代参考传递给孩子!(或与接口一起使用DI)
委托/活动使我们无需参考即可从孩子与父母沟通!
在上面的图中,我不使用Delegate / Event,并且父组件B 必须具有父组件A 的引用才能执行A方法中无关的业务逻辑(高级耦合)。
使用这种方法,我将不得不放置所有使用组件B的组件的所有引用!:(
在上面的图表中,我使用了Delegate / Event,而组件B不必知道A。(低耦合度)
您可以在应用程序中的任何位置使用B组件!