C# 类和foreach变量的向下转换之间的差异

C# 类和foreach变量的向下转换之间的差异,c#,inheritance,casting,C#,Inheritance,Casting,我有这样的课程: public class A { public int AProperty { get; set; } public List<A> Children; } public class B:A { public string Name { get; set; } } 我可以这样做: A a = new A(); B b = (B)a;//SystemCastInvalidException B bCanDo= new B(); bCanD

我有这样的课程:

public class A
{
    public int AProperty { get; set; }
    public List<A> Children;
}

public class B:A
{
    public string Name { get; set; }
}
我可以这样做:

A a = new A();
B b = (B)a;//SystemCastInvalidException
B bCanDo= new B();
bCanDo.Children.Add(new B());       

foreach (var c in bCanDo.Children)
{
   B notExpected = (B)c;//OKAY. Why? 
}

我错过了什么?为什么我可以在
foreach
?是的,这在逻辑上都是正确的,但我在哪里可以阅读有关它的信息呢?

这是一个运行时错误,而不是编译器错误,所以让我们看看您在这里做什么:

在第一个示例中,您正在构造类型为
A
的对象,然后尝试将其强制转换为类型
B
。这是非法的,因为
A
不是从
B
继承的。它不是非法的,因为编译器认为这是非法的,它编译代码,然后在运行时崩溃,因为这肯定是无效的强制转换

但是,在第二个示例中,您正在构造一个
B
类型的对象,然后将其添加到一个可以保存
a
类型对象的列表中。由于
B
继承自
A
这是合法的。然后将第一个对象拾取回来并将其投射到
B
。这也是合法的,因为底层对象实际上是
B
类型

基本上,以下是两个简单步骤的示例:

A a = new A();
B b = (B)a; // fails with InvalidCastException

A a = new B();
B b = (B)a; // works OK
这与foreach无关,它与你的两个例子做了不同的事情有关。要查看相同的代码在使用
foreach
时失败,请尝试以下操作:

B bCanDo= new B();
bCanDo.Children.Add(new A()); // <-- notice new A() here

foreach (var c in bCanDo.Children)
{
   B notExpected = (B)c; // crash
}
B bCanDo=new B();

bCanDo.Children.Add(新的A());// 这是一个运行时错误,而不是编译器错误,所以让我们看看您在这里做什么:

在第一个示例中,您正在构造类型为
A
的对象,然后尝试将其强制转换为类型
B
。这是非法的,因为
A
不是从
B
继承的。它不是非法的,因为编译器认为这是非法的,它编译代码,然后在运行时崩溃,因为这肯定是无效的强制转换

但是,在第二个示例中,您正在构造一个
B
类型的对象,然后将其添加到一个可以保存
a
类型对象的列表中。由于
B
继承自
A
这是合法的。然后将第一个对象拾取回来并将其投射到
B
。这也是合法的,因为底层对象实际上是
B
类型

基本上,以下是两个简单步骤的示例:

A a = new A();
B b = (B)a; // fails with InvalidCastException

A a = new B();
B b = (B)a; // works OK
这与foreach无关,它与你的两个例子做了不同的事情有关。要查看相同的代码在使用
foreach
时失败,请尝试以下操作:

B bCanDo= new B();
bCanDo.Children.Add(new A()); // <-- notice new A() here

foreach (var c in bCanDo.Children)
{
   B notExpected = (B)c; // crash
}
B bCanDo=new B();

bCanDo.Children.Add(新的A());// 在cannot的情况下,a包含一个类型为a的对象,该对象不能强制转换为B,因为它不是类型B


在can的情况下,类型为B的a对象被添加到子对象中,因为B是a的子类型,所以您可以这样做,但它仍然是类型为B的对象。当您循环子对象并将其强制转换到B时,您只能这样做,因为它已经是B。是否添加
B.children.add(新a())它将再次失败。

在cannot的情况下,a包含一个类型为a的对象,该对象不能转换为B,因为它不是类型B


在can的情况下,类型为B的a对象被添加到子对象中,因为B是a的子类型,所以您可以这样做,但它仍然是类型为B的对象。当您循环子对象并将其强制转换到B时,您只能这样做,因为它已经是B。是否添加
B.children.add(新a())它将再次失败。

您需要了解的是,强制转换不会以任何方式更改基础对象

如果我有这些课程:

public class Animal { }
public class Dog : Animal { }
…我写了这段代码:

Dog d = new Dog();
Animal a = (Animal)d;
Dog d = new Dog();
Cat c = (Cat)d;
a
变量仍然是一只
Dog
,它只是被当作一只
动物来处理

如果我定义了这个类:

public class Cat : Animal { }
…然后我试着写下以下代码:

Dog d = new Dog();
Animal a = (Animal)d;
Dog d = new Dog();
Cat c = (Cat)d;
…我得到一个错误,但不是因为我不能将
更改为
,而是因为对象
d
始终是一只
,我不能将其视为
永远不能是

因此,在编写代码时:

A a = new A();
B b = (B)a;//SystemCastInvalidException
…同样适用-a
a
永远不能是a
B

但是在你的代码中,a
B
可以是
a

因此,如果我重新编写您的代码,如下所示:

B bCanDo = new B();
bCanDo.Children.Add(new B());

foreach (A a in bCanDo.Children)
{
    B notExpected = (B)a; 
}

…您可以看到,即使
bCanDo
子项是
A
类型,也可以添加
B
类型的子项-A
B
可以是
A
。因此,当您遍历
子对象时,子对象的类型永远不会改变,即使
子对象的成员是
A
,如果添加了
B
,您始终可以将其转换回
B
。这就是为什么foreach
cast可以工作。

您需要了解的是,cast不会以任何方式改变底层对象

如果我有这些课程:

public class Animal { }
public class Dog : Animal { }
…我写了这段代码:

Dog d = new Dog();
Animal a = (Animal)d;
Dog d = new Dog();
Cat c = (Cat)d;
a
变量仍然是一只
Dog
,它只是被当作一只
动物来处理

如果我定义了这个类:

public class Cat : Animal { }
…然后我试着写下以下代码:

Dog d = new Dog();
Animal a = (Animal)d;
Dog d = new Dog();
Cat c = (Cat)d;
…我得到一个错误,但不是因为我不能将
更改为
,而是因为对象
d
始终是一只
,我不能将其视为
永远不能是

因此,在编写代码时:

A a = new A();
B b = (B)a;//SystemCastInvalidException
…同样适用-a
a
永远不能是a
B

但是在你的代码中,a
B
可以是
a

因此,如果我重新编写您的代码,如下所示:

B bCanDo = new B();
bCanDo.Children.Add(new B());

foreach (A a in bCanDo.Children)
{
    B notExpected = (B)a; 
}
…您可以看到,即使
bCanDo
子项是
A
类型,也可以添加
B
类型的子项-A
B
可以是
A
。s