如何在 C# 中将父对象强制转换为子对象
How it is possible to cast parent to child in c#
前几天在面试中,被要求解释以下案例:
public class Parent
{
}
public class Child:Parent
{
}
案例一:
Parent p = new Child();
var c = (Child)p;
案例2:
Parent p = new Parent();
var c = (Child)p;
我说case1
有效,case2
无效,他却问:
Why case2
is invalid while both p
variable in case1
and case2
are Type of Parent
class?
在案例 2 中,我们通过以下消息得到 运行时间异常:
Unable to cast object of type 'Parent' to type 'Child
而在 case1
p
中变量也是 Parent
class.
的类型
他要我从stack
和heap
记忆的角度来解释。
你有什么想法吗?
更新:
面试官误导我的地方正是他在两种情况下所说的 p
是 'parent' class 的类型,根据答案,只是两个变量的静态类型是 'parent',但在 运行 时间它们有不同的类型,真正重要的是 运行 时间类型。
您必须区分变量的静态类型(编译时已知的类型)和分配给变量的 object 引用的 运行 时间类型。
p
的静态类型在这两种情况下都是Parent
,因为它被声明为Parent p
。不管你分配什么。您甚至可以分配它 null
.
赋值后p
的值的运行时间类型第一种情况是Child
,第二种情况是Parent
。如果 p
是 null
.
则不确定
可以将 Child
分配给 Parent
,因为 Child
class 派生自 Parent
。因此,Child
是 Parent
(在 OO 术语中)。
但是,Parent
不是 Child
,因为它不是从它派生的。因此,在第二种情况下,您不能将 p
转换为 Child
。在第一种情况下,转换是有效的,因为您将 object 转换为其实际的 运行 时间类型。它告诉编译器,我知道分配给一个 Parent
变量的这个 object 是一个 Child
,所以请把它当作一个 Child
。这不涉及任何转换;但是,运行将执行时间检查,如果不允许转换,则可能会抛出异常。
你被要求从栈和堆内存的角度解释它。很抱歉,但这与堆栈或堆内存完全无关。它与继承规则和分配兼容性有关。
当然,这里我们处理的是引用类型(classes)。这允许我们从 Parent
推导出 Child
。这对于值类型(结构)是不可能的。重点是引用类型与值类型,而不是堆与堆栈,这是一个实现细节。顺便说一句,class 中的值类型(结构)字段也将存储在堆中。
另请参阅:The Stack Is An Implementation Detail, Part One and Two(Eric Lippert 的博客)
您并没有要求它,但是转换为派生类型之前通常要进行类型测试,因为您通常事先不知道 运行 时间类型。假设 Child
class 添加了一个新方法 DriveParentsToDespair()
(只有 children 可以做到这一点)。然后你可以使用 Type testing with pattern matching 来测试类型并同时将其分配给一个新变量:
if (p is Child child) {
child.DriveParentsToDespair();
}
这可以避免在 p
的 运行 时间类型为 Parent
的情况下出现异常。
前几天在面试中,被要求解释以下案例:
public class Parent
{
}
public class Child:Parent
{
}
案例一:
Parent p = new Child();
var c = (Child)p;
案例2:
Parent p = new Parent();
var c = (Child)p;
我说case1
有效,case2
无效,他却问:
Why
case2
is invalid while bothp
variable incase1
andcase2
are Type ofParent
class?
在案例 2 中,我们通过以下消息得到 运行时间异常:
Unable to cast object of type 'Parent' to type 'Child
而在 case1
p
中变量也是 Parent
class.
他要我从stack
和heap
记忆的角度来解释。
你有什么想法吗?
更新:
面试官误导我的地方正是他在两种情况下所说的 p
是 'parent' class 的类型,根据答案,只是两个变量的静态类型是 'parent',但在 运行 时间它们有不同的类型,真正重要的是 运行 时间类型。
您必须区分变量的静态类型(编译时已知的类型)和分配给变量的 object 引用的 运行 时间类型。
p
的静态类型在这两种情况下都是Parent
,因为它被声明为Parent p
。不管你分配什么。您甚至可以分配它 null
.
赋值后p
的值的运行时间类型第一种情况是Child
,第二种情况是Parent
。如果 p
是 null
.
可以将 Child
分配给 Parent
,因为 Child
class 派生自 Parent
。因此,Child
是 Parent
(在 OO 术语中)。
但是,Parent
不是 Child
,因为它不是从它派生的。因此,在第二种情况下,您不能将 p
转换为 Child
。在第一种情况下,转换是有效的,因为您将 object 转换为其实际的 运行 时间类型。它告诉编译器,我知道分配给一个 Parent
变量的这个 object 是一个 Child
,所以请把它当作一个 Child
。这不涉及任何转换;但是,运行将执行时间检查,如果不允许转换,则可能会抛出异常。
你被要求从栈和堆内存的角度解释它。很抱歉,但这与堆栈或堆内存完全无关。它与继承规则和分配兼容性有关。
当然,这里我们处理的是引用类型(classes)。这允许我们从 Parent
推导出 Child
。这对于值类型(结构)是不可能的。重点是引用类型与值类型,而不是堆与堆栈,这是一个实现细节。顺便说一句,class 中的值类型(结构)字段也将存储在堆中。
另请参阅:The Stack Is An Implementation Detail, Part One and Two(Eric Lippert 的博客)
您并没有要求它,但是转换为派生类型之前通常要进行类型测试,因为您通常事先不知道 运行 时间类型。假设 Child
class 添加了一个新方法 DriveParentsToDespair()
(只有 children 可以做到这一点)。然后你可以使用 Type testing with pattern matching 来测试类型并同时将其分配给一个新变量:
if (p is Child child) {
child.DriveParentsToDespair();
}
这可以避免在 p
的 运行 时间类型为 Parent
的情况下出现异常。