为什么派生类的泛型会产生非派生类?

时间:2013-10-17 15:53:14

标签: c# oop generics polymorphism

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

class A
{
}

class B : A
{
}

class C<T>
{
}

现在C<B> 未从C<A>派生

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

是否有任何具体的理由和例子?

修改

我读过很多类似的帖子,比如: In C#, why can't a List<string> object be stored in a List<object> variable

Why can't I cast a dictionary of one value type to dictionary of another value type when the value types can be cast from one another?

Casting from IEnumerable<Object> to IEnumerable<string>

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

3 个答案:

答案 0 :(得分:9)

  

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

让我们先说明你提到的basic principle。来自维基百科:

  

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

您缺少的是关键词:对象

您可以将类型为B对象替换为需要类型为A对象的任何位置。但是,使用类型 A您可以使用类型 B进行的所有事情都不是这样。如果有人问你要一辆车而你给他们一辆1995年的福特护航,那么他们就可以把它用作汽车。但这并不意味着您可以在维基百科中进行搜索和替换,并将“car”一词的每个用法改为“1995 Ford Escort”,并且文章仍然正确!

事实上,您无法使用C<B>预期C<A>的原因是因为违反了 Liskov替换原则:

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类型的对象,但ca实际上是cb,并且您无法使用D你可以在任何地方使用B!因此,您无法在任何可以使用C<B>的地方使用C<A>

当编译器证明这样做是安全的时,C#4及以上版本允许这种协方差。有关详细信息,请在C#covariance上进行网络搜索;你会找到很多关于这个主题的文章。 (包括我的。)

答案 1 :(得分: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时会发生什么,假设存储的实例只能是B类型,但它实际上是A类型。

因此,C<B>并非衍生自({+ 1}}不符合任何内容。

答案 2 :(得分:1)

好吧,当你写一个课程C<T>时,所有内容都是C,而不是TT仅仅是提供用于表示特殊C<>类型的类型信息。 C<>有自己的实现,与T完全没有关系。当你写:

var c = new C<int>();

变量c的行为完全符合您在课程C<>中所写的内容。无论Tint还是string还是其他任何内容,它的行为都相同。

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

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

你可以写:

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

你知道,这可能是因为C来自D。班级的左侧很重要。这也是非法的:

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

即使A来自B,因为C的{​​{1}}与A的{​​{1}}无关(甚至D 1 {} B)这是一个单独的类。

当您将C视为与B完全不同的类型时,这种通用多态实际上毫无意义,尽管它们具有共同的行为。为了说明另一个例子,考虑一下,一个类可以通过多个类型来通用。假设有

C<A>

C<B>class C<S, T> { } 怎么样?它们的类型是否具有可比性,因为这两个类型具有C<IList<A>, A>C<IList<int>, B>以及IList作为泛型类型?来自A的{​​{1}}是否可投弃?简而言之,这些都是不同的类型。

现在,如果你不想做作业

B

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

C<T>

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

没有关于协方差和逆变的信息(仅适用于接口和代表),没有关于泛型的完整信息。如果发生这种情况,请参阅O. R. Mapper关于危险的答案。 C#允许这种情况的一种情况是数组。遗憾的是,这是一个破碎的设计。这在C#

中是可能的
C<S, T>

我很想详细说明,但我做的任何事情都会让它变得多余。请参阅此优秀答案by Eric Lippert以获取明确信息。