使用强类型的“发件人”?

我完全意识到我所提议的并不遵循.NET的指导方针,因此,仅仅因为这个原因可能是一个糟糕的主意。 不过,我想从两个可能的角度来考虑这一点:

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

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

我在考虑使用使用强类型的“发件人”的事件签名,而不是将其作为当前.NET设计模式的“对象”输入。 也就是说,不是使用如下所示的标准事件签名:

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

我正在考虑使用使用强类型“发件人”参数的事件签名,如下所示:

首先,定义一个“StrongTypedEventHandler”:

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

这与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(...));
        }
    }
}

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

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',以便处理潜在发送者对象的全部范围。

除了打破常规(这是我不会轻视的,相信我),我想不出任何缺点。

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

但是我没有看到自己为C#或Visual Basic .NET以外的任何语言开发,我不介意将它限制在.NET Framework 3.0及更高版本的C#和VB.NET中。 (我不能想象现在回到2.0,说实话。)

任何人都可以想到这个问题吗? 或者这是否仅仅因为会议的规定而大打折扣呢?这会让人们的胃口转向?

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

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

(2)C#简单事件提升 - 使用“发件人”与自定义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();
        }
    }
}

编辑#2:这是对安德鲁海尔关于协变和逆变以及它如何应用于此的陈述的回应。 C#语言的代表长期以来一直具有协变性和相反性,以至于它只是感觉“内在”,但事实并非如此。 它甚至可能是在CLR中启用的东西,我不知道,但是Visual Basic .NET在其.NET Framework 3.0(VB.NET 2008)之前并没有为它的代表获得协变和逆变能力。 结果,.NET 2.0及以下版本的Visual Basic.NET将无法使用这种方法。

例如,上面的例子可以被转换成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

VB.NET 2008可以100%正常运行。 但是现在我已经在VB.NET 2005上对它进行了测试,可以肯定的是,它不会编译,并指出:

方法'Public Sub SomeEventHandler(sender As Object,e As vbGenericEventHandling.GenericEventHandling.PublisherEventArgs)'与委托'Delegate Sub StrongTypedEventHandler(TSender,TEventArgs As System.EventArgs)没有相同的签名(sender As Publisher,e As 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;

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

它确实通过FxCop规则CA1009,其中规定:

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

当然,我们知道所有这些,并且正在违反规则。 (如果首选的话,所有事件处理程序都可以在其签名中使用标准“对象发件人” - 这是一项不会改变的改变。)

所以使用SuppressMessageAttribute做到这一点:

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

我希望这种方法在未来某个时候成为标准。 它非常好地工作。

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

麦克风


看起来微软已经接受了这一点,因为现在MSDN上有一个类似的例子:

通用代表


你提出的建议确实有很多意义,我只是想知道这是否是其中之一,因为它最初是在泛型之前设计的,或者真正有这个原因。


Windows Runtime(WinRT)引入了一个TypedEventHandler<TSender, TResult>委托,它完全符合你的StrongTypedEventHandler<TSender, TResult>所做的,但显然没有对TResult类型参数的限制:

public delegate void TypedEventHandler<TSender, TResult>(TSender sender,
                                                         TResult args);

MSDN文档在这里。

链接地址: http://www.djcxy.com/p/52931.html

上一篇: Using a Strong Typed 'Sender'?

下一篇: How do I "connect" a table view to a view controller