为什么C#4.0中的类没有通用方差?

dev*_*ium 55 .net c# generics c#-4.0

如果我们有接口,为什么我们也没有它用于类?使用它时会产生什么问题?

Eri*_*ert 111

假设你有一个C<T>在T中具有协变性的类.它的实现是什么样的?T必须出来.这意味着C<T>不能有任何采用T的方法,带有setter的T类型的任何属性,或任何类型为T的字段,因为字段在逻辑上与属性setter相同; T进去了.

就T而言,使用协变类构建的唯一有用的东西是不可变的.现在,我认为拥有协变的不可变列表和堆栈以及类型的类型将是非常棒的.但是这个特征并不是那么明显,以至于它可以明确证明在使类型系统本身支持协变不可变类类型时的大量开支.

上面的评论询问了一个有用的例子.考虑以下草图:

sealed class Stack<out T>
{
    private readonly T head;
    private readonly Stack<T> tail;
    public T Peek() { return head; }
    public Stack<T> Pop() { return tail; }
    public Stack(T head, Stack<T> tail)
    {
        this.tail = tail;
        this.head = head;
    }
}
static class StackExtensions
{
    public static Stack<T> Push<T>(this Stack<T> tail, T head) 
    {
        return new Stack<T>(head, tail);
    }
    public static bool IsEmpty<T>(this Stack<T> stack)
    {
        return stack == null;
    }
}
Run Code Online (Sandbox Code Playgroud)

假设你有协变类.现在你可以说

Stack<string> strings = null;
strings = strings.Push("hello");
strings = strings.Push("goodbye");
Stack<object> objects = strings;
objects = objects.Push(123);
Run Code Online (Sandbox Code Playgroud)

嘿,我们只是把一个整数推到一堆字符串上,但一切都很好!没有理由为什么这不是类型安全的.在可变数据结构上违反类型安全的操作可以在不可变数据结构上安全地协变.

  • 不可变集合的想法非常有用,Scala有一整套不可变集合,它们都支持协方差. (5认同)
  • 好的例子,埃里克.对不起,我不能两次投票. (3认同)
  • 我为你做了.现在太糟糕了,我不能再投票了. (3认同)
  • 对于类型T协变的类在每个实例将在其构造函数中接收它将需要的所有T或T创建者的情况下是有用的; 与类型T相反的逆变类在类将在其构造函数中接收一个或多个T消费者的情况下最有用.这种情况不一定是罕见或晦涩的,但在大多数情况下,类的消费者可以使用协变/逆变接口类型而不是类类型. (3认同)
  • @Ilya:因为CLR类型系统的设计者没有创建一个类型系统,其中具有T的只读字段的类可能在T中是协变的.是否可以创建这样的特征?当然,这在逻辑上是明智的.它只是没有实施.仅仅因为某个功能可能无法实现; 有人必须做这项工作. (3认同)
  • @mathk:可扩展性是一项功能,并且具有成本.设计可扩展性很难; 它需要对用户如何扩展类进行大量研究,以确保可扩展点是正确的.因此*总是密封任何你不打算扩展的课程*. (2认同)
  • @mathk:当然,什么能保证我的基类对构建持久性堆栈,分布式堆栈等有用?是什么保证将来对基类的更改不会破坏派生类,即“我什至不知道甚至存在”的派生类?您绝对正确地认为子类化与行为的改变有关。您错过的是*子类化也依赖于基类*提供的服务,基类作者可能没有*意向*提供的服务。 (2认同)
  • @EricLippert 你能解释一下,为什么 T 上的协变类不能有 T 类型的只读字段?在您的示例中,这些字段没问题,不会破坏类型安全。这些字段可以在类型构造函数中初始化,它在 T 上始终不变,因此可以接受类型 T 的值。 (2认同)