为什么.NET生态系统中的TEventArgs不能在标准事件模式中保持一致?

时间:2019-02-27 14:03:35

标签: c# .net .net-core contravariance

在学习有关.NET的标准事件模型的更多信息时,我发现在C#中引入泛型之前,处理事件的方法由以下委托类型表示:

//
// 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中引入了泛型之后,我认为这种委托类型是使用泛型重写的:

//
// 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 类型参数没有设置为 contravariant

如果我没记错的话,建议使在代理签名中作为形式参数出现的类型参数与在代理签名协变量中作为返回类型的类型参数相反。

在约瑟夫·阿尔巴哈里(Joseph Albahari)的书《简而言之C#》中,我引用:

  

如果您要定义一个通用的委托人类型,那么好的做法是:

     
      
  • 将仅在返回值上使用的类型参数标记为协变(出)。
  •   
  • 将仅在参数上使用的所有类型参数标记为反变量(输入)。
  •   
     

这样做可以让转换过程在尊重他人的情况下自然发挥作用   类型之间的继承关系。

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

如下:

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

先谢谢了。

已编辑以阐明第二个问题:

似乎以前对TEventArgs(其中TEventArgs:EventArgs )的通用约束已经被Microsoft删除,因此看起来设计团队意识到这没有多大实际意义。

我修改了答案,以包含

的一些屏幕截图

.NET reference source

enter image description here

1 个答案:

答案 0 :(得分:38)

首先,要解决该问题的注释中的一些问题:我通常会大力反对“为什么不这样做”的问题,因为很难找到世界上每个人选择不执行此工作的明确原因< / em>,并且因为所有工作默认情况下都不完成。相反,您必须找到工作的理由,并从其他工作中拿走对工作而言不太重要的资源。

此外,这种形式的“为什么不这样”的问题,询问在特定公司工作的人的动机和选择,可能只会由做出该决定的人来回答,而这些人可能不在这里。

但是,在这种情况下,我们可以对我关闭“为什么不”问题的一般规则作例外处理,因为该问题说明了我从未写过的关于委托协方差的重要观点。 < / p>

我没有做出让事件代表不变的决定,但是如果我能够做到这一点,出于两个原因,我会让事件代表不变。

首先纯粹是“鼓励良好做法”。事件处理程序通常是为处理特定事件而专门设计的,因此,我意识到没有充分的理由使使用签名中不匹配的委托作为处理程序变得比现在更容易,即使这些不匹配可能是通过方差处理。事件处理程序与应该处理的事件在各个方面都完全匹配,这使我更加自信,开发人员在构造事件驱动的工作流时知道他们在做什么。

那是一个很弱的原因。更有力的原因也是更悲惨的原因。

我们知道,可以将泛型委托类型的返回类型设为协变,而将其参数类型设为相反。我们通常会在分配兼容性的背景下考虑方差。也就是说,如果手头有一个Func<Mammal, Mammal>,我们可以将其分配给类型为Func<Giraffe, Animal>的变量,并知道其基础功能将始终以哺乳动物为生-因为现在它只会得到长颈鹿- -并且将永远归还动物-因为它归还哺乳动物。

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

类似字段的事件是使用委托求和来实现的;这就是为什么将处理程序添加到事件表示为+=的原因。 (我不是这种语法的忠实拥护者,但是我们现在仍然坚持使用它。)

尽管这两个功能彼此独立运行良好,但在组合时却无法正常工作。当我实现委托方差时,我们的测试很快就发现CLR中存在许多与委托添加有关的错误,这些错误由于启用了方差的转换而导致底层委托类型不匹配。这些错误自CLR 2.0起就存在,但是直到C#4.0为止,没有主流语言公开过这些错误,也没有为它们编写任何测试用例,等等。

可悲的是,我不记得这些错误的复制者是什么。那是十二年前的事,我不知道我是否还把任何笔记藏在某个地方的磁盘上。

当时,我们与CLR团队合作,尝试解决下一个CLR版本中的错误,但与风险相比,它们的优先级还不够高。在这些发行版中,像IEnumerable<T>IComparable<T>之类的许多类型都成为了变体,而FuncAction类型也变了,但是很少添加使用变体转换将两个不匹配的Func在一起。但是对于活动代表来说,他们生活中的唯一目的是加在一起。它们将一直被添加在一起,并且如果它们是变体,则存在将这些错误暴露给很多用户的风险。

在C#4之后不久,我就不再关注这些问题了,老实说,我不知道这些问题是否曾经得到解决。尝试以各种组合方式将一些不匹配的代表加在一起,看看是否有不好的事情发生!

所以这是一个很好的但不幸的原因,为什么不让他们在C#4.0发行时间框架内变事件委托。我不知道是否还有充分的理由。您必须问CLR小组的成员。