1

假设我们有两个类AB其中 B 派生自 A。

class A
{
}

class B : A
{
}

class C<T>
{
}

现在C<B> 不派生C<A>.

这让我有点困惑,因为我坚持认为我可以用 A 做的“一切”,我可以用 B 做。我确信我错过了一些东西,因为它似乎与 OOP 的基础知识相矛盾。

有什么具体的理由和例子吗?

编辑:

我读过许多类似的帖子,例如: 在 C# 中,为什么不能将 List<string> 对象存储在 List<object> 变量中

当值类型可以相互转换时,为什么我不能将一种值类型的字典转换为另一种值类型的字典?

从 IEnumerable<Object> 转换为 IEnumerable<string>

但是在每篇文章中,答案都集中在特定的类/数据结构中,并使这个特定的代码能够工作,而不是为什么一般来说不可能?

4

3 回答 3

9

我认为我可以用 A 做的“一切”,我可以用 B 做。我确信我错过了一些东西,因为它似乎与 OOP 的基础知识相矛盾。

让我们首先说明您所暗示的基本原则。来自维基百科:

如果 S 是 T 的子类型,则 T 类型的对象可以被 S 类型的对象替换,而不会改变该程序的任何所需属性

您缺少的是关键字:objects

您可以在需要类型对象B任何地方替换类型对象A。但并非所有你可以用type 做的事情都可以用 typeA做。如果有人向您要车,而您给了他们一辆 1995 年的福特 Escort,他们可以将其用作汽车。但这并不意味着您可以在维基百科中进行搜索和替换,并将“汽车”一词的所有用法更改为“1995 Ford Escort”并且文章仍然正确! B

事实上,你不能使用C<B>where C<A>is expected 的原因是因为这样做违反了里氏替换原则:

class A {}
class B : A {}
class D : A {}
class C<T>
{
  public T M(T t) { ... }
}
...
C<B> cb = new C<B>();
C<A> ca = cb; // Suppose this were legal
ca.M(new D());

D您可以在任何可以使用a 的地方使用类型的对象A,但ca实际上是 a cb,并且您不能在D任何可以使用 a 的地方使用 a B!因此,您不能在可以使用 a 的C<B>任何地方使用 a C<A>

当编译器可以证明这样做是安全的时, C# 4 及更高版本确实允许这种协变。对 C# covariance 进行网络搜索以获取详细信息;你会发现很多关于这个主题的文章。(包括我的。)

于 2013-10-17T18:14:21.110 回答
2

看这个例子:

class C<T>
{
    public T Value { get; set; }
}

// ...

C<B> cb = new C<B>();

现在,如果C<B>从 派生C<A>,以下将是可能的:

C<A> ca = cb;
ca.Value = new A(); // ca.Value is defined by C<A> and thus typed to A

想象一下当我们cb.Value现在访问时会发生什么,假设存储的实例只能是 type B,但它实际上是 type A

因此,C<B>不派生自 (is not assignment-compatiable to) C<A>

于 2013-10-17T16:04:52.120 回答
1

好吧,当你写一门课时C<T>,一切都是关于C,而不是TT只是提供用于表示特殊C<>类型的类型信息。C<>有自己的实现,与T. 当你写:

var c = new C<int>();

变量的行为c完全按照你在课堂上写的C<>。无论Tisint还是string什么,它的行为都是一样的。

另请注意,编写为 like 的类C<S>是一种特殊类型,与C<T>另一种类型不同,因为所有行为都受到它的C-ness 的限制。我们永远不知道它是如何C<>编写的以及它将如何表现。所以你描述的方式不是泛型如何发挥作用。如果您有额外的课程,例如

class D<T> : C<T>
{
}

你可以写:

C<A> c = new D<A>();

你看,这又是可能的,因为C派生自D. 班级的左边很重要。这又是非法的:

C<A> c = new D<B>();

即使A派生自B,因为与of (甚至Cof )A没有关系,它是一个单独的类。DBCB

这种泛型多态性实际上是毫无意义的,当你认为C<A>它们是一种完全不同的类型时,C<B>尽管它们具有共同的行为。用另一个例子来说明,想想这个,一个类可以是多个类型的泛型。假设有

class C<S, T>
{
}

C<IList<A>, A>和怎么样C<IList<int>, B>?它们的类型是否具有可比性,因为这两个具有IListAB作为泛型类型?C<T>浇注料C<S, T>来自? 不,简而言之,这些是不同的类型。

现在如果你不想做任务

C<A> ca = new C<B>();

可能您可以编写自己的隐式转换器。

class C<T>
{
    public static implicit operator C<T>(C<B> c)
    {
        type check, explode or return
    }
}

//now you may write:
C<A> ca = new C<B>();
//or even
C<A> ca = new D<B>();

请注意,这并不意味着继承关系,而只是一种转换操作。

如果没有关于协变和逆变的信息(仅适用于接口和委托),则没有关于泛型的信息是不完整的。如果让这种情况发生,请参阅 OR Mapper 关于危险的回答。C# 允许这样做的一种情况是数组。可悲的是,这是一个破碎的设计。这在 C# 中是可能的

object[] o = new string[1];
o[0] = 1; //explosion

我很想详细说明,但我所做的任何事情都会使它变得多余。有关明确信息,请参阅Eric Lippert的出色回答。

于 2013-10-17T17:09:42.723 回答