C#:显式事件添加/删除!=典型事件?
我已经声明了一个通用事件处理函数
public delegate void EventHandler();
我已经添加了扩展方法'RaiseEvent':
public static void RaiseEvent(this EventHandler self) { if (self != null) self.Invoke(); }
当我使用典型的语法定义事件
public event EventHandler TypicalEvent;
那么我可以调用使用扩展方法没有问题:
TypicalEvent.RaiseEvent();
但是,当我用明确的添加/删除语法定义事件
private EventHandler _explicitEvent; public event EventHandler ExplicitEvent { add { _explicitEvent += value; } remove { _explicitEvent -= value; } }
那么扩展方法在用显式的添加/删除语法定义的事件上不存在:
ExplicitEvent.RaiseEvent(); //RaiseEvent() does not exist on the event for some reason
当我徘徊在事件看到它说的原因:
事件“ExplicitEvent”只能出现在+ =或 – =的左侧
为什么使用典型语法定义的事件不同于使用显式添加/删除语法定义的事件,以及为什么扩展方法不适用于后者?
编辑:我发现我可以通过直接使用私人事件处理程序解决它:
_explicitEvent.RaiseEvent();
但是我仍然不明白为什么我不能像使用典型语法定义的事件那样直接使用事件。 也许有人可以启发我。
因为你可以做到这一点(这是非真实的样本,但它“工作”):
private EventHandler _explicitEvent_A; private EventHandler _explicitEvent_B; private bool flag; public event EventHandler ExplicitEvent { add { if ( flag = !flag ) { _explicitEvent_A += value; /* or do anything else */ } else { _explicitEvent_B += value; /* or do anything else */ } } remove { if ( flag = !flag ) { _explicitEvent_A -= value; /* or do anything else */ } else { _explicitEvent_B -= value; /* or do anything else */ } } }
编译器如何知道应该如何处理“ExplicitEvent.RaiseEvent();”? 答:不可以。
“ExplicitEvent.RaiseEvent();” 只是句法糖,只有在事件被隐含地实现的情况下才能被预测。
当你创build一个类似于“场”的事件时,像这样:
public event EventHandler Foo;
编译器生成一个字段和一个事件。 在声明事件的类的源代码中,每当你引用Foo
,编译器都明白你指的是这个字段 。 但是,该字段是私有的,所以无论Foo
从其他类引用Foo
,它都指向事件(因此也就是添加/删除代码)。
如果您声明自己的显式添加/删除代码,则不会获得自动生成的字段。 所以,你只有一个事件,你不能直接在C#中引发一个事件 – 你只能调用一个委托实例。 事件不是委托实例,它只是一个添加/删除对。
现在,你的代码包含这个:
public EventHandler TypicalEvent;
这仍然有点不同 – 它没有声明一个事件 – 它声明了委托typesEventHandler
的公共字段 。 任何人都可以调用它,因为这个值只是一个委托实例。 了解领域和事件之间的区别很重要。 你不应该写这种types的代码,就像我确定你通常没有其他types的公有字段,比如string
和int
。 不幸的是,这是一个简单的错字,而且是一个相对困难的停止。 你只会注意到编译器允许你分配或使用另一个类的值。
查看我的关于事件和代表的文章以获取更多信息。
那是因为你不看正确。 逻辑与“属性”中的相同。 一旦你设置了添加/删除它不再是一个实际的事件,而是一个暴露实际事件的包装(事件只能从类本身内部触发,所以你总是可以在本地访问真实的事件)。
private EventHandler _explicitEvent; public event EventHandler ExplicitEvent { add { _explicitEvent += value; } remove { _explicitEvent -= value; } } private double seconds; public double Hours { get { return seconds / 3600; } set { seconds = value * 3600; } }
在这两种情况下,具有get / set或add / remove属性的成员都不包含任何数据。 你需要一个“真正的”私人成员来包含实际的数据。 这些属性允许您在将成员暴露给外部世界时编程额外的逻辑。
一个很好的例子是,当你不需要的时候停止额外的计算(没有人在听事件)。
例如,让我们说事件是由一个定时器触发的,如果没有人注册到事件,我们不希望定时器工作:
private System.Windows.Forms.Timer timer = new System.Windows.Forms.Timer(); private EventHandler _explicitEvent; public event EventHandler ExplicitEvent { add { if (_explicitEvent == null) timer.Start(); _explicitEvent += value; } remove { _explicitEvent -= value; if (_explicitEvent == null) timer.Stop(); } }
你可能想locking添加/删除一个对象(事后)…
TypicalEvent的“简单”声明会造成一些编译器的诡计。 它创build一个事件元数据条目,添加和删除方法和一个后台字段。 当您的代码引用了TypicalEvent时,编译器将其转换为对后台字段的引用; 当外部代码引用了TypicalEvent(使用+ =和 – =)时,编译器将其转换为对add或remove方法的引用。
“显式”声明绕过了这个编译器的诡计。 你正在拼写出添加和删除方法和后台字段:事实上,正如TcKs指出,可能甚至没有后台字段(这是使用显式forms的常见原因:请参阅例如System.Windows.Forms中的事件。控制)。 因此,编译器不能再安静地将对TypicalEvent的引用转换为对后台字段的引用:如果需要后台字段,实际的委托对象,则必须直接引用后台字段:
_explicitEvent.RaiseEvent()