为什么TEventArgs在.NET生态系统的标准事件模式中没有逆变?
Posted
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了为什么TEventArgs在.NET生态系统的标准事件模式中没有逆变?相关的知识,希望对你有一定的参考价值。
当我在.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类型参数不是逆变的?
如果我没有弄错,建议在委托的签名逆变中使用作为形式参数出现的类型参数,并在委托签名协变中作为返回类型的类型参数。
在Joseph Albahari的书中,C#in a Nutshell,我引述:
如果您要定义通用委托类型,那么最好:
- 将仅在返回值上使用的类型参数标记为协变(out)。
- 将仅用于参数的任何类型参数标记为逆变量(in)。
这样做可以通过尊重类型之间的继承关系来自然地进行转换。
第二个问题:为什么没有通用约束来强制执行TEventArgs派生自System.EventArgs?
如下:
public delegate void EventHandler<TEventArgs> (object source, TEventArgs e) where TEventArgs : EventArgs;
提前致谢。
编辑澄清第二个问题:
看起来像TEventArgs(TEventArgs:EventArgs)之前存在的泛型约束并且它被Microsoft删除了,所以看起来设计团队意识到它没有太多实际意义。
我编辑了我的答案,其中包含了一些截图
首先,为了解决问题评论中的一些问题:我一般都会回答“为什么不”的问题,因为很难找到简洁的理由,为什么世界上每个人都选择不做这项工作,而且因为所有的工作都不是默认完成。相反,你必须找到工作的理由,并从其他不那么重要的工作中拿走资源。
此外,这种形式的“为什么不”问题,询问在特定公司工作的人的动机和选择,可能只能由做出该决定的人负责,他们可能不在这里。
但是,在这种情况下,我们可以对我关闭“为什么不”问题的一般规则作出例外,因为这个问题说明了我之前从未写过的关于代表协方差的重要观点。
我没有决定让事件代表保持非变量,但如果我有能力这样做,我会让事件代表保持非变量,原因有两个。
第一个纯粹是“鼓励良好做法”的观点。事件处理程序通常是专门为处理特定事件而构建的,并且没有任何理由让我更容易使用签名中不匹配的代理作为处理程序,即使这些不匹配可能是通过差异处理。一个事件处理程序在每个方面完全匹配它应该处理的事件,这使我更有信心开发人员在构建事件驱动的工作流时知道他们在做什么。
这是一个非常弱的原因。更强烈的原因也是令人悲伤的原因。
我们知道,泛型委托类型的返回类型可以是协变的,参数类型也可以是逆变的。我们通常会考虑赋值兼容性的上下文中的差异。也就是说,如果我们手中有一个Func<Mammal, Mammal>
,我们可以将它分配给Func<Giraffe, Animal>
类型的变量,并且知道潜在的函数总是需要一个哺乳动物 - 因为现在它只会得到长颈鹿 - 而且总是会返回一只动物 - - 因为它回归哺乳动物。
但我们也知道代表可以加在一起;代表是不可变的,所以将两个代表加在一起会产生第三个代表; sum是summands的顺序组合。
类似于字段的事件使用委托求和来实现;这就是为事件添加处理程序的原因表示为+=
。 (我不是这种语法的忠实粉丝,但我们现在已经坚持了。)
虽然这两种功能在彼此独立的情况下运作良好,但它们的组合效果很差。当我实现委托方差时,我们的测试在短时间内发现CLR中存在许多关于委托添加的错误,其中由于启用了方差的转换,底层委托类型不匹配。这些错误自CLR 2.0以来一直存在,但直到C#4.0,没有主流语言暴露过这些错误,没有为它们编写测试用例,等等。
可悲的是,我不记得虫子的繁殖者是什么了;这是十二年前我不知道我是否还有任何记录隐藏在磁盘上的某个地方。
我们当时与CLR团队合作,尝试为下一版本的CLR解决这些问题,但与风险相比,他们的优先级不高。许多类型如IEnumerable<T>
和IComparable<T>
等在这些版本中变成了变体,Func
和Action
类型也是如此,但很少使用变体转换将两个不匹配的Func
s加在一起。但对于活动代表来说,他们生活中唯一的目的就是加在一起;它们会一直加在一起,如果它们是变体的,那么就有可能将这些错误暴露给很多用户。
在C#4之后不久我就忘记了这些问题,老实说我不知道他们是否曾经被解决过。尝试以不同的组合将一些不匹配的代表加在一起,看看是否有任何不良事件发生!
因此,为什么不在C#4.0发布时间框架中使事件委托变体是一个很好但不幸的原因。是否还有充分的理由,我不知道。您必须向CLR团队的某个人询问。
以上是关于为什么TEventArgs在.NET生态系统的标准事件模式中没有逆变?的主要内容,如果未能解决你的问题,请参考以下文章