c# - 为什么在 .NET 生态系统的标准事件模式中没有将 TEventArgs 设为逆变?

标签 c# .net .net-core contravariance

在进一步了解 .NET 中的标准事件模型时,我发现在 C# 中引入泛型之前,将处理事件的方法由这种委托(delegate)类型表示:

//
// 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);

但是在 C# 2 中引入泛型之后,我认为这个委托(delegate)类型是使用泛型重写的:
//
// 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);

我在这里有两个问题:

首先,为什么不是 TEventArgs 类型参数制作 逆变 ?

如果我没有记错的话,建议将在委托(delegate)签名中作为形式参数出现的类型参数和将作为委托(delegate)签名协变中的返回类型的类型参数进行设置。

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

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.



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

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

提前致谢。

编辑以澄清第二个问题:

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

我编辑了我的答案以包含一些截图

.NET reference source

enter image description here

最佳答案

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

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

但是,在这种情况下,我们可以为我关闭“为什么不”问题的一般规则破例,因为 这个问题说明了我以前从未写过的关于委托(delegate)协方差的一个重要观点。

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

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

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

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

但是我们也知道代表可以加在一起;委托(delegate)是不可变的,因此将两个委托(delegate)加在一起会产生第三个;总和是被加数的顺序组成。

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

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

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

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

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

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

关于c# - 为什么在 .NET 生态系统的标准事件模式中没有将 TEventArgs 设为逆变?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/54907236/

相关文章:

C# 拖放在 Windows 7 上不起作用

c# - 将代码移至 viewModel 类绑定(bind)

c# - 将 ServiceDescription 代码移植到 .net Core

c# - xamarin.forms 与 VS 2015

c# - 是否可以使用 extern 和 override 修饰符指定一个函数?

c# - 如何将可枚举类型转换为 AsyncEnumerable 类型?

c++ - 在 C++/CLI 中从 native 转换为管理 unsigned short 的最快方法

c# - 尽管有效的正则表达式和激活的解析在 gitlab 中没有覆盖率报告

c# - 将 Microsoft.Azure.ServiceBus 替换为 Azure.Messaging.ServiceBus

c# - .NET 如何在启动期间访问 TOptions(选项模式)