目录

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.

二、newoverride 的区别

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