我完全意识到我提出的建议并不遵循.NET准则,因此,仅仅出于这个原因,可能不是一个好主意.不过,我想从两个可能的Angular 考虑:
(1)我是否应该考虑这一点用于我自己的开发工作,这是出于内部目的的100%.
(2)这是框架设计者可以考虑改变或更新的概念吗?
我正在考虑使用一个使用强类型"sender"的事件签名,而不是将其键入为"object",后者是当前的.NET设计模式.也就是说,不要使用如下所示的标准事件签名:
class Publisher
{
public event EventHandler<PublisherEventArgs> SomeEvent;
}
我正在考虑使用利用强类型"sender"参数的事件签名,如下所示:
首先,定义一个"StrongTypedEventHandler":
[SerializableAttribute]
public delegate void StrongTypedEventHandler<TSender, TEventArgs>(
TSender sender,
TEventArgs e
)
where TEventArgs : EventArgs;
这与行动没有太大区别<;特文塔格斯特森德;,但通过使用StrongTypedEventHandler
,我们强制执行TEventArgs源于System.EventArgs
.
接下来,作为示例,我们可以在发布类中使用StrongTypedEventHandler,如下所示:
class Publisher
{
public event StrongTypedEventHandler<Publisher, PublisherEventArgs> SomeEvent;
protected void OnSomeEvent()
{
if (SomeEvent != null)
{
SomeEvent(this, new PublisherEventArgs(...));
}
}
}
上述安排将使订阅者能够利用不需要强制转换的强类型事件处理程序:
class Subscriber
{
void SomeEventHandler(Publisher sender, PublisherEventArgs e)
{
if (sender.Name == "John Smith")
{
// ...
}
}
}
我完全意识到这违反了标准.NET事件处理模式;但是,请记住,如果需要,逆变将使订阅者能够使用传统的事件处理签名:
class Subscriber
{
void SomeEventHandler(object sender, PublisherEventArgs e)
{
if (((Publisher)sender).Name == "John Smith")
{
// ...
}
}
}
也就是说,如果事件处理程序需要订阅来自不同(或可能未知)对象类型的事件,则处理程序可以将"sender"参数键入为"object",以便处理所有潜在的sender对象.
除了打破惯例(相信我,这是我不会掉以轻心的事情),我想不出这有什么坏处.
这里可能存在一些CLS合规性问题.这在VisualBasic中运行.NET 2008 [Edit: I have since tested this, and it is confirmed: VB.NET 2005 and below cannot handle this, but VB.NET 2008 is 100% fine. See "Edit #2", below.]%很好(我已经测试过),但我相信旧版本的Visual Basic.净到2005年没有代表协方差和逆变.可能还有其他原因.NET语言也有这个问题,我不能确定.
但是我不认为我自己是在为C#或Visual Basic.NET以外的任何语言进行开发,我也不介意将其限制为用于.NET Framework 3.0和更高版本的C#和VB.NET.(老实说,在这一点上我无法想象回到2.0.)
还有谁能想到这方面的问题?或者,这是不是简单地打破常规,让人反胃?
以下是我找到的一些相关链接:
(1) Event Design Guidelines [MSDN 3.5]
(2)C# simple Event Raising - using “sender” vs. custom EventArgs [StackOverflow 2009]人
(3)Event signature pattern in .net [StackOverflow 2008]个
我对任何人和每个人对这件事的看法都感兴趣...
提前谢谢,
迈克
Edit #1:这是对 Tommy Carlier's post :
这里有一个完整的工作示例,它显示了强类型事件处理程序和使用"对象发送器"参数的当前标准事件处理程序都可以与此方法共存.您可以复制粘贴代码并运行它:
namespace csScrap.GenericEventHandling
{
class PublisherEventArgs : EventArgs
{
// ...
}
[SerializableAttribute]
public delegate void StrongTypedEventHandler<TSender, TEventArgs>(
TSender sender,
TEventArgs e
)
where TEventArgs : EventArgs;
class Publisher
{
public event StrongTypedEventHandler<Publisher, PublisherEventArgs> SomeEvent;
public void OnSomeEvent()
{
if (SomeEvent != null)
{
SomeEvent(this, new PublisherEventArgs());
}
}
}
class StrongTypedSubscriber
{
public void SomeEventHandler(Publisher sender, PublisherEventArgs e)
{
MessageBox.Show("StrongTypedSubscriber.SomeEventHandler called.");
}
}
class TraditionalSubscriber
{
public void SomeEventHandler(object sender, PublisherEventArgs e)
{
MessageBox.Show("TraditionalSubscriber.SomeEventHandler called.");
}
}
class Tester
{
public static void Main()
{
Publisher publisher = new Publisher();
StrongTypedSubscriber strongTypedSubscriber = new StrongTypedSubscriber();
TraditionalSubscriber traditionalSubscriber = new TraditionalSubscriber();
publisher.SomeEvent += strongTypedSubscriber.SomeEventHandler;
publisher.SomeEvent += traditionalSubscriber.SomeEventHandler;
publisher.OnSomeEvent();
}
}
}
Edit #2:这是对Andrew Hare's statement关于协方差和逆变换以及它在这里的应用的回应.长期以来,C#语言中的代表都有协变和逆变,这让人觉得是"内在的",但事实并非如此.它甚至可能是在CLR中启用的,我不知道,但是VisualBasic.NET在测试之前没有为其代表提供协方差和逆变功能.NET框架3.0(VB.NET 2008).因此,VisualBasic.净赚.NET 2.0及以下版本将无法使用这种方法.
例如,上面的例子可以翻译成VB.净额如下:
Namespace GenericEventHandling
Class PublisherEventArgs
Inherits EventArgs
' ...
' ...
End Class
<SerializableAttribute()> _
Public Delegate Sub StrongTypedEventHandler(Of TSender, TEventArgs As EventArgs) _
(ByVal sender As TSender, ByVal e As TEventArgs)
Class Publisher
Public Event SomeEvent As StrongTypedEventHandler(Of Publisher, PublisherEventArgs)
Public Sub OnSomeEvent()
RaiseEvent SomeEvent(Me, New PublisherEventArgs)
End Sub
End Class
Class StrongTypedSubscriber
Public Sub SomeEventHandler(ByVal sender As Publisher, ByVal e As PublisherEventArgs)
MessageBox.Show("StrongTypedSubscriber.SomeEventHandler called.")
End Sub
End Class
Class TraditionalSubscriber
Public Sub SomeEventHandler(ByVal sender As Object, ByVal e As PublisherEventArgs)
MessageBox.Show("TraditionalSubscriber.SomeEventHandler called.")
End Sub
End Class
Class Tester
Public Shared Sub Main()
Dim publisher As Publisher = New Publisher
Dim strongTypedSubscriber As StrongTypedSubscriber = New StrongTypedSubscriber
Dim traditionalSubscriber As TraditionalSubscriber = New TraditionalSubscriber
AddHandler publisher.SomeEvent, AddressOf strongTypedSubscriber.SomeEventHandler
AddHandler publisher.SomeEvent, AddressOf traditionalSubscriber.SomeEventHandler
publisher.OnSomeEvent()
End Sub
End Class
End Namespace
VB.NET2008可以100%正常运行.但我现在已经在VB上测试过了.NET 2005,只是为了确定,它没有编译,声明:
方法的公共Sub SomeEventHandler(发件人作为对象,例如 作为 vbGenericEventHandling.GenericEventHandling.PublisherEventArgs)‘ 的签名与 代理的代理子 StrongTypedEventHandler(属于TSender, TEventArgs As System.EventArgs)(发送方 作为出版商,例如 PublisherEventArgs)‘
基本上,代理在VB中是不变的.NET版本2005及以下版本.事实上,我在几年前就想到了这个 idea ,但是VB.NET无法处理这件事让我很烦恼...但我现在已经坚定地转向C#,和VB.NET现在可以处理它了,所以,好吧,这篇文章就是这样.
Edit: Update #3个
好的,我用这个已经相当成功了一段时间了.这真的是一个很好的系统.我决定将我的"StrongTypedEventHandler"命名为"GenericEventHandler",定义如下:
[SerializableAttribute]
public delegate void GenericEventHandler<TSender, TEventArgs>(
TSender sender,
TEventArgs e
)
where TEventArgs : EventArgs;
除了这个重命名,我完全按照上面讨论的方式实现了它.
它确实违反了FxCop规则CA1009,该规则规定:
"按照惯例,.NET事件有两个 指定事件的参数 发件人和事件数据.事件处理程序 签名应遵循以下格式: void MyEventHandler(对象发送者, 事件参数e).‘sender’参数 始终为System.Object类型,甚至 如果有可能雇佣更多的 特定类型.‘e’参数是 始终为System.EventArgs类型. 不提供事件数据的事件 应使用System.EventHandler 委托类型.事件处理程序返回 无效,以便他们可以发送每个事件 添加到多个目标方法.任何值 由目标返回的数据将会丢失 在第一通电话之后."
当然,我们都知道这一切,而且无论如何都在违反规则.(在任何情况下,如果愿意,所有事件处理程序都可以在其签名中使用标准的"对象发送者"——这是一个非 destruct 性的更改.)
因此,使用SuppressMessageAttribute
可以实现以下目的:
[SuppressMessage("Microsoft.Design", "CA1009:DeclareEventHandlersCorrectly",
Justification = "Using strong-typed GenericEventHandler<TSender, TEventArgs> event handler pattern.")]
我希望这种方法在future 某个时候成为标准.它真的非常好用.
谢谢大家的意见,我真的很感激.
迈克