↓ 跳过正文

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

·4 分钟
作者
Hexin
在确定的逻辑与不确定的现实之间,做一名耐心的构建者。
目录

示例代码
#

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 决定。