.NET中的事件签名 - 使用强类型"发件人"?

Mik*_*lum 106 .net c# vb.net events

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

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

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

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

class Publisher
{
    public event EventHandler<PublisherEventArgs> SomeEvent;
}
Run Code Online (Sandbox Code Playgroud)

我正在考虑使用一个使用强类型'sender'参数的事件签名,如下所示:

首先,定义一个"StrongTypedEventHandler":

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

这与Action <TSender,TEventArgs>并没有什么不同,但通过使用StrongTypedEventHandler,我们强制执行TEventArgs派生System.EventArgs.

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

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

    protected void OnSomeEvent()
    {
        if (SomeEvent != null)
        {
            SomeEvent(this, new PublisherEventArgs(...));
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

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

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

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

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

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

除了破坏惯例(我不小心相信,相信我),我不能想到任何缺点.

这里可能存在一些CLS合规性问题.这确实在Visual Basic .NET 2008中运行100%(我已经测试过),但我相信Visual Basic .NET到2005年的旧版本没有委托协方差和逆变.[编辑:因为我已经进行了测试,确认:VB.NET 2005及以下无法处理这个问题,但VB.NET 2008年是100%的罚款.请参阅下面的"编辑#2".]可能还有其他.NET语言也存在此问题,我无法确定.

但我不认为自己开发的是除C#或Visual Basic .NET之外的任何语言,我不介意将其限制为C#和VB.NET for .NET Framework 3.0及更高版本.(老实说,我无法想象在这一点上回到2.0.)

其他人可以想到这个问题吗?或者这简直违反惯例,以至于它会让人的肚子转向?

以下是我发现的一些相关链接:

(1)活动设计指南[MSDN 3.5]

(2)C#simple Event Raising - 使用"sender"与自定义EventArgs [StackOverflow 2009]

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

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

提前致谢,

麦克风

编辑#1:这是对Tommy Carlier的帖子的回应:

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

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();
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

编辑#2:这是对Andrew Hare关于协方差和逆变的声明以及它如何应用的回应.C#语言的代表长期以来一直有协方差和逆变,只是觉得"内在",但事实并非如此.它甚至可能是在CLR中启用的东西,我不知道,但Visual Basic .NET在.NET Framework 3.0(VB.NET 2008)之前没有为其代理获得协方差和逆变能力.因此,Visual Basic.NET for .NET 2.0及更低版本将无法使用此方法.

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

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
Run Code Online (Sandbox Code Playgroud)

VB.NET 2008可以100%运行它.但我现在已经在VB.NET 2005上进行了测试,只是为了确定,并且它没有编译,说明:

方法'Public Sub SomeEventHandler(sender As Object,e as vbGenericEventHandling.GenericEventHandling.PublisherEventArgs)'与委托'Delegate Sub StrongTypedEventHandler(Of TSender,TEventArgs As System.EventArgs)(发件人为Publisher,e为PublisherEventArgs)的签名不同"

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

编辑:更新#3

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

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

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

它确实超过了FxCop规则CA1009,它规定:

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

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

所以使用a SuppressMessageAttribute就可以了:

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

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

感谢你们所有人的意见,我真的很感激...

麦克风

Bas*_*Bas 25

似乎微软已经开始接受这一点,因为类似的例子现在在MSDN上:

通用代表

  • +1啊,非常好.他们确实已经接受了这一点.这很好.但是,我希望他们在VS IDE中将其作为一种公认的模式,因为就像现在一样,在IntelliSense等方面使用这种模式更为尴尬. (2认同)

BFr*_*ree 13

你提出的建议实际上有很多意义,我只是想知道这是否就是那种简单的事情之一,因为它最初是在仿制药之前设计的,或者是否有真正的原因.


Pie*_*aud 13

Windows运行时(WinRT)引入了一个TypedEventHandler<TSender, TResult>委托,它完全按照您的方式StrongTypedEventHandler<TSender, TResult>执行,但显然没有对TResulttype参数的约束:

public delegate void TypedEventHandler<TSender, TResult>(TSender sender,
                                                         TResult args);
Run Code Online (Sandbox Code Playgroud)

MSDN文档在这里.

  • 它特别在TypedEventHandler文档中指出,如果没有事件数据,`args`将为`null`,因此它看起来确实在默认情况下远离使用本质上为空的对象.我猜测最初的想法是带有第二个参数类型`EventArgs`的方法可以处理任何事件,因为类型总是兼容的.他们现在可能意识到能够用一种方法处理多个不同的事件并不是那么重要. (3认同)

And*_*are 5

我对以下陈述提出异议:

  • 我相信到2005年的旧版Visual Basic .NET没有委托协方差和逆变.
  • 我完全意识到这就是亵渎神灵.

首先,你在这里所做的一切都与协方差或逆变无关. (编辑: 以前的说法是错误的,以获取更多信息,请参见协变和逆变的代表),该解决方案将工作在所有的CLR 2.0或更高版本就好了(显然这会不会因为它使用泛型在CLR 1.0应用程序的工作).

其次,我强烈反对你的想法接近"亵渎",因为这是一个很棒的主意.

  • 啊,有意思!似乎委托协方差/逆变是CLR的一部分,但(由于我不知道的原因)在最新版本之前VB.NET没有公开它.以下是Francesco Balena撰写的一篇文章,该文章展示了如何使用Reflection实现委托差异,如果没有语言本身启用的话:http://www.dotnet2themax.com/blogs/fbalena/PermaLink,guid,3741061e-5656-4f12-abe9 -1ada257deaa2.aspx. (4认同)
  • 嗨,安德鲁,谢谢你的大拇指!考虑到你的声誉水平,这对我来说意义重大......在协方差/逆变问题上:如果订阅者提供的代表与发布者事件的签名完全不符,则涉及协方差和逆变.C#从一开始就具有委托协方差和逆变,因此感觉固有,但VB.NET在.NET 3.0之前没有委托协方差和逆变.因此,VB.NET for .NET 2.0及更低版本将无法使用此系统.(请参阅上面"编辑#2"中添加的代码示例.) (2认同)

小智 5

我看了一下如何使用新的WinRT并根据其他意见来处理这个问题,并最终决定这样做:

[Serializable]
public delegate void TypedEventHandler<in TSender, in TEventArgs>(
    TSender sender,
    TEventArgs e
) where TEventArgs : EventArgs;
Run Code Online (Sandbox Code Playgroud)

考虑到在WinRT中使用名称TypedEventHandler,这似乎是最好的方法.