答案 0 :(得分:25)
似乎微软已经接受了这一点,因为类似的例子现在在MSDN上:
答案 1 :(得分:13)
你提出的建议实际上有很多意义,我只是想知道这是否就是那种简单的事情之一,因为它最初是在仿制药之前设计的,或者是否有真正的原因。< / p>
答案 2 :(得分:12)
Windows运行时(WinRT)引入了TypedEventHandler<TSender, TResult>
委托,它完全按照StrongTypedEventHandler<TSender, TResult>
的要求执行,但显然没有TResult
类型参数的约束:
public delegate void TypedEventHandler<TSender, TResult>(TSender sender,
TResult args);
MSDN文档为here。
答案 3 :(得分:5)
我对以下陈述提出异议:
- 我认为Visual Basic .NET到2005年的旧版本没有委托协方差和逆变。
- 我完全意识到这就是亵渎神灵。
首先,你在这里所做的一切与协方差或逆变无关。(编辑: 以前的陈述是错误的,有关更多信息请参阅Covariance and Contravariance in Delegates )此解决方案在所有CLR版本2.0及更高版本中都能正常运行(显然这将不在CLR 1.0应用程序中工作,因为它使用泛型)。
其次,我强烈反对你的想法接近“亵渎神灵”,因为这是一个很棒的主意。
答案 4 :(得分:5)
我看了一下如何使用新的WinRT处理这个问题,并根据其他意见,并最终决定这样做:
[Serializable]
public delegate void TypedEventHandler<in TSender, in TEventArgs>(
TSender sender,
TEventArgs e
) where TEventArgs : EventArgs;
考虑到在WinRT中使用名称TypedEventHandler,这似乎是最好的方法。
答案 5 :(得分:2)
我认为这是一个好主意,MS可能根本没有时间或兴趣投入使其更好,例如当他们从ArrayList转移到基于通用的列表时。
答案 6 :(得分:2)
根据我的理解,“发件人”字段总是应该引用持有事件订阅的对象。如果我有我的druthers,那么还会有一个字段,其中包含足以在必要时取消订阅事件的信息(*)(例如,考虑一个订阅'收集更改'事件的更改记录器;它包含两个部分其中一个做实际工作并保存实际数据,另一个提供公共接口包装器,主要部分可以保存对包装器部分的弱引用。如果包装器部分被垃圾收集,那就意味着不再有任何人对正在收集的数据感兴趣,因此变更记录器应该取消订阅它收到的任何事件。
由于对象可能代表另一个对象发送事件,因此我可以看到对于具有Object类型的“sender”字段以及使EventArgs派生字段包含对该对象的引用的一些潜在用途。应该采取行动的对象。然而,“发送者”字段的使用可能受到以下事实的限制:对象无法取消订阅未知发件人。
(*)实际上,处理取消订阅的一种更简洁的方法是为返回布尔值的函数设置多播委托类型;如果这样的委托调用的函数返回True,则会修补委托以删除该对象。这将意味着委托将不再是真正不可变的,但是应该可以以线程安全的方式实现这种改变(例如,通过使对象引用无效并使多播委托代码忽略任何嵌入的空对象引用)。在这种情况下,无论事件发生在何处,都可以非常干净地处理对已处置对象的发布和事件的尝试。
答案 7 :(得分:2)
回顾亵渎是使发件人成为对象类型的唯一原因(如果忽略VB 2005代码中的逆转问题,这是微软的错误恕我直言),任何人都可以提出至少理论上的动机来确定第二个论点EventArgs类型。更进一步,是否有充分的理由在这种特殊情况下遵守微软的指导方针和惯例?
我们需要为我们想要在事件处理程序中传递的另一个数据开发另一个EventArgs包装器似乎很奇怪,为什么不能直接在那里传递那些数据。请考虑以下代码部分
[例1]
public delegate void ConnectionEventHandler(Server sender, Connection connection);
public partial class Server
{
protected virtual void OnClientConnected(Connection connection)
{
if (ClientConnected != null) ClientConnected(this, connection);
}
public event ConnectionEventHandler ClientConnected;
}
[例2]
public delegate void ConnectionEventHandler(object sender, ConnectionEventArgs e);
public class ConnectionEventArgs : EventArgs
{
public Connection Connection { get; private set; }
public ConnectionEventArgs(Connection connection)
{
this.Connection = connection;
}
}
public partial class Server
{
protected virtual void OnClientConnected(Connection connection)
{
if (ClientConnected != null) ClientConnected(this, new ConnectionEventArgs(connection));
}
public event ConnectionEventHandler ClientConnected;
}
答案 8 :(得分:1)
我不认为你想做什么有什么不对。在大多数情况下,我怀疑object sender
参数仍然是为了继续支持2.0之前的代码。
如果您真的想对公共API进行此更改,您可能需要考虑创建自己的基础EvenArgs类。像这样:
public class DataEventArgs<TSender, TData> : EventArgs
{
private readonly TSender sender, TData data;
public DataEventArgs(TSender sender, TData data)
{
this.sender = sender;
this.data = data;
}
public TSender Sender { get { return sender; } }
public TData Data { get { return data; } }
}
然后你可以宣布你的活动
public event EventHandler<DataEventArgs<MyClass, int>> SomeIndexSelected;
这样的方法:
private void HandleSomething(object sender, EventArgs e)
仍然可以订阅。
修改强>
最后一行让我思考了一点......你应该能够在不破坏任何外部功能的情况下实现你提出的建议,因为运行时没有问题向下转换参数。我仍然倾向于DataEventArgs
解决方案(个人)。我会这样做,但是知道它是多余的,因为发送者存储在第一个参数中并作为事件args的属性。
坚持使用DataEventArgs
的一个好处是,您可以链接事件,更改发件人(代表最后一位发件人),而EventArgs会保留原始发件人。
答案 9 :(得分:1)
根据当前情况(发件人是对象),您可以轻松地将方法附加到多个事件:
button.Click += ClickHandler;
label.Click += ClickHandler;
void ClickHandler(object sender, EventArgs e) { ... }
如果sender是泛型的,则click-event的目标不是Button类型或Label类型,而是Control类型的目标(因为事件是在Control上定义的)。因此,Button类上的某些事件将具有Control类型的目标,其他事件将具有其他目标类型。
答案 10 :(得分:1)
去吧。对于非基于组件的代码,我经常将事件签名简化为
public event Action<MyEventType> EventName
其中MyEventType
未从EventArgs
继承。如果我从不打算使用EventArgs的任何成员,为什么还要费心。