C# 向上投射及其对堆的影响
对于以下类别:C# 向上投射及其对堆的影响,c#,oop,inheritance,upcasting,C#,Oop,Inheritance,Upcasting,对于以下类别: public class Parent { //Parent members } public class ChildA : Parent { //ChildA members } public class ChildB : Parent { //ChildB members } 如果我将ChildA或ChildB实例向上转换为父实例,那么我无法访问它们的成员,但它们的成员仍然存在,因为如果我向下转换并再次尝试访问它们的成员,我会发现它们仍然有数据 我认为这意味着父实例将继
public class Parent {
//Parent members
}
public class ChildA : Parent {
//ChildA members
}
public class ChildB : Parent {
//ChildB members
}
如果我将ChildA或ChildB实例向上转换为父实例,那么我无法访问它们的成员,但它们的成员仍然存在,因为如果我向下转换并再次尝试访问它们的成员,我会发现它们仍然有数据
我认为这意味着父实例将继续为子类分配内存
那么,这是否意味着当我实例化一个父类时,它将为子类成员分配内存,或者这只是在我强制转换时发生的
如果我们向前或向后进行强制转换,父类是否可以为多个子类分配内存?在您上面描述的情况下,强制转换不会影响从基类强制转换到子类时分配的内存,反之亦然 如果实例化一个父对象,内存中将有一个父对象。如果您将其强制转换到任一子类,它将失败,并出现
InvalidCastException
如果实例化其中一个子对象,内存中将有一个子对象。您可以将其强制转换为父级,然后再次转换。在这两种情况下,内存分配都不会改变
此外,如果您实例化ChildA,强制转换到父级,然后尝试强制转换到ChildB,您将获得引用类型的“正常”向上转换和向下转换
对于引用类型,强制转换变量不会更改堆上已分配的对象的类型,它只会影响引用该对象的变量的类型
因此,不存在强制转换引用类型(即类中的对象实例)的额外堆开销
考虑以下类层次结构:
public class Fruit
{
public Color Colour {get; set;}
public bool Edible {get; set;}
}
public class Apple : Fruit
{
public Apple { Color = Green; Edible = true; KeepsDoctorAtBay = true;}
public bool KeepsDoctorAtBay{get; set;}
}
当与上浇和下浇同时使用时:
堆上只有一个分配,即初始的var foo=new Apple()
在各种变量赋值之后,所有三个变量,foo
、bar
和baz
都指向同一个对象(堆上的Apple
实例)
向上投射(Fruit bar=foo
)只会限制变量只能访问Fruit
方法和属性,如果(Apple)bar
向下投射成功,则向下投射类型的所有方法、属性和事件都将可用于变量。如果向下转换失败,将抛出InvalidCastException
,因为类型系统将在运行时检查堆对象的类型与变量类型的兼容性
转换运算符
根据tolanj的评论,如果替换引用类型的默认强制转换,那么关于堆的所有赌注都将被取消
例如,如果我们添加一个不相关的类:
public class WaxApple // Not inherited from Fruit or Apple
{
public static explicit operator Apple(WaxApple wax)
{
return new Apple
{
Edible = false,
Colour = Color.Green,
KeepsDoctorAtBay = false
};
}
}
可以想象,WaxApple的显式操作符Apple可以做任何它喜欢的事情,包括在堆上分配新对象
var wax = new WaxApple();
var fakeApple = (Apple)wax;
// Explicit cast operator called, new heap allocation as per the conversion code.
我认为这意味着父实例将继续为子类分配内存
不,因为父类不知道它的子类
var a = new ClassA();
.NET
为ClassA
的所有成员分配内存
var b = (Parent)a;
.NET
对内存没有任何作用a
和b
指向同一个内存块(分配给ClassA
)。a(向下-)转换只不过是“父类的眼睛”对类实例的视图。因此,通过强制转换,您既不会丢失也不会添加任何信息或内存,只需引用为原始实例分配的相同内存allready即可。这就是为什么您仍然可以访问(例如通过反射)类型为Parent
的变量中ChildA
的成员的原因。信息仍然存在,只是不可见
因此,不是有两个内存分配,而是有两个内存引用
但是,请注意,如果您提供自己的演员阵容,例如从ChildA
到ChildB
,则这不适用。这样做通常看起来或多或少类似于此:
public static explicit operator ChildA(ChildB b)
{
var a = new ChildA((Parent)b);
/* set further properties defined in ChildA but not in ChildB*/
}
这里有两个完全不同的实例,一个是
ChildA
类型的实例,另一个是ChildB
类型的实例,它们都消耗自己的内存。区分对象和变量。创建实例时,就是在创建对象。当您强制转换时,您是在强制转换变量,而不是对象。感谢您的回复,我是这样想的,因为父变量持有一个子变量,并且仍然可以强制转换它并访问其成员,但我知道我得到了它,感谢explain@Honey-用于参考类型(类
)变量仅保存指向已分配内存的指针。来回转换不会改变指针,也不会改变分配的内存。它仅仅改变了指针值的解释方式。对于值类型(struct
,以及int
、float
、byte
等基本类型),值存储在变量本身中。由于无法继承值类型,大多数强制转换(如从int
到float
)只需执行一些转换并将结果存储在目标变量中。强制转换不会影响在任何情况下分配的内存。->对于这种类型的强制转换(子类->父类,类->实现接口等),为true,可能值得注意的是,这仅在“存在隐式/显式cast运算符、使用并分配内存的情况除外”的任何情况下才是正确的@亲爱的-大多数情况下没有额外的内存分配,除非您在cast运算符中做了一些奇特的操作。实际上,class
es也是如此-如果定义了显式/隐式cast操作符,则所有赌注都是无效的。最后,可以将值类型强制转换为对象
(这是一种引用类型),这涉及到“装箱”-thi