具有继承的模板类参数的集合
本文关键字:参数 集合 继承 | 更新日期: 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# 中,某些接口是协变的,但类不是。如上A
和B
,可以说
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
应该返回一个 object
,MyClass<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>();