2

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

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

public interface ITest
{
    object Property
    {
        get;
    }
}

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

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

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

4

3 回答 3

2

CLR 不支持协变返回类型,但它支持从 .NET 2.0 开始的委托/接口泛型变体。

换句话说,这并不是真正取决于 C# 团队,而是取决于 CLR 团队。

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

编辑:为了反驳关于返回类型协方差的观点,从 CLI 规范的第 8.10.4 节开始,谈论 vtable“插槽”:

对于标记为“期望现有槽”的每个新成员,查看是否存在类型(即字段或方法)、名称和签名的完全匹配,如果找到则使用该槽,否则分配一个新槽。

从分区 II,第 9.9 节:

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

没有迹象表明比较是以允许差异的方式进行的。

如果您认为 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以具有“字符串”而不是“对象”的返回类型:

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

重建:

ilasm /OUTPUT:Test.exe Test.il

重新运行它,输出:

Base.Foo

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

于 2009-08-09T11:23:33.113 回答
1
  1. 方法的返回值总是“out”,它们总是在赋值表达式的右侧。
  2. CLR 程序集格式具有将实例方法分配给接口方法的元数据,但是这种方法推断仅依赖于输入参数,他们可能需要创建新格式来支持,不仅它还会变得模棱两可。
  3. 实例方法和接口签名之间的新方法映射算法也可能很复杂并且占用大量 CPU。而且我相信接口方法的方法签名是在编译时解决的。因为在运行时它可能太贵了。
  4. 方法推断/解决可能是问题,如下所述。

考虑使用仅具有不同返回类型的允许动态方法解析的示例(这是绝对错误的)

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??
于 2009-08-09T12:34:36.127 回答
1

CLR 不支持方法覆盖的变化,但接口实现有一个解决方法:

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

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

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

于 2009-08-09T17:30:12.183 回答