我不得不认真思考如何很好地解释这一点。解释似乎和理解它一样困难。
假设您有一个基类 Fruit。你有两个子类 Apple 和 Banana。
Fruit
/ \
Banana Apple
您创建两个对象:
Apple a = new Apple();
Banana b = new Banana();
对于这两个对象,您可以将它们类型转换为 Fruit 对象。
Fruit f = (Fruit)a;
Fruit g = (Fruit)b;
您可以将派生类视为它们的基类。
但是,您不能将基类视为派生类
a = (Apple)f; //This is incorrect
让我们将其应用到列表示例中。
假设您创建了两个列表:
List<Fruit> fruitList = new List<Fruit>();
List<Banana> bananaList = new List<Banana>();
你可以做这样的事情......
fruitList.Add(new Apple());
and
fruitList.Add(new Banana());
因为当您将它们添加到列表中时,它本质上是对它们进行类型转换。你可以这样想...
fruitList.Add((Fruit)new Apple());
fruitList.Add((Fruit)new Banana());
然而,将相同的逻辑应用于相反的情况会引发一些危险信号。
bananaList.Add(new Fruit());
是相同的
bannanaList.Add((Banana)new Fruit());
因为不能像派生类一样对待基类,所以会产生错误。
以防万一您的问题是为什么这会导致错误,我也会对此进行解释。
这是水果类
public class Fruit
{
public Fruit()
{
a = 0;
}
public int A { get { return a; } set { a = value } }
private int a;
}
这是香蕉类
public class Banana: Fruit
{
public Banana(): Fruit() // This calls the Fruit constructor
{
// By calling ^^^ Fruit() the inherited variable a is also = 0;
b = 0;
}
public int B { get { return b; } set { b = value; } }
private int b;
}
想象一下你再次创建了两个对象
Fruit f = new Fruit();
Banana ba = new Banana();
请记住,香蕉有两个变量“a”和“b”,而水果只有一个“a”。
所以当你这样做时...
f = (Fruit)b;
f.A = 5;
您创建了一个完整的 Fruit 对象。
但如果你要这样做...
ba = (Banana)f;
ba.A = 5;
ba.B = 3; //Error!!!: Was "b" ever initialized? Does it exist?
问题是您没有创建完整的 Banana 类。并非所有数据成员都已声明/初始化。
现在我洗完澡回来,给自己买了点零食,事情变得有点复杂。
事后看来,当我讨论复杂的事情时,我应该放弃这个比喻
让我们创建两个新类:
public class Base
public class Derived : Base
他们可以做任何你喜欢做的事
现在让我们定义两个函数
public Base DoSomething(int variable)
{
return (Base)DoSomethingElse(variable);
}
public Derived DoSomethingElse(int variable)
{
// Do stuff
}
这有点像“out”的工作原理,您应该始终能够像使用基类一样使用派生类,让我们将其应用于接口
interface MyInterface<T>
{
T MyFunction(int variable);
}
out/in 之间的主要区别在于泛型用作返回类型或方法参数时,这是前一种情况。
让我们定义一个实现该接口的类:
public class Thing<T>: MyInterface<T> { }
然后我们创建两个对象:
MyInterface<Base> base = new Thing<Base>;
MyInterface<Derived> derived = new Thing<Derived>;
如果你这样做:
base = derived;
您会收到类似“无法隐式转换...”的错误
您有两种选择,1)显式转换它们,或者 2)告诉编译器隐式转换它们。
base = (MyInterface<Base>)derived; // #1
or
interface MyInterface<out T> // #2
{
T MyFunction(int variable);
}
如果您的界面如下所示,则第二种情况会出现:
interface MyInterface<T>
{
int MyFunction(T variable); // T is now a parameter
}
再次将其与两个函数联系起来
public int DoSomething(Base variable)
{
// Do stuff
}
public int DoSomethingElse(Derived variable)
{
return DoSomething((Base)variable);
}
希望您看到情况如何逆转,但本质上是相同类型的转换。
再次使用相同的类
public class Base
public class Derived : Base
public class Thing<T>: MyInterface<T> { }
和相同的物体
MyInterface<Base> base = new Thing<Base>;
MyInterface<Derived> derived = new Thing<Derived>;
如果你尝试将它们设置为相等
base = derived;
你的编译器会再次对你大喊大叫,你有和以前一样的选择
base = (MyInterface<Base>)derived;
or
interface MyInterface<in T> //changed
{
int MyFunction(T variable); // T is still a parameter
}
基本上,当泛型仅用作接口方法的返回类型时,就可以使用了。当它将被用作方法参数时使用。使用委托时也适用相同的规则。
有一些奇怪的例外,但我不会在这里担心它们。
提前对任何粗心错误表示歉意=)