.NET 中的事件签名——使用强类型“发送者”? [关闭]

2023-12-09

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

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

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

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

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

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

首先,定义一个“Strong TypedEventHandler”:

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

这与 Action 并没有什么不同,但是通过使用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% 正常(我已经测试过),但我相信 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# 简单事件引发 - 使用“sender”与自定义 EventArgs [StackOverflow 2009]

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

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

提前致谢,

Mike

Edit #1:这是为了回应汤米·卡利尔的帖子:

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

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:这是为了回应安德鲁·黑尔的声明关于协变和逆变及其如何在这里应用。 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

VB.NET 2008 可以 100% 正常运行。但我现在已经在 VB.NET 2005 上对其进行了测试,只是为了确定,它无法编译,并指出:

方法“公共子” SomeEventHandler(发送者作为对象,e 作为 vbGenericEventHandling.GenericEventHandling.PublisherEventArgs)' 没有相同的签名 delegate '委托子 StrongTypedEventHandler(TSender 的, TEventArgs 作为 System.EventArgs)(sender 作为出版商,e 作为 发布者事件参数)'

基本上,委托在 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( 对象发送者, 事件参数 e). “发送者”参数 始终是 System.Object 类型,甚至 如果可以雇用更多 具体类型。 'e' 参数是 始终为 System.EventArgs 类型。 不提供事件数据的事件 应该使用 System.EventHandler 委托类型。事件处理程序返回 void 以便他们可以发送每个事件 到多个目标方法。任意值 目标返回的信息将会丢失 在第一次通话之后。”

当然,我们知道这一切,但无论如何都会违反规则。 (如果在任何情况下都愿意的话,所有事件处理程序都可以在其签名中使用标准的“对象发送者”——这是一个非破坏性的更改。)

所以使用一个SuppressMessageAttribute诀窍是:

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

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

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

Mike


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

通用代表

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

.NET 中的事件签名——使用强类型“发送者”? [关闭] 的相关文章

随机推荐