为什么在 .NET 生态系统的标准事件模式中没有将 TEventArgs 设为逆变? [英] Why wasn't TEventArgs made contravariant in the standard event pattern in the .NET ecosystem?

查看:23
本文介绍了为什么在 .NET 生态系统的标准事件模式中没有将 TEventArgs 设为逆变?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

更多地了解.NET中的标准事件模型时,我发现在C#中引入泛型之前,处理事件的方法是由这种委托类型表示的:

<代码>////概括://表示将处理没有事件数据的事件的方法.////参数://发件人://事件源.////电子://一个不包含事件数据的对象.公共委托 void EventHandler(object sender, EventArgs e);

但是在 C# 2 中引入泛型之后,我认为这个委托类型是使用泛型重写的:

<代码>////概括://表示当事件提供数据时将处理事件的方法.////参数://发件人://事件源.////电子://一个包含事件数据的对象.////类型参数://TEventArgs://事件产生的事件数据的类型.公共委托 void EventHandler(对象发送者,TEventArgs e);

我有两个问题:

首先,为什么 TEventArgs 类型参数没有逆变?

如果我没记错的话,建议将在委托签名中作为形式参数出现的类型参数和将成为委托签名协变中的返回类型的类型参数进行逆变.

在 Joseph Albahari 的书《C# in a Nutshell》中,我引用:

<块引用>

如果您要定义泛型委托类型,最好的做法是:

  • 将仅用于返回值的类型参数标记为协变 (out).
  • 将仅用于参数的任何类型参数标记为逆变 (in).

这样做可以通过尊重自然地进行转换类型之间的继承关系.

第二个问题:为什么没有通用约束来强制 TEventArgs 从 System.EventArgs 派生?

如下:

public delegate void EventHandler(object source, TEventArgs e) 其中 TEventArgs : EventArgs;

提前致谢.

编辑以澄清第二个问题:

似乎 TEventArgs 上的通用约束(其中 TEventArgs : EventArgs)之前存在并且被 Microsoft 删除,因此设计团队似乎意识到它没有多大实际意义.

我编辑了我的答案以包含来自

的一些屏幕截图

解决方案

首先,解决问题评论中的一些问题:我通常会强烈反对为什么不"的问题,因为很难找到简洁的原因世界上的每个人都选择不做这项工作,因为所有工作都不是默认完成的.相反,您必须找到一个工作的理由,并从其他工作中拿走资源,而这些资源对于做这件事来说并不重要.

此外,这种形式的为什么不"问题,询问在特定公司工作的人的动机和选择,可能只有做出该决定的人才能回答,他们可能不在身边.

>

然而,在这种情况下,我们可以对我关闭为什么不"问题的一般规则做一个例外,因为这个问题说明了我以前从未写过的关于委托协方差的重要观点.

我没有决定保持事件委托不变,但如果我能够这样做,我会保持事件委托不变,原因有两个.

第一个纯粹是鼓励良好做法"的观点.事件处理程序通常是专门为处理特定事件而构建的,我知道没有充分的理由比使用签名中存在不匹配的委托作为处理程序更容易,即使这些不匹配可以通过方差处理.一个在各个方面都与它应该处理的事件完全匹配的事件处理程序让我更有信心,开发人员在构建事件驱动的工作流时知道他们在做什么.

这是一个很弱的理由.更强烈的原因也是更悲伤的原因.

我们知道,泛型委托类型的返回类型可以是协变的,而参数类型可以是逆变的;我们通常在分配兼容性的上下文中考虑方差.也就是说,如果我们手头有一个 Func,我们可以将它赋值给一个 Func 类型的变量,并且知道底层的函数将始终采用哺乳动物——因为现在它只会得到长颈鹿——并且总是会返回一只动物——因为它会返回哺乳动物.

但我们也知道委托可以加在一起;委托是不可变的,因此将两个委托加在一起会产生第三个;和是被加数的顺序组合.

类字段事件使用委托求和实现;这就是为什么向事件添加处理程序表示为 +=.(我不是这种语法的忠实粉丝,但我们现在坚持使用它.)

尽管这两个功能彼此独立运行良好,但它们在组合中运行效果不佳.当我实现委托变体时,我们的测试很快发现 CLR 中存在许多关于委托添加的错误,其中底层委托类型由于启用变体的转换而不匹配.这些 bug 从 CLR 2.0 开始就存在,但直到 C# 4.0,主流语言都没有暴露过这些 bug,也没有为它们编写过测试用例,等等.

遗憾的是,我不记得错误的复制者是什么;那是 12 年前的事了,我不知道我是否还有任何笔记藏在某个地方的磁盘上.

我们当时与 CLR 团队合作,尝试在下一版本的 CLR 中解决这些错误,但与风险相比,它们的优先级不够高.许多类型,如 IEnumerableIComparable 等,在这些版本中都变体了,FuncAction 类型,但很少使用变体转换将两个不匹配的 Func 加在一起.但对于事件委托来说,他们生活的唯一目的就是相加;它们会一直被添加到一起,如果它们是变体,就会有将这些错误暴露给大量用户的风险.

在 C# 4 之后不久我就忘记了这些问题,老实说,我不知道它们是否得到了解决.尝试以各种组合添加一些不匹配的委托,看看是否会发生任何不好的事情!

所以这是一个很好但不幸的原因,为什么在 C# 4.0 发布时间范围内使事件委托变体.是否还有充分的理由,我不知道.您必须询问 CLR 团队中的某个人.

When learning more about the standard event model in .NET, I found that before introducing generics in C#, the method that will handle an event is represented by this delegate type:

//
// Summary:
//     Represents the method that will handle an event that has no event data.
//
// Parameters:
//   sender:
//     The source of the event.
//
//   e:
//     An object that contains no event data.
public delegate void EventHandler(object sender, EventArgs e);

But after generics were introduced in C# 2, I think this delegate type was rewritten using genericity:

//
// Summary:
//     Represents the method that will handle an event when the event provides data.
//
// Parameters:
//   sender:
//     The source of the event.
//
//   e:
//     An object that contains the event data.
//
// Type parameters:
//   TEventArgs:
//     The type of the event data generated by the event.
public delegate void EventHandler<TEventArgs>(object sender, TEventArgs e);

I have two questions here:

First, why wasn't the TEventArgs type parameter made contravariant ?

If I'm not mistaken it is recommended to make the type parameters that appear as formal parameters in a delegate's signature contravariant and the type parameter that will be the return type in the delegate signature covariant.

In Joseph Albahari's book, C# in a Nutshell, I quote:

If you’re defining a generic delegate type, it’s good practice to:

  • Mark a type parameter used only on the return value as covariant (out).
  • Mark any type parameters used only on parameters as contravariant (in).

Doing so allows conversions to work naturally by respecting inheritance relationships between types.

Second question: Why was there no generic constraint to enforce that the TEventArgs derive from System.EventArgs?

As follows:

public delegate void EventHandler<TEventArgs>  (object source, TEventArgs e) where TEventArgs : EventArgs; 

Thanks in advance.

Edited to clarify the second question:

It seems like the generic constraint on TEventArgs (where TEventArgs : EventArgs) was there before and it was removed by Microsoft, so seemingly the design team realized that it didn’t make much practical sense.

I edited my answer to include some of the screenshots from

.NET reference source

解决方案

First off, to address some concerns in the comments to the question: I generally push back hard on "why not" questions because it's hard to find concise reasons why everyone in the world chose to not do this work, and because all work is not done by default. Rather, you have to find a reason to do work, and take away resources from other work that is less important to do it.

Moreover, "why not" questions of this form, which ask about the motivations and choices of people who work at a particular company may only be answerable by the people who made that decision, who are probably not around here.

However, in this case we can make an exception to my general rule of closing "why not" questions because the question illustrates an important point about delegate covariance that I have never written about before.

I did not make the decision to keep event delegates non-variant, but had I been in a position to do so, I would have kept event delegates non-variant, for two reasons.

The first is purely an "encourage good practices" point. Event handlers are usually purpose-built for handling a particular event, and there is no good reason I'm aware of to make it easier than it already is to use delegates that have mismatches in the signature as handlers, even if those mismatches can be dealt with through variance. An event handler that matches exactly in every respect the event it is supposed to be handling gives me more confidence that the developer knows what they're doing when constructing an event-driven workflow.

That's a pretty weak reason. The stronger reason is also the sadder reason.

As we know, generic delegate types can be made covariant in their return types and contravariant in their parameter types; we normally think of variance in the context of assignment compatibility. That is, if we have a Func<Mammal, Mammal> in hand, we can assign it to a variable of type Func<Giraffe, Animal> and know that the underlying function will always take a mammal -- because now it will only get giraffes -- and will always return an animal -- because it returns mammals.

But we also know that delegates may be added together; delegates are immutable, so adding two delegates together produces a third; the sum is the sequential composition of the summands.

Field-like events are implemented using delegate summation; that's why adding a handler to an event is represented as +=. (I am not a big fan of this syntax, but we're stuck with it now.)

Though both these features work well independently of each other, they work poorly in combination. When I implemented delegate variance, our tests discovered in short order that there were a number of bugs in the CLR regarding delegate addition where the underlying delegate types were mismatched due to variance-enabled conversions. These bugs had been there since CLR 2.0, but until C# 4.0, no mainstream language had ever exposed the bugs, no test cases had been written for them, and so on.

Sadly, I do not recall what the reproducers for the bugs were; it was twelve years ago and I do not know if I still have any notes on it tucked away on a disk somewhere.

We worked with the CLR team at the time to try and get these bugs addressed for the next version of the CLR, but they were not considered high enough priority compared to their risk. Lots of types like IEnumerable<T> and IComparable<T> and so on were made variant in those releases, as were the Func and Action types, but it is rare to add together two mismatched Funcs using a variant conversion. But for event delegates, their only purpose in life is to be added together; they would be added together all the time, and had they been variant, there would have been risk of exposing these bugs to a great many users.

I lost track of the issues shortly after C# 4 and I honestly do not know if they were ever addressed. Try adding together some mismatched delegates in various combinations and see if anything bad happens!

So that's a good but unfortunate reason why to not make event delegates variant in the C# 4.0 release timeframe. Whether there is still a good reason, I don't know. You'd have to ask someone on the CLR team.

这篇关于为什么在 .NET 生态系统的标准事件模式中没有将 TEventArgs 设为逆变?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

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