C# .NET 中的事件签名——使用强类型的“发送者”?

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/1046016/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-06 06:54:44  来源:igfitidea点击:

Event Signature in .NET -- Using a Strong Typed 'Sender'?

c#.netvb.netevents

提问by Mike Rosenblum

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:

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

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

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

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

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

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:

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

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

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

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

First, define a "StrongTypedEventHandler":

首先,定义一个“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.

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

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

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

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:

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

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.

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

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.)

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

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]

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

(2) C# simple Event Raising - using “sender” vs. custom EventArgs [StackOverflow 2009]

(2) C# 简单事件引发 - 使用“sender”与自定义 EventArgs [StackOverflow 2009]

(3) Event signature pattern in .net [StackOverflow 2008]

(3) .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 :

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

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 statementregarding 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.

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

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

比如上面的例子可以翻译成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 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:

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

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)'

方法 'Public Sub SomeEventHandler(sender As Object, e As vbGenericEventHandling.GenericEventHandling.PublisherEventArgs)' 与委托 '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.

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

Edit: Update #3

编辑:更新#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:

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

[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:

它确实绊倒了 FxCop 规则 CA1009,其中指出:

"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."

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

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 SuppressMessageAttributedoes the trick:

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

[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

麦克风

采纳答案by Bas

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

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

Generic Delegates

通用委托

回答by Andrew Hare

I take issue with the following statements:

我对以下陈述有异议:

  • I believe that the older versions of Visual Basic .NET through 2005 do not have delegate covariance and contravariance.
  • I do fully realize that this verges on blasphemy.
  • 我相信 Visual Basic .NET 到 2005 年的旧版本没有委托协变和逆变。
  • 我完全意识到这几乎是亵渎神灵。

First of all, nothing you have done here has anything to do with covariance or contravariance.(Edit:The previous statement is wrong, for more information please see Covariance and Contravariance in Delegates) This solution will work just fine in all CLR versions 2.0 and up (obviously this will notwork in a CLR 1.0 application as it uses generics).

首先,您在这里所做的一切都与协变或逆变无关。编辑:前面的陈述是错误的,有关更多信息,请参阅委托中的协变和逆变)该解决方案在所有 CLR 2.0 及更高版本中都可以正常工作(显然这在 CLR 1.0 应用程序中不起作用,因为它使用泛型)。

Secondly, I strongly disagree that your idea verges on "blasphemy" as this is a wonderful idea.

其次,我强烈不同意您的想法接近“亵渎”,因为这是一个绝妙的想法。

回答by BFree

What you're proposing does make alot of sense actually, and I just wonder if this is one of those things that's simply the way it is because it was originally designed before generics, or if there's a real reason for this.

你所提议的实际上确实很有意义,我只是想知道这是否只是因为它最初是在泛型之前设计的那样的事情之一,或者是否有真正的原因。

回答by Michael Meadows

I don't think there's anything wrong with what you want to do. For the most part, I suspect that the object senderparameter remains in order to continue to support pre 2.0 code.

我不认为你想做的事情有什么问题。在大多数情况下,我怀疑object sender保留该参数是为了继续支持 2.0 之前的代码。

If you really want to make this change for a public API, you might want to consider creating your own base EvenArgs class. Something like this:

如果您真的想为公共 API 进行此更改,您可能需要考虑创建自己的基础 EvenArgs 类。像这样的东西:

public class DataEventArgs<TSender, TData> : EventArgs
{
    private readonly TSender sender, TData data;

    public DataEventArgs(TSender sender, TData data)
    {
        this.sender = sender;
        this.data = data;
    }

    public TSender Sender { get { return sender; } }
    public TData Data { get { return data; } }
}

Then you can declare your events like this

然后你可以像这样声明你的事件

public event EventHandler<DataEventArgs<MyClass, int>> SomeIndexSelected;

And methods like this:

和这样的方法:

private void HandleSomething(object sender, EventArgs e)

will still be able to subscribe.

仍然可以订阅。

EDIT

编辑

That last line made me think a bit... You should actually be able to implement what you propose without breaking any outside functionality since the runtime has no problem downcasting parameters. I would still lean toward the DataEventArgssolution (personally). I would do so, however knowing that it is redundant, since the sender is stored in the first parameter and as a property of the event args.

最后一行让我有点思考......实际上,您应该能够在不破坏任何外部功能的情况下实现您的建议,因为运行时向下转换参数没有问题。我仍然倾向于DataEventArgs解决方案(个人)。我会这样做,但是知道它是多余的,因为发送者存储在第一个参数中并作为事件 args 的属性。

One benefit of sticking with the DataEventArgsis that you can chain events, changing the sender (to represent the last sender) while the EventArgs retains the original sender.

坚持使用 的一个好处DataEventArgs是您可以链接事件,更改发件人(代表最后一个发件人)而 EventArgs 保留原始发件人。

回答by Otávio Décio

I think it is a great idea and MS might simply not have the time or interest to invest in making this better as for example when they moved from ArrayList to generic based lists.

我认为这是一个好主意,MS 可能根本没有时间或兴趣投资使其变得更好,例如当他们从 ArrayList 转移到基于泛型的列表时。

回答by Tommy Carlier

With the current situation (sender is object), you can easily attach a method to multiple events:

以目前的情况(发送者是对象),你可以很容易地将一个方法附加到多个事件上:

button.Click += ClickHandler;
label.Click += ClickHandler;

void ClickHandler(object sender, EventArgs e) { ... }

If sender would be generic, the target of the click-event would not be of type Button or Label, but of type Control (because the event is defined on Control). So some events on the Button-class would have a target of type Control, others would have other target types.

如果 sender 是通用的,则单击事件的目标将不是 Button 或 Label 类型,而是 Control 类型(因为事件是在 Control 上定义的)。因此,Button 类上的某些事件将具有 Control 类型的目标,而其他事件将具有其他目标类型。

回答by Scott Weinstein

Go for it. For non component based code, I often simplify Event signatures to be simply

去吧。对于非基于组件的代码,我经常将事件签名简化为简单的

public event Action<MyEventType> EventName

where MyEventTypedoes not inherit from EventArgs. Why bother, if I never intend to use any of the members of EventArgs.

whereMyEventType不继承自EventArgs. 如果我从不打算使用 EventArgs 的任何成员,那又何必呢。

回答by supercat

From what I understand, the "Sender" field is always supposed to refer to the object which holds the event subscription. If I had my druthers, there would also be a field holding information sufficient to unsubscribe an event should it become necessary(*) (consider, for example, a change-logger which subscribes to 'collection-changed' events; it contains two parts, one of which does the actual work and holds the actual data, and the other of which provides a public interface wrapper, the main part could hold a weak reference to the wrapper part. If the wrapper part gets garbage-collected, that would mean there was no longer anybody interested in the data that was being collected, and the change-logger should thus unsubscribe from any event it receives).

据我了解,“发件人”字段始终应该指代持有事件订阅的对象。如果我有我的 druthers,也会有一个字段保存足够的信息,以便在必要时取消订阅事件(*)(例如,考虑订阅“collection-changed”事件的更改记录器;它包含两部分,其中一个执行实际工作并保存实际数据,另一个提供公共接口包装器,主要部分可以持有对包装器部分的弱引用。如果包装器部分被垃圾收集,那将意味着不再有人对正在收集的数据感兴趣,因此更改记录器应该取消订阅它收到的任何事件)。

Since it's possible that an object may send events on behalf of another object, I can see some potential usefulness for having a "sender" field which is of Object type, and for having the EventArgs-derived field contain a reference to the object which should be acted upon. The usefuless of the "sender" field, however, is probably limited by the fact that there's no clean way for an object to unsubscribe from an unknown sender.

因为一个对象可能代表另一个对象发送事件,我可以看到一些潜在的用处,让一个对象类型的“发送者”字段,以及让 EventArgs 派生的字段包含对对象的引用,该对象应该被采取行动。然而,“发件人”字段的用处可能受到以下事实的限制:对象无法从未知发件人处取消订阅。

(*) Actually, a cleaner way of handling unsubscriptions would be to have a multicast delegate type for functions which return Boolean; if a function called by such a delegate returns True, the delegate would be patched to remove that object. This would mean that delegates would no longer be truly immutable, but it should be possible to effect such change in thread-safe manner (e.g. by nulling out the object reference and having the multicast delegate code ignore any embedded null object references). Under this scenario, an attempt to publish and event to a disposed object could be handled very cleanly, no matter where the event came from.

(*) 实际上,处理取消订阅的一种更简洁的方法是为返回布尔值的函数使用多播委托类型;如果此类委托调用的函数返回 True,则将修补该委托以删除该对象。这意味着委托将不再是真正不可变的,但应该可以以线程安全的方式实现这种更改(例如,通过清除对象引用并让多播委托代码忽略任何嵌入的空对象引用)。在这种情况下,无论事件来自何处,都可以非常干净地处理向已处理对象发布和事件的尝试。

回答by Lu4

Looking back to blasphemy as the only reason for making sender an object type (if to omit problems with contravariance in VB 2005 code, which is a Microsoft's blunder IMHO), can anyone suggest at least theoretical motive for nailing the second argument to EventArgs type. Going even further, is there a good reason to conform with Microsoft's guidelines and conventions in this particular case?

回顾亵渎是使发送者成为对象类型的唯一原因(如果忽略 VB 2005 代码中的逆变问题,这是微软的错误恕我直言),任何人都可以提出至少理论上的动机将第二个参数固定到 EventArgs 类型。更进一步,在这种特殊情况下是否有充分的理由遵守 Microsoft 的准则和约定?

Having need to develop another EventArgs wrapper for another data that we want to pass inside event handler seems odd, why can't straightly pass that data there. Consider the following sections of code

需要为我们想要在事件处理程序中传递的另一个数据开发另一个 EventArgs 包装器似乎很奇怪,为什么不能直接在那里传递该数据。考虑以下代码段

[Example 1]

[示例1]

public delegate void ConnectionEventHandler(Server sender, Connection connection);

public partial class Server
{
    protected virtual void OnClientConnected(Connection connection)
    {
        if (ClientConnected != null) ClientConnected(this, connection);
    }

    public event ConnectionEventHandler ClientConnected;
}

[Example 2]

[示例2]

public delegate void ConnectionEventHandler(object sender, ConnectionEventArgs e);

public class ConnectionEventArgs : EventArgs
{
    public Connection Connection { get; private set; }

    public ConnectionEventArgs(Connection connection)
    {
        this.Connection = connection;
    }
}

public partial class Server
{
    protected virtual void OnClientConnected(Connection connection)
    {
        if (ClientConnected != null) ClientConnected(this, new ConnectionEventArgs(connection));
    }

    public event ConnectionEventHandler ClientConnected;
}

回答by Pierre Arnaud

The Windows Runtime (WinRT) introduces a TypedEventHandler<TSender, TResult>delegate, which does exactly what your StrongTypedEventHandler<TSender, TResult>does, but apparently without the constraint on the TResulttype parameter:

Windows 运行时 (WinRT) 引入了一个TypedEventHandler<TSender, TResult>委托,它完全符合您的要求StrongTypedEventHandler<TSender, TResult>,但显然没有对TResult类型参数的约束:

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

The MSDN documentation is here.

MSDN 文档在这里