.NET 4.0 协变性

10

在回答另一个问题后,我尝试了以下操作。我认为我没有正确解释那个问题,但我想知道下面的内容是否可能(我的尝试失败了),如果不可能,为什么不可能:

    public class MyBaseClass { }

    public class MyClass : MyBaseClass { }

    public class B<T> { }

    public class A<T> : B<T> { }

    static void Main(string[] args)
    {
        // Does not compile
        B<MyBaseClass> myVar = new A<MyClass>();
    }

我认为可以使用具有协变类型参数的通用接口使其工作:

    interface IB<out T> { }

    public class B<T> : IB<T> { }

但我错了,那也不行。

编辑

正如SLaks所指出的“接口是协变的;类不是。”(感谢SLaks)。那么现在我的问题是为什么?设计背后的思考是什么(我认为这是Eric Lippert的问题),它不可能、不可取还是在“也许有一天”的清单上?

3个回答

29
“不在泛型类类型上实现差异”的设计决策的背后思路是什么?是不可能、不可取还是“也许有一天”的计划?Andrew Kennedy 是最适合提出确定性答案的人,他在 Microsoft Research Cambridge 设计和实现了大部分原始的泛型和差异逻辑。但是,我可以猜测为什么我们决定放弃在泛型类上实现差异。

简而言之:T 的安全协变需要对 T 的操作进行“只读”操作。T 的逆变需要对 T 的操作进行“只写”操作。你拥有一个希望在 T 上进行变体的类 C<T>。假设 C 具有类型为 T 的字段,请问你想要该字段仅为“只读”还是“只写”?因为这些都是您的选择!

在哪些情况下,具有只能写入但无法读取的字段才会稍微有用?几乎没有多少。在什么情况下,具有只能读取但无法写入的字段才有用?只有当该字段被标记为只读时

简而言之,逆变泛型类几乎从不有用,因为您无法从它们中读取任何通用数据,而 协变类仅在类是不可变数据类型时才大多有用

我非常喜欢不可变的数据类型,并且认为能够创建一个协变的不可变堆栈将是一个很好的功能,而无需涉及接口。但是,在 C# 中,协变泛型持久不可变函数数据结构并不是主流,当 CLR 添加泛型时,它们显然也不是主流。此外,我们除了只读字段之外没有支持基础架构来表达“这是一个不可变数据类型”的概念,如果我们要对不可变类进行协变类类型,那么最好做 许多 支持不可变类的特性,而不仅仅是这个模糊的特性。

因此,我可以理解为什么这个特性没有在 CLR 2.0 / C# 2.0 中通过。如果我们今天再次设计它,当函数式编程风格有些更受欢迎时,也许会这样做。但是,我们暂时没有计划这样做。

我会在未来几个月中撰写一篇博客文章,以便提供更详细的答案。


如果一个泛型类被初始化为一个或多个T消费者,以便向其提供任何给定类型T的项目,则该类可能在T方面是非平凡的逆变的。关于不可变类型,我想看到的是在可变和不可变对象之间平稳移动数据的框架/模式——在仅使用可变对象时需要防御性复制,在使用不可变对象时生成许多短暂实例,并且在“纯”写入时代价昂贵的线程交互。 - supercat
我的看法是,这里的一个权衡是要么制定简单明了的规则,比如“不支持变体类”,要么支持更多场景并制定更复杂的规则和非显而易见的注意事项。嗯,这两种方法都有优缺点。 - Kirill Kobelev
有趣。请问我可以有博客文章的链接吗? - Sentinel

3
直接的类到类转换是不起作用的,但如果您在变量类型中使用IB接口,那么就可以了。根据这篇MSDN协变FAQ博客文章协变和逆变的MSDN页面,变体类型参数仅限于通用接口和通用委托类型。
public class MyBaseClass  {}

public class MyClass : MyBaseClass {}

interface IB<out T>{}

public class B<T> : IB<T> {}

public class A<T> : B<T> {}

static void Main(string[] args)
{
    IB<MyBaseClass> myVar = new A<MyClass>();
}

谢谢Rich,但我们已经从SLaks的回答中得到了这个答案,发表相同的答案通常是不好的做法。 - Myles McDonnell
1
@MylesMcDonnell同意,但他提供了额外的细节和链接,所以我认为这确实有助于讨论... - Dean Kuga

3

接口是协变的;类不是。

A<MyClass> 可以转换为 IB<MyBaseClass>


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接