具有继承的模板类参数的集合

本文关键字:参数 集合 继承 | 更新日期: 2023-09-27 17:57:03

对我来说,这样做是完美的:

public class A { }
public class B : A { }
public class C
{
    public List<A> b = new List<B>();
}
List

期望元素属于类 A,对于 List 也是如此。我知道存在类型不匹配,但从逻辑上讲,编译器允许这种不匹配是完全合理的。为什么不呢?

具有继承的模板类参数的集合

List<B>不能

分配给List<A>,这是有充分理由的。

让我们假设不匹配是允许的,让我们想象你的类C中的以下虚构方法:

public void DoSomething()
{
    b.Add(new A()); // (1)
    List<B> tmp = (List<B>)b; // (2)
    foreach (B item in tmp) { // (3)
        // ...
    }
}
  • 第 (1) 行有效,因为b被键入为A项的列表,因此自然地,我们可以向b添加新的A实例。
  • 第 (2) 行有效,因为 b 引用的实例实际上是 List<B> 的实例,因此强制转换是有效的。
  • 第 (3) 行将崩溃,因为b包含不属于 B 类型的项目。tmp 属于类型 List<B> 的事实保证了列表中的所有项目都是 B 类型,但如果允许将List<B>分配给List<A>,则情况不再如此。

因此,编译器不允许这种不匹配。

你期望List<T>T中是协变的,但事实并非如此。

在 C# 中,某些接口是协变的,但类不是。如上AB,可以说

IEnumerable<A> b = new List<B>();

IReadOnlyList<A> b = new List<B>();

因为所讨论的接口是协变的,即用"out"声明,如

public interface IEnumerable<out T> ...
public interface IReadOnlyLies<out T> ...

因为如果列表存储在List<A>变量中,程序希望能够将类型为 A 的对象放入该变量中。 将List<B>对象存储在 List<A> 变量中将阻止您将 A 类型的对象放入显式声明为能够保存类型 A 的列表中。

那是:

List<A> b = new List<B>()
// compiler knows list should be of type A, so it expects this to work:
b.add(new A());

但是,如果可以将List<B>分配给List<A>变量,则会产生类型错误,即使编译器知道变量b属于List<A>类型,因此应该能够保存对象类型A

相反,您只需使用 new List<A> 并向其中添加类型 B 的元素(这是允许的),或者将变量的类型更改为 List<B>

这取决于泛型容器是使用协变还是逆变泛型参数设置的。

您可以检查 MSDN 以查看它是如何声明的。

使用类无法实现此目的。您可以使用接口(查找协方差和逆变),但在使用 List/IList 时则不行。想象一下以下场景:

public class MyClass<T> 
{
    T GetT() { /* Blah blah */ }
    void SetT(T value) { /* Blah Blah */ }
}

写这个:

MyClass<object> example = new MyClass<string>();

这将适用于第一种方法; example 应该返回一个 objectMyClass<string>返回一个字符串,这是合法的,因为多态性。

第二种方法更成问题。假设你之后写了这个:

example.SetT(new object());

这是非法的,因为MyClass<string>期望string但得到object。不好。

如前所述,您可以使用协方差和逆变来使它适用于接口。你可以编写这样的接口:

public interface Covariant<out T>
{
    T FunctionReturningT();
}
public class MyInterfaceImplementation<T> : Covariant<T>
{
    public T FunctionReturningT() { /* Blah Blah */ }
}

使接口协变,这意味着编写以下内容是合法的:

Covariant<object> example = new MyInterfaceImplementation<string>();

而你也可以写以下内容:

public interface Contravariant<in T>
{
    void FunctionAskingForT(T value);
}
public class MyInterfaceImplementation<T> : Contravariant<T>
{
    public void FunctionAskingForT(T value) { /* Blah Blah */ }
}

你刚刚使该接口逆,这意味着编写以下内容是合法的:

Contravariant<string> example = new MyInterfaceImplementation<object>();