.NET 中的事件签名——使用强类型的“发送者"? [英] Event Signature in .NET -- Using a Strong Typed 'Sender'?

查看:27
本文介绍了.NET 中的事件签名——使用强类型的“发送者"?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我完全意识到我提出的建议不遵循 .NET 准则,因此仅凭这个原因可能是一个糟糕的主意.但是,我想从两个可能的角度考虑这一点:

(1) 我是否应该考虑将其用于我自己的开发工作,100% 用于内部目的.

(2) 这是一个框架设计者可以考虑改变或更新的概念吗?

我正在考虑使用利用强类型发送者"的事件签名,而不是将其键入为对象",这是当前的 .NET 设计模式.也就是说,而不是使用如下所示的标准事件签名:

类发布者{公共事件 EventHandler一些事件;}

我正在考虑使用一个利用强类型发送者"参数的事件签名,如下所示:

首先定义一个StrongTypedEventHandler":

[SerializableAttribute]公共委托 void StrongTypedEventHandler(TSender 发件人,TEventArgs e)其中 TEventArgs : EventArgs;

这与 Action 并没有什么不同,但是通过使用 StrongTypedEventHandler,我们强制 TEventArgs 派生自 System.EventArgs.

接下来,作为一个例子,我们可以在发布类中使用StrongTypedEventHandler,如下所示:

类发布者{公共事件 StrongTypedEventHandler一些事件;protected void OnSomeEvent(){if (SomeEvent != null){SomeEvent(this, new PublisherEventArgs(...));}}}

上述安排将使订阅者能够使用不需要强制转换的强类型事件处理程序:

class 订阅者{void SomeEventHandler(Publisher sender, PublisherEventArgs e){if (sender.Name == "John Smith"){//...}}}

我完全意识到这违背了标准的 .NET 事件处理模式;但是,请记住,如果需要,逆变器将使订阅者能够使用传统的事件处理签名:

class 订阅者{void SomeEventHandler(object sender, PublisherEventArgs e){if (((Publisher)sender).Name == "John Smith"){//...}}}

也就是说,如果事件处理程序需要订阅来自不同(或可能未知)对象类型的事件,则处理程序可以将发送者"参数键入为对象",以便处理潜在发送者对象的全部范围.

除了打破惯例(这是我不会掉以轻心的事情,相信我)我想不出任何缺点.

此处可能存在一些 CLS 合规性问题.这确实在 Visual Basic .NET 2008 中运行 100% 很好(我已经测试过),但我相信 Visual Basic .NET 到 2005 年的旧版本没有委托协变和逆变. 可能还有其他 .NET 语言也有此问题,我不确定.

但我不认为自己为 C# 或 Visual Basic .NET 以外的任何语言进行开发,而且我不介意将其限制为 C# 和 VB.NET for .NET Framework 3.0 及更高版本.(说实话,我无法想象此时要回到 2.0.)

其他人能想到这个问题吗?或者这只是打破常规,让人们胃口大开?

以下是我找到的一些相关链接:

(1) 事件设计指南 [MSDN 3.5]>

(2) C#简单的事件引发——使用sender" 与自定义 EventArgs [StackOverflow 2009]

(3) .net 中的事件签名模式 [StackOverflow 2008]>

我对任何人和每个人对此的看法都很感兴趣...

提前致谢,

迈克

编辑 #1: 这是对 汤米卡利尔的帖子:

这是一个完整的工作示例,它表明强类型事件处理程序和使用对象发送者"参数的当前标准事件处理程序可以与这种方法共存.您可以复制粘贴代码并运行:

命名空间 csScrap.GenericEventHandling{类 PublisherEventArgs : EventArgs{//...}[序列化属性]公共委托 void StrongTypedEventHandler(TSender 发件人,TEventArgs e)其中 TEventArgs : EventArgs;类发布者{公共事件 StrongTypedEventHandler一些事件;公共无效 OnSomeEvent(){if (SomeEvent != null){SomeEvent(this, new PublisherEventArgs());}}}类 StrongTypedSubscriber{public void SomeEventHandler(Publisher sender, PublisherEventArgs e){MessageBox.Show("StrongTypedSubscriber.SomeEventHandler 被调用.");}}类传统订阅者{public void SomeEventHandler(object sender, PublisherEventArgs e){MessageBox.Show("TraditionalSubscriber.SomeEventHandler 被调用.");}}班级测试员{public static void Main(){出版商出版商=新出版商();StrongTypedSubscriber strongTypedSubscriber = new StrongTypedSubscriber();传统订阅者传统订阅者 = 新传统订阅者();Publisher.SomeEvent += strongTypedSubscriber.SomeEventHandler;发布者.SomeEvent +=传统订阅者.SomeEventHandler;发布者.OnSomeEvent();}}}

编辑 #2: 这是为了回应 Andrew Hare 的声明 关于协变和逆变以及它在此处的适用方式.C# 语言中的委托长期以来一直具有协变和逆变,以至于感觉内在",但事实并非如此.它甚至可能是在 CLR 中启用的功能,我不知道,但是直到 .NET Framework 3.0 (VB.NET 2008) 之前,Visual Basic .NET 才为其委托获得协变和逆变功能.因此,用于 .NET 2.0 及以下版本的 Visual Basic.NET 将无法使用这种方法.

例如上面的例子可以翻译成VB.NET如下:

Namespace GenericEventHandling类 PublisherEventArgs继承 EventArgs' ...' ...结束班<SerializableAttribute()>_Public Delegate Sub StrongTypedEventHandler(Of TSender, TEventArgs As EventArgs) _(ByVal sender As TSender, ByVal e As TEventArgs)班级出版商公共事件 SomeEvent As StrongTypedEventHandler(Of Publisher, PublisherEventArgs)公共子 OnSomeEvent()RaiseEvent SomeEvent(Me, New PublisherEventArgs)结束子结束班类强类型订阅者Public Sub SomeEventHandler(ByVal sender As Publisher, ByVal e As PublisherEventArgs)MessageBox.Show("StrongTypedSubscriber.SomeEventHandler 被调用.")结束子结束班类传统订阅者Public Sub SomeEventHandler(ByVal sender As Object, ByVal e As PublisherEventArgs)MessageBox.Show("TraditionalSubscriber.SomeEventHandler 被调用.")结束子结束班班级测试员公共共享子主()Dim 发布者作为发布者 = 新发布者Dim strongTypedSubscriber As StrongTypedSubscriber = New StrongTypedSubscriber将传统订阅者调暗为传统订阅者 = 新的传统订阅者AddHandler publisher.SomeEvent, AddressOf strongTypedSubscriber.SomeEventHandlerAddHandler publisher.SomeEvent, AddressOf传统订阅者.SomeEventHandler发布者.OnSomeEvent()结束子结束班结束命名空间

VB.NET 2008 可以 100% 正常运行.但是我现在已经在 VB.NET 2005 上测试了它,只是为了确定,它不能编译,说明:

<块引用>

方法'公共子SomeEventHandler(sender As Object, e作为vbGenericEventHandling.GenericEventHandling.PublisherEventArgs)'没有相同的签名delegate '委托子StrongTypedEventHandler(属于TSender,TEventArgs As System.EventArgs)(发送方作为出版商,作为PublisherEventArgs)'

基本上,委托在 VB.NET 2005 及以下版本中是不变的.几年前我确实想到了这个想法,但是 VB.NET 无法处理这个问题让我感到困扰......但我现在已经坚定地转向 C#,并且 VB.NET 现在可以处理它,所以,好吧,因此这个帖子.

更新 #3

好的,我已经成功地使用了一段时间了.这确实是一个不错的系统.我决定将我的StrongTypedEventHandler"命名为GenericEventHandler",定义如下:

[SerializableAttribute]公共委托 void GenericEventHandler(TSender 发件人,TEventArgs e)其中 TEventArgs : EventArgs;

除了这个重命名之外,我完全按照上面讨论的方式实现了它.

它确实被 FxCop 规则 CA1009 绊倒,其中规定:

<块引用>

"按照惯例,.NET 事件有两个指定事件的参数发送者和事件数据.事件处理程序签名应遵循以下形式:void MyEventHandler( 对象发送者,事件参数 e).发件人"参数始终是 System.Object 类型,即使如果可以雇用更多具体类型.'e' 参数是始终为 System.EventArgs 类型.不提供事件数据的事件应该使用 System.EventHandler委托类型.事件处理程序返回void 以便他们可以发送每个事件到多个目标方法.任何值目标返回将丢失在第一次通话之后."

当然,我们知道这一切,无论如何都在违反规则.(在任何情况下,如果愿意,所有事件处理程序都可以在其签名中使用标准的对象发送者"——这是一个不间断的更改.)

所以使用 SuppressMessageAttribute 可以解决问题:

[SuppressMessage("Microsoft.Design", "CA1009:DeclareEventHandlersCorrectly",Justification = "使用强类型 GenericEventHandler 事件处理程序模式.")]

我希望这种方法在未来的某个时候成为标准.它真的很好用.

感谢大家的意见,我真的很感激...

迈克

解决方案

微软似乎已经注意到了这一点,因为现在 MSDN 上有一个类似的例子:

通用代表

I fully realize that what I am proposing does not follow the .NET guidelines, and, therefore, is probably a poor idea for this reason alone. However, I would like to consider this from two possible perspectives:

(1) Should I consider using this for my own development work, which is 100% for internal purposes.

(2) Is this a concept that the framework designers could consider changing or updating?

I am thinking about using an event signature that utilizes a strong typed 'sender', instead of typing it as 'object', which is the current .NET design pattern. That is, instead of using a standard event signature that looks like this:

class Publisher
{
    public event EventHandler<PublisherEventArgs> SomeEvent;
}

I am considering using an event signature that utilizes a strong-typed 'sender' parameter, as follows:

First, define a "StrongTypedEventHandler":

[SerializableAttribute]
public delegate void StrongTypedEventHandler<TSender, TEventArgs>(
    TSender sender,
    TEventArgs e
)
where TEventArgs : EventArgs;

This is not all that different from an Action<TSender, TEventArgs>, but by making use of the StrongTypedEventHandler, we enforce that the TEventArgs derives from System.EventArgs.

Next, as an example, we can make use of the StrongTypedEventHandler in a publishing class as follows:

class Publisher
{
    public event StrongTypedEventHandler<Publisher, PublisherEventArgs> SomeEvent;

    protected void OnSomeEvent()
    {
        if (SomeEvent != null)
        {
            SomeEvent(this, new PublisherEventArgs(...));
        }
    }
}

The above arrangement would enable subscribers to utilize a strong-typed event handler that did not require casting:

class Subscriber
{
    void SomeEventHandler(Publisher sender, PublisherEventArgs e)
    {           
        if (sender.Name == "John Smith")
        {
            // ...
        }
    }
}

I do fully realize that this breaks with the standard .NET event-handling pattern; however, keep in mind that contravariance would enable a subscriber to use a traditional event handling signature if desired:

class Subscriber
{
    void SomeEventHandler(object sender, PublisherEventArgs e)
    {           
        if (((Publisher)sender).Name == "John Smith")
        {
            // ...
        }
    }
}

That is, if an event handler needed to subscribe to events from disparate (or perhaps unknown) object types, the handler could type the 'sender' parameter as 'object' in order to handle the full breadth of potential sender objects.

Other than breaking convention (which is something that I do not take lightly, believe me) I cannot think of any downsides to this.

There may be some CLS compliance issues here. This does run in Visual Basic .NET 2008 100% fine (I've tested), but I believe that the older versions of Visual Basic .NET through 2005 do not have delegate covariance and contravariance. [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.] There may be other .NET languages that also have a problem with this, I can't be sure.

But I do not see myself developing for any language other than C# or Visual Basic .NET, and I do not mind restricting it to C# and VB.NET for .NET Framework 3.0 and above. (I could not imagine going back to 2.0 at this point, to be honest.)

Can anyone else think of a problem with this? Or does this simply break with convention so much that it makes people's stomachs turn?

Here are some related links that I've found:

(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]

I am interested in anyone's and everyone's opinion on this...

Thanks in advance,

Mike

Edit #1: This is in response to Tommy Carlier's post :

Here's a full working example that shows that both strong-typed event handlers and the current standard event handlers that use a 'object sender' parameter can co-exist with this approach. You can copy-paste in the code and give it a run:

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: This is in response to Andrew Hare's statement regarding covariance and contravariance and how it applies here. Delegates in the C# language have had covariance and contravariance for so long that it just feels "intrinsic", but it's not. It might even be something that is enabled in the CLR, I don't know, but Visual Basic .NET did not get covariance and contravariance capability for its delegates until the .NET Framework 3.0 (VB.NET 2008). And as a result, Visual Basic.NET for .NET 2.0 and below would not be able to utilize this approach.

For example, the above example can be translated into VB.NET as follows:

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.NET 2008 can run it 100% fine. But I've now tested it on VB.NET 2005, just to be sure, and it does not compile, stating:

Method 'Public Sub SomeEventHandler(sender As Object, e As vbGenericEventHandling.GenericEventHandling.PublisherEventArgs)' does not have the same signature as delegate 'Delegate Sub StrongTypedEventHandler(Of TSender, TEventArgs As System.EventArgs)(sender As Publisher, e As PublisherEventArgs)'

Basically, delegates are invariant in VB.NET versions 2005 and below. I actually thought of this idea a couple of years ago, but VB.NET's inability to deal with this bothered me... But I've now moved solidly to C#, and VB.NET can now handle it, so, well, hence this post.

Edit: Update #3

Ok, I have been using this quite successfully for a while now. It really is a nice system. I decided to name my "StrongTypedEventHandler" as "GenericEventHandler", defined as follows:

[SerializableAttribute]
public delegate void GenericEventHandler<TSender, TEventArgs>(
    TSender sender,
    TEventArgs e
)
where TEventArgs : EventArgs;

Other than this renaming, I implemented it exactly as discussed above.

It does trip over FxCop rule CA1009, which states:

"By convention, .NET events have two parameters that specify the event sender and event data. Event handler signatures should follow this form: void MyEventHandler( object sender, EventArgs e). The 'sender' parameter is always of type System.Object, even if it is possible to employ a more specific type. The 'e' parameter is always of type System.EventArgs. Events that do not provide event data should use the System.EventHandler delegate type. Event handlers return void so that they can send each event to multiple target methods. Any value returned by a target would be lost after the first call."

Of course, we know all this, and are breaking the rules anyway. (All event handlers can use the standard 'object Sender' in their signature if preferred in any case -- this is a non-breaking change.)

So the use of a SuppressMessageAttribute does the trick:

[SuppressMessage("Microsoft.Design", "CA1009:DeclareEventHandlersCorrectly",
    Justification = "Using strong-typed GenericEventHandler<TSender, TEventArgs> event handler pattern.")]

I hope that this approach becomes the standard at some point in the future. It really works very nicely.

Thanks for all your opinions guys, I really appreciate it...

Mike

解决方案

It seems Microsoft has picked up on this as a similar example is now on MSDN:

Generic Delegates

这篇关于.NET 中的事件签名——使用强类型的“发送者"?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆