C#-向上转型与方法隐藏(new)

目录
示例代码
class MyBaseClass
{
public void Print()
{
Console.WriteLine("This is the base class.");
}
}
class MyDerivedClass : MyBaseClass
{
public int var1;
new public void Print()
{
Console.WriteLine("This is the derived class.");
}
}
class Program
{
static void Main()
{
MyDerivedClass derived = new();
MyBaseClass mybc = derived;
// MyBaseClass mybc = (MyBaseClass)derived;
derived.Print();
mybc.Print();
}
}输出:
This is the derived class.
This is the base class.一、为什么 mybc.Print() 输出基类版本
原因:使用了 new 而不是 override
new public void Print()
{
Console.WriteLine("This is the derived class.");
}这里的 new 表示:
隐藏(Hide)基类中的同名方法。
它不会产生多态。
因此:
MyBaseClass mybc = derived;
mybc.Print();编译器看到 mybc 的类型是:
MyBaseClass于是直接调用:
MyBaseClass.Print()输出:
This is the base class.二、new 与 override 的区别
使用 new
class MyBaseClass
{
public void Print()
{
Console.WriteLine("Base");
}
}
class MyDerivedClass : MyBaseClass
{
new public void Print()
{
Console.WriteLine("Derived");
}
}
class Program
{
static void Main()
{
MyDerivedClass d = new();
MyBaseClass b = d;
d.Print();
b.Print();
}
}输出
Derived
Base特点
- 隐藏基类方法
- 不支持多态
- 编译时决定调用哪个方法
- 根据变量类型决定调用哪个版本
使用 virtual + override
class MyBaseClass
{
public virtual void Print()
{
Console.WriteLine("Base");
}
}
class MyDerivedClass : MyBaseClass
{
public override void Print()
{
Console.WriteLine("Derived");
}
}
class Program
{
static void Main()
{
MyDerivedClass d = new();
MyBaseClass b = d;
d.Print();
b.Print();
}
}输出
Derived
Derived特点
- 重写基类方法
- 支持多态
- 运行时决定调用哪个方法
- 根据对象实际类型决定调用哪个版本
对比总结
| 特性 | new |
override |
|---|---|---|
| 作用 | 隐藏方法 | 重写方法 |
| 是否多态 | 否 | 是 |
| 调用依据 | 变量类型 | 对象实际类型 |
| 决定时间 | 编译时 | 运行时 |
三、向上转型(Upcasting)
什么是向上转型
MyDerivedClass derived = new();
MyBaseClass mybc = derived;这里发生了:
MyDerivedClass → MyBaseClass称为:
向上转型(Upcasting)
隐式向上转型
MyBaseClass mybc = derived;编译器自动完成转换。
因为:
每个 MyDerivedClass 都是一个 MyBaseClass所以永远安全。
显式向上转型
MyBaseClass mybc = (MyBaseClass)derived;与:
MyBaseClass mybc = derived;效果完全相同。
四、是否应该写显式转换
常规项目代码
推荐:
MyBaseClass mybc = derived;原因:
- 简洁
- 符合 C# 编码习惯
- 转换本身已经安全
教学场景
可以写:
MyBaseClass mybc = (MyBaseClass)derived;用于强调:
MyDerivedClass
↓
MyBaseClass发生了一次向上转型。
五、向上转型后对象是否发生变化
没有变化
MyDerivedClass derived = new();
MyBaseClass mybc = derived;对象始终只有一个:
MyDerivedClass对象不会生成新的对象。
验证
Console.WriteLine(mybc.GetType().Name);输出:
MyDerivedClass说明:
对象仍然是 MyDerivedClass六、引用与对象的关系
内存模型
MyDerivedClass derived = new();
MyBaseClass mybc = derived;可以理解为:
derived ───┐
│
▼
MyDerivedClass对象
▲
│
mybc ──────┘两个变量引用同一个对象。
验证
Console.WriteLine(ReferenceEquals(derived, mybc));输出:
True说明:
derived 和 mybc 引用同一个对象七、派生类对象的组成
例如:
class MyBaseClass
{
public int x;
}
class MyDerivedClass : MyBaseClass
{
public int y;
}一个派生类对象可以理解为:
┌────────────────────┐
│ MyBaseClass部分 │
│ x │
├────────────────────┤
│ MyDerivedClass部分 │
│ y │
└────────────────────┘因此常说:
派生类对象包含基类部分和派生类新增部分。
八、向上转型后能访问哪些成员
示例
class MyDerivedClass : MyBaseClass
{
public int var1;
}
MyDerivedClass derived = new();
MyBaseClass mybc = derived;可以访问
mybc.Print();因为:
Print 定义在基类中不能访问
mybc.var1;编译错误:
MyBaseClass 中不存在 var1为什么
因为:
mybc 的静态类型是 MyBaseClass编译器只允许访问:
MyBaseClass声明的成员九、“mybc 指向基类部分” 的理解
很多初学者会这样理解:
derived 指向整个对象
mybc 指向对象中的基类部分这种理解有一定帮助,但不够准确。
更准确的理解
应该理解为:
derived 和 mybc 都引用整个对象区别只是:
derived
↓
可以看到 Base + Derived
mybc
↓
只能看到 Base因此:
对象没有变
变的是引用的静态类型或者说:
变的是观察对象的视角十、向下转型(Downcasting)
与向上转型相反:
MyBaseClass mybc = new MyDerivedClass();
MyDerivedClass d = (MyDerivedClass)mybc;这里必须显式转换:
(MyDerivedClass)因为:
不是每个 MyBaseClass 都是 MyDerivedClass例如:
MyBaseClass b = new MyBaseClass();此时:
MyDerivedClass d = (MyDerivedClass)b;运行时会抛出异常。
十一、核心结论
关于对象
- 派生类对象包含基类部分和派生类新增部分。
- 向上转型不会创建新对象。
- 对象的实际类型不会改变。
关于引用
MyBaseClass mybc = derived;本质是:
将派生类引用转换为基类引用。
不是:
将派生类对象转换为基类对象。
关于成员访问
向上转型后:
只能直接访问基类声明的成员但对象仍然是完整的派生类对象。
关于方法调用
使用 new
根据变量类型决定调用哪个方法
MyBaseClass mybc = derived;
mybc.Print();调用:
MyBaseClass.Print()使用 virtual + override
根据对象实际类型决定调用哪个方法
MyBaseClass mybc = derived;
mybc.Print();调用:
MyDerivedClass.Print()总结
向上转型不会改变对象本身,只会改变引用的静态类型;对象始终是完整的派生类对象,而引用能够访问哪些成员由其静态类型决定,多态行为则由
virtual/override决定。