代码之家  ›  专栏  ›  技术社区  ›  Zack ISSOIR

代理的方差规则的奇怪示例

  •  0
  • Zack ISSOIR  · 技术社区  · 6 年前

    在Eric Lippert关于协方差和逆变或方差的博客文章中,以及 简而言之 ,声明:

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

    • 将仅用于参数的任何类型参数标记为逆变(in)。

    这样做可以让转换自然地工作,通过尊重 类型之间的继承关系。

    所以我在做这个实验,发现了一个很奇怪的例子。

    使用此类层次结构:

    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表达式中给定的一个表达式:

    • 委托实例。
    • 表达式树,属于表达式类型。

    我认为:

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

    所发生的是一种从以下方面的转变:

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

    从委托到委托的差异规则与方法组到委托的差异规则是否不同?

    0 回复  |  直到 6 年前
        1
  •  5
  •   Eric Lippert    6 年前

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

    这三样东西可以转换为委托类型:(1)lambda(或C#2风格的匿名方法),(2)方法组或本地方法,(3)另一个委托。在每种情况下,协变和逆变转换的规则是不同的吗?

    对。

    您应该阅读规范以了解确切的细节,但要简短地:

    • 泛型委托类型可以转换为另一个泛型委托类型 仅当代理类型参数标记为协变或逆变时 . 也就是说, Func<Giraffe> 可以转换为 Func<Animal> 因为 Func<out T> 标记为协变。(旁白:如果需要从一个委托类型到另一个委托类型进行变量转换,而委托类型不支持variance,则可以使用 Invoke 方法,现在我们使用方法组规则,但失去了引用相等性。)

    • Giraffe G() delegate Animal D(); 即使 D 不是通用的,或者是通用的但没有标记为变量。

    • 目标类型 ,则分析lambda主体,如果主体分析没有错误并且结果与目标类型的结果类型兼容,则它是可转换的。 如果lambda确实具有形式参数类型,则它们必须与目标类型的形式参数类型完全匹配

    它们为什么不同?

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

    像“规范的哪一页定义了这种行为?”有一个答案,但“为什么规范会这么说?”基本上是要求对15年前做过这项设计工作的人进行心理分析,为什么他们发现某些妥协很有说服力,而其他的则没有那么多。我没有能力也不愿意做这样的分析;这需要重新思考几百个小时的争论。

    昨天 除了决定精确或不精确的匹配何时重要,以及它们有多重要 . 问一个更具体的问题 .

    告诉你,让我们 代替我做那项工作。以下是您的一个场景:

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

    请向我描述一下禁止用户编写那行代码的显著好处。 例:在我看来,它确实像个虫子。看起来用户开始输入一个东西,中途改变了主意。这种毫无意义、奇怪的不一致性是草率的、有缺陷的代码的高度特征,而且可以很容易地防止。 . 这看起来确实是个错误。

    现在提出一个反论点,即代码应该被允许

    现在,提出更多关于每种选择的细微利弊的论据,以及不同的开发人员场景如何影响您对每个选择的分析。给出很多,很多实际代码的例子。

    请记住,有些用户是类型系统的专家,有些则不是。有些是有20年经验的建筑师,有些是刚从大学毕业的。有些人是Java程序员,他们昨天刚学了C,但仍处于擦除的心态;有些人是F程序员,他们习惯于完整的程序推理。 对每种方案的优缺点做详细的说明,然后提出一个折衷方案,在任何重要的方案上都不过分妥协。

    现在考虑一下成本 . 提议的特性是否难以实现?它是否添加了新的错误消息?信息是清晰的,还是会迷惑用户?提议的功能是否可能阻止未来的功能?我注意到你必须 好的 预测语言的未来来做这一步。

    一旦你做出决定,那么 用一句话来描述所有这些工作,并回答“你为什么决定这样做?”