Action<T>和delegate event的区别

63

我已经看到开发者使用以下代码,但它们的区别是什么?哪些符合标准?因为 ActionFunc<T> 也是委托,所以它们相同吗?

public event Action<EmployeeEventAgs> OnLeave;
public void Leave()
{
    OnLeave(new EmployeeEventAgs(this.ID));
}

视觉工作室

public delegate void GoOnLeave(EmployeeEventAgs e);
public event GoOnLeave OnLeave;
public void Leave()
{
    OnLeave(new EmployeeEventAgs(this.ID));
}
8个回答

60

值得一提的是,这两个示例都没有使用标准的.NET约定。 EventHandler<T>通常应该声明事件:

public event EventHandler<EmployeeEventArgs> Leave;

“On”前缀应保留用于引发事件的受保护方法:

protected virtual void OnLeave(EmployeeEventArgs e) {
    var handler = Leave;
    if (handler != null) handler(this, e);
}

你不必以这种方式实现,但任何人都会立即识别模式,理解你的代码,并知道如何使用和自定义它。

而且它有一个极大的优点,不需要在自定义委托声明和 Action<>EventHandler<> 之间做出选择,是最好的方法。这回答了你的问题。


51
这是很好的建议,但我不认为它回答了问题。 - Jason Kleban
3
你是有意将 Leave 处理程序声明为委托类型,而不是使用以下方式进行声明:public event EventHandler<> ...吗?使用 event 关键字可以在处理公开事件的类时发挥巨大作用,因为如果没有该关键字,使用 obj.Leave = someHandler 而不是 obj.Leave += someHandler 可以轻松地清除整个事件集合。 - Ivaylo Slavov
3
不,那是个错误。三年里没有人注意到 :) 谢谢! - Hans Passant

40

以下两行代码几乎是等效的:

public event Action<EmployeeEventAgs> Leave;

相对于:

public event EventHandler<EmployeeEventAgs> Leave;

区别在于事件处理方法的签名。如果您使用带有操作的第一种方法,可以得到以下结果:

public void LeaveHandler(EmployeeEventAgs e) { ... }

然后是这个:

obj.Leave += LeaveHandler;

使用第二种方法时,LeaveHandler的签名需要不同:

public void LeaveHandler(object sender, EmployeeEventAgs e) { ... }
很重要的是要注意,无论哪种情况下都使用了event关键字来声明事件成员。以这种方式声明的事件成员不仅仅是类的一个字段,尽管它看起来像是这样的。相反,编译器将其创建为一个事件属性1。事件属性与普通属性类似,只是它们没有getset访问器。编译器只允许它们在+=-=赋值的左侧使用(添加或删除事件处理程序)。没有办法覆盖已分配的事件处理程序,也没有办法在声明该事件的类之外调用该事件
如果两个示例中都缺少事件关键字,则可以执行以下操作而不会出错或警告:
obj.Leave = LeaveHandler;

这将清除所有已注册的处理程序并用LeaveHandler替换它们。

此外,您还可以执行以下调用:

obj.Leave(new EmployeeEventAgs());
上述两种情况被认为是一种反模式,如果您想创建事件,则事件应仅由所有者对象调用,并且不应允许取消订阅者的无法跟踪的删除。 event 关键字是.NET的编程构造,可帮助您坚持正确使用事件。
考虑到上述内容,我认为许多人坚持使用 EventHandler 方法,因为很少会在没有 event 关键字的情况下使用 EventHandler。 Action 的使用范围更广,但在用作事件时并不像事件那么自然。 后者当然是个人意见,因为事件处理程序方法可能已经在我的编码实践中变得太过硬化。 但是,如果正确使用操作,则使用它们来进行事件并不是违规的。

1 当编译器看到类似以下代码时,事件属性就是编译器自动生成的内容:

event EventHandler SomeEvent 

它最终成为与以下代码大致相同:

private EventHandler _someEvent; // notice the lack of the event keyword!
public event EventHandler SomeEvent
{
    add { _someEvent += value; }
    remove { _someEvent -= value; }
}

我们写成这样的事件调用:

this.SomeEvent(sender, args);

被转换为:

this._someEvent(sender, args);

26

Action<T>delegate void ... (T t)完全相同。

Func<T>delegate T ... ()完全相同。


2
不是“完全相同的”……它们确实具有相同的签名,但它们不能进行赋值兼容(在我看来这非常不幸……) - Thomas Levesque
7
它们不完全相同吗?在这里,X和Y是不同的: public delegate void X(T t); public delegate void Y(T t);你不能将一个分配给另一个。 - pdr

9

5

是的,Action和Func只是在3.5 clr中定义的方便委托。

Action、Func和lambda表达式都只是委托的语法糖和便利方式。

它们并没有什么神奇的地方。有几个人编写了简单的2.0附加库,以添加此功能到2.0代码中。


5

您可能希望在这里看看,查看编译器实际为Action生成的内容是最好的描述。您所写的内容没有功能上的区别,只是采用更短、更方便的语法。


3

一般来说,它们是等效的。但在使用委托作为事件类型的上下文中,惯例是使用EventHandler(其中T继承自EventArgs):

public event EventHandler<EmployeeEventArgs> Left;

public void Leave()
{
    OnLeft(this.ID);
}

protected virtual void OnLeft(int id)
{
    if (Left != null) {
        Left(new EmployeeEventArgs(id));
    }
}

0

你本可以自己编写这些 Action 和 Func 泛型委托,但由于它们通常很有用,所以它们已经为你编写好并放入了 .Net 库中。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接