将事件视为对象

C#还不够OO吗? 在这里,我给出一个(可能是坏的)例子。

public class Program { public event EventHandler OnStart; public static EventHandler LogOnStart = (s, e) => Console.WriteLine("starts"); public class MyCSharpProgram { public string Name { get; set; } public event EventHandler OnStart; public void Start() { OnStart(this, EventArgs.Empty); } } static void Main(string[] args) { MyCSharpProgram cs = new MyCSharpProgram { Name = "C# test" }; cs.OnStart += LogOnStart; //can compile //RegisterLogger(cs.OnStart); // Line of trouble cs.Start(); // it prints "start" (of course it will :D) Program p = new Program(); RegisterLogger(p.OnStart); //can compile p.OnStart(p, EventArgs.Empty); //can compile, but NullReference at runtime Console.Read(); } static void RegisterLogger(EventHandler ev) { ev += LogOnStart; } } 

RegisterLogger(cs.OnStart)导致编译错误,因为“事件XXX只能出现在+ =或 – = blabla的左侧”。 但为什么RegisterLogger(p.OnStart)可以? 同时,虽然我注册了p.OnStart,但它也会抛出一个NullReferenceException,似乎p.OnStart并没有“真正”传递给一个方法。

“事件XXX只能出现在+ =或 – = blabla的左侧”

这实际上是因为C#足够“OO”。 OOP的核心原则之一是封装; 事件提供了这种forms,就像属性一样:在声明类中,它们可以直接访问,但在外部它们只暴露给+=-=运算符。 这样,声明类就可以完全控制事件的调用时间。 客户端代码只能对调用它们时发生的事情有发言权。

您的代码RegisterLogger(p.OnStart)编译的原因是它是在Program类的范围内声明的,其中声明了Program.OnStart事件。

您的代码RegisterLogger(cs.OnStart) 无法编译的原因是它是从Program类的范围内声明的,但MyCSharpProgram.OnStart事件(显然)在MyCSharpProgram类中声明。

正如Chris Taylor指出的那样,你在p.OnStart(p, EventArgs.Empty);行上得到NullReferenceException的原因p.OnStart(p, EventArgs.Empty); 是调用RegisterLogger因为它为局部变量赋值,对作为参数传入的局部变量赋值的对象没有影响 。 要更好地理解这一点,请考虑以下代码:

 static void IncrementValue(int value) { value += 1; } int i = 0; IncrementValue(i); // Prints '0', because IncrementValue had no effect on i -- // a new value was assigned to the COPY of i that was passed in Console.WriteLine(i); 

正如将int作为参数并为其赋值的方法仅影响复制到其堆栈的局部变量一样,将EventHandler作为参数并为其赋值的方法仅将局部变量影响为好(在任务中)。

对RegisterLogger进行以下更改,将ev声明为事件处理程序的引用参数。

 static void RegisterLogger(ref EventHandler ev) { ev += LogOnStart; } 

然后,在调用方法时,您的调用点还需要使用’ref’关键字,如下所示

 RegisterLogger(ref p.OnStart); 

无法编译的原因:

RegisterLogger(cs.OnStart);

…是事件处理程序和传递给它的方法是在不同的类中。 C#非常严格地处理事件,并且只允许事件出现的类除了添加处理程序(包括将其传递给函数或调用它)之外的任何事情。

例如,这也不会编译(因为它在不同的类中):

 cs.OnStart(cs, EventArgs.Empty); 

至于无法以这种方式将事件处理程序传递给函数,我不确定。 我猜测事件就像值类型一样运作。 通过ref传递它将解决您的问题,但:

 static void RegisterLogger(ref EventHandler ev) { ev += LogOnStart; } 

当一个对象声明一个事件时,它只公开向该类外部的事件添加和/或删除处理程序的方法(前提是它不重新定义添加/删除操作)。 在其中,它被视为“对象”,并且或多或少地像声明的委托变量一样工作。 如果没有向事件添加处理程序,就好像它从未被初始化并且为null 。 这是设计的方式。 以下是框架中使用的典型模式:

 public class MyCSharpProgram { // ... // define the event public event EventHandler SomeEvent; // add a mechanism to "raise" the event protected virtual void OnSomeEvent() { // SomeEvent is a "variable" to a EventHandler if (SomeEvent != null) SomeEvent(this, EventArgs.Empty); } } // etc... 

现在,如果您必须坚持在类之外公开委托,请不要将其定义为事件。 然后,您可以将其视为任何其他字段或属性。

我修改了您的示例代码来说明:

 public class Program { public EventHandler OnStart; public static EventHandler LogOnStart = (s, e) => Console.WriteLine("starts"); public class MyCSharpProgram { public string Name { get; set; } // just a field to an EventHandler public EventHandler OnStart = (s, e) => { /* do nothing */ }; // needs to be initialized to use "+=", "-=" or suppress null-checks public void Start() { // always check if non-null if (OnStart != null) OnStart(this, EventArgs.Empty); } } static void Main(string[] args) { MyCSharpProgram cs = new MyCSharpProgram { Name = "C# test" }; cs.OnStart += LogOnStart; //can compile RegisterLogger(cs.OnStart); // should work now cs.Start(); // it prints "start" (of course it will :D) Program p = new Program(); RegisterLogger(p.OnStart); //can compile p.OnStart(p, EventArgs.Empty); //can compile, but NullReference at runtime Console.Read(); } static void RegisterLogger(EventHandler ev) { // Program.OnStart not initialized so ev is null if (ev != null) //null-check just in case ev += LogOnStart; } }