对我来说,这样做是完美的:
public class A { }
public class B : A { }
public class C
{
public List<A> b = new List<B>();
}
List 期望元素属于 A 类,对于 List 也是如此。我知道存在类型不匹配,但从逻辑上讲,编译器允许这种不匹配是完全合理的。为什么不?
对我来说,这样做是完美的:
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)
// ...
}
}
b
它是作为A
项目列表输入的,所以很自然地,我们可以A
向b
.b
实际上是 的实例List<B>
,所以强制转换是有效的。b
包含不属于 类型的项目B
。tmp
这是 type的事实List<B>
保证列表的所有项目都是 type ,但如果允许分配to B
,情况就不再如此了。List<B>
List<A>
因此,编译器不允许这种不匹配。
因为如果列表存储在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());
但是,如果您可以将 a 分配List<B>
给List<A>
变量,则会产生类型错误,即使编译器知道该变量b
是类型的List<A>
,因此应该能够保存类型A
对象。
相反,您只需使用new List<A>
并向其添加类型的元素B
,这是允许的,或者您将变量的类型更改为List<B>
.
您期望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
/时使用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>
期望 astring
但得到object
. 不好。
您可以使用前面提到的协变和逆变来使接口工作。你可以写一个这样的接口:
public interface Covariant<out T>
{
T FunctionReturningT();
}
public class MyInterfaceImplementation<T> : Covariant<T>
{
public T FunctionReturningT() { /* Blah Blah */ }
}
使接口covariant,这意味着它是合法的写:
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,这意味着这样写是合法的:
Contravariant<string> example = new MyInterfaceImplementation<object>();