非泛型类型的协变量/逆变量支持?

时间:2009-08-09 11:21:19

标签: c# covariance contravariance

我想知道为什么C#团队决定不支持非泛型的共同/逆转,因为它们可能同样安全。这个问题相当主观,因为我不希望团队成员做出回应,但有人可能会有我(和Barbara Liskov)缺乏的洞察力。

让我们看看这个示例界面:

public interface ITest
{
    object Property
    {
        get;
    }
}

以下实现将失败,尽管完全安全(我们总是可以返回更具体的类型而不违反接口 - 不是在C#中,但至少在理论上)。

public class Test : ITest
{
    public string Property
    {
        get;
    }
}

如果接口包含一个setter,代码自然不安全,但这并不是限制整体实现的理由,因为可以通过使用out / in来声明安全性,就像泛型一样。

3 个答案:

答案 0 :(得分:2)

CLR不支持协变返回类型,而从.NET 2.0开始支持委托/接口泛型差异。

换句话说,它不是由C#团队决定,而是CLR团队。

至于为什么CLR不支持正常方差 - 我不确定,除了增加复杂性之外,大概没有必要的感知效益。

编辑:要反驳关于返回类型协方差的观点,请参阅CLI规范的第8.10.4节,讨论vtable“slots”:

  

对于标记的每个新成员   “期待现有的插槽”,看看是否   完全匹配的种类(即田地或   方法),名称和签名存在   并且如果找到则使用该插槽,   否则分配一个新的插槽。

从第II节第9.9节开始:

  

具体来说,为了确定   会员是否隐藏(静态或   实例成员)或覆盖(for   虚拟方法)来自基础的成员   类或接口,只需替换   每个通用参数及其   泛型参数,并比较   结果会员签名。

没有迹象表明比较是以允许变化的方式完成的。

如果您认为CLR 允许变异,我认为鉴于上述证据,您需要使用适当的IL来证明它。

编辑:我只是在IL中尝试它,它不起作用。编译此代码:

using System;

public class Base
{
    public virtual object Foo()
    {
        Console.WriteLine("Base.Foo");
        return null;
    }
}

public class Derived : Base
{
    public override object Foo()
    {
        Console.WriteLine("Derived.Foo");
        return null;
    }
}

class Test
{
    static void Main()
    {
        Base b = new Derived();
        b.Foo();
    }
}

运行它,输出:

Derived.Foo

反汇编:

ildasm Test.exe /out:Test.il

编辑Derived.Foo使返回类型为“string”而不是“object”:

.method public hidebysig virtual instance string Foo() cil managed

重建:

ilasm /OUTPUT:Test.exe Test.il

重新运行,输出:

Base.Foo

换句话说,就CLR而言,Derived.Foo不再覆盖Base.Foo。

答案 1 :(得分:1)

  1. 方法的返回值始终为“out”,它们始终位于赋值表达式的右侧。
  2. CLR程序集格式具有将实例方法分配给接口方法的元数据,但此方法推断仅依赖于输入参数,它们可能需要创建新格式以支持,而不仅仅是它变得暧昧。
  3. 实例方法和接口签名之间的新方法映射算法可能很复杂且CPU密集。我相信接口方法的方法签名在编译时得到解决。因为在运行时它可能太贵了。
  4. 方法推断/解决可能是问题,如下所述。
  5. 考虑使用允许动态方法解析的示例,仅使用不同的返回类型(绝对错误)

    public class Test{
         public int DoMethod(){ return 2; }
         public string DoMethod() { return "Name"; }
    } 
    Test t;
    int n = t.DoMethod();  // 1st method
    string txt = t.DoMethod(); // 2nd method
    object x = t.DoMethod(); // DOOMED ... which one??
    

答案 2 :(得分:1)

CLR不支持方法覆盖的差异,但是接口实现有一种解决方法:

public class Test : ITest
{
    public string Property
    {
        get;
    }

    object ITest.Property
    {
        get
        {
            return Property;
        }
    }
}

这将实现与协变覆盖相同的效果,但只能用于接口和直接实现