代表方差规则的怪异示例 - c#

在埃里克·利珀特(Eric Lippert)的博客文章中,简短地介绍了协方差和逆方差或方差,以及在诸如C#的书本中,都指出:

如果您要定义通用的委托人类型,则最好的做法是:

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

这样一来,通过尊重
类型之间的继承关系。

因此,我正在对此进行试验,并且找到了一个相当奇怪的示例。

使用此类的层次结构:

class Animal { }

class Mamal : Animal { }
class Reptile : Animal { }

class Dog : Mamal { }
class Hog : Mamal { }

class Snake : Reptile { }
class Turtle : Reptile { }

在尝试使用方法组到委托转换和委托到委托转换时,我编写了以下代码片段:

 // Intellisense is complaining here  
 Func<Dog, Reptile> func1 = (Mamal d) => new Reptile();

 // A local method that has the same return type and same parameter type as the lambda expression.
 Reptile GetReptile(Mamal d) => new Reptile();

 // Works here.  
 Func<Dog, Reptile> func2 = GetReptile;

为什么方差规则适用于局部方法而不适用于lambda表达式?

假定lambda表达式是一个未命名的方法,它代替了委托实例,并且编译器立即将lambda表达式转换为以下任意一种:

委托实例。
类型为Expression的表达式树。

我认为与:

 Func<Dog, Reptile> func1 = (Mamal d) => new Reptile();

发生的是来自类似的转换:

Func<Mamal, Reptile> => Func<Dog, Reptile>. 

代表之间的差异规则与方法组到代表的差异规则是否不同?

参考方案

让我稍微澄清一下您的问题。

这三件事可以转换为委托类型:(1)lambda(或C#2样式匿名方法),(2)方法组或本地方法,(3)另一个委托。在每种情况下,哪些协变量和反变量转换的规则在法律上是否不同?

是。

它们有何不同?

您应该阅读规范以获取确切的详细信息,但是要简短:

仅当将委托类型参数标记为协变或相反时,才可以将其转换为另一种委托类型。即,因为Func<Giraffe>被标记为协变,所以Func<Animal>可以转换为Func<out T>。 (此外:如果您需要从一种委托类型到另一种委托类型进行变体转换,并且该委托类型不支持方差,您可以做的是使用“源”委托的Invoke方法的方法组,并且现在我们使用的是方法组规则,但是失去了引用相等性。)
即使未将委托标记为支持方差,也可以使用协方差和逆方差规则将方法组或局部方法转换为匹配的委托人类型。也就是说,即使Giraffe G()不是通用的,或者是通用的但未标记为变体,也可以将delegate Animal D();转换为D
转换lambda的规则很复杂。如果lambda没有形式参数类型,则使用目标类型的形式参数类型,分析lambda主体,如果主体进行无误分析并且结果与目标类型的结果类型兼容,则该主体是可转换的。如果lambda确实具有形式参数类型,则它们必须与目标类型的形式参数类型完全匹配。

他们为什么不同?

不同的东西是不同的。我真的不知道如何回答这样一个模糊,广泛的“为什么”问题。

这些规则是由十几个人坐在一个房间多年后得出的。在C#1中添加了用于委托转换的方法组,在C#2中添加了通用委托,在C#3中添加了lambda,在C#4中添加了通用委托方差。我不知道如何回答关于“为什么”的问题实际上完成了数百个小时的设计工作,其中一半以上是在我加入设计团队之前的。该设计工作涉及许多争论和折衷。请不要问关于编程语言设计的模糊“为什么”和“为什么不”的问题。

诸如“规范的哪一页定义了此行为?”之类的问题。有一个答案,但是“为什么规范会这样说?”基本上是要求对15年前从事此设计工作的人员进行心理分析,以及为什么他们发现某些折衷方案令人信服,而其他折衷方案却没有那么多。我不具备或不愿意进行这种分析;这实际上涉及重新散布数百小时的争论。

如果您的问题是“哪些通用语言设计原则会鼓励或不鼓励精确或不精确的匹配?”这是我可以长时间讨论的话题。例如,昨天我设计了一种新的过载解决算法,过载分辨率除了决定精确或不精确匹配的重要性以及重要性外,别无其他。问一个更具体的问题。

告诉你,让我们代替我来做这项工作。这是您的一种情况:

Action<Mammal> ma = (Animal a) => ...

向我描述禁止用户编写该行代码的引人注目的好处。例子:对我来说看起来确实像个虫子。看起来用户开始输入一件事,并在中途改变了主意。这种毫无意义的,怪异的不一致是草率的,错误的代码的高度特征,可以很容易地避免。 C#的设计原则之一是,该语言会在您可能犯错时告诉您。那肯定看起来像个错误。

现在反驳说应该允许代码。示例:就Lambda和本地方法之间的可转换性规则而言,是否也应保持一致是一个普遍原则?与防止草率错误的规则相比,该规则有多重要?

现在提出更多关于每种选择的优缺点的争论,以及不同的开发人员方案如何影响您对每种选择的分析。给出许多实际代码示例。

请记住,有些用户是类型系统方面的专家,有些则不是。有些是具有二十年经验的建筑师,有些则刚大学毕业。有些Java程序员昨天才开始使用C#,但仍处于一种擦除状态。有些是F#程序员,他们习惯于对程序进行全程序推理。对每种情况的优缺点进行大量记录,然后提出一个折衷的建议,该建议在任何重要的情况下都不能妥协。

现在考虑费用。拟议的功能会难以实现吗?是否添加新的错误消息?消息是否清晰,还是会使用户感到困惑?建议的功能可能会阻止将来的任何功能吗?我注意到,要执行此步骤,您必须对语言的未来做出很好的预测。

一旦做出决定,然后用一个句子描述所有工作,回答“为什么要决定?”这个问题。

剃刀付款集成->如何通过关闭按钮X检测剃刀付款模型是否关闭 - javascript

当用户关闭而无需付款时,我在CI框架中使用Razorpay,请创建razor支付模型,然后取消订单,我希望按状态更改为已取消的状态触发查询。所以我怎么能检测到这一点。我已经通过单击jQuery单击关闭功能但无法使用... javascript大神给出的解决方案 Razorpay提供了JS方法来检测模式关闭。您编写的任何JS代码都不会在结帐页面上运行,因为它是…

如何使用箭头符号(->)创建受保护的方法? - java

当我们编写以下代码时Stream.of(1,2,3,4,5).filter(i -> (i%2 == 0)).map( i -> i*i ); 表达式i -> (i%2 == 0)或i -> i*i将变为私有方法。在我的用例中,编写了一个junit测试,以确保没有方法是私有的(是的,这是强制性的),并且对于这些lambda表达式而言,…

粗糙的Unicode->没有CLDR的语言代码? - javascript

我在写字典应用。如果用户键入Unicode字符,我想检查该字符是哪种语言。例如字 - returns ['zh', 'ja', 'ko'] العربية - returns ['ar'] a - returns ['en', 'fr', …

将谓词<T>转换为Func <T,bool> - c#

我有一个包含成员Predicate的类,希望在Linq表达式中使用该类:using System.Linq; class MyClass { public bool DoAllHaveSomeProperty() { return m_instrumentList.All(m_filterExpression); } private IEnumerable&…

LeetCode题解求一根绳子被切两刀能组成一个三角形的概率。

如题题解:我们可以设绳长为1,设:- 其中两段长为x, y且x, y都>0- 故第三段长为1-x-y且>0故可以在二维坐标轴画出一个三角形(由x=0;y=0;1-x-y=0围成)要想构成三角形还要满足:- x+y > 1-x-y => x+y > 0.5- x+1-x-y > y => y < 0.5- y+1…