CodeGym /课程 /C# SELF /C#中的方法重写(Method Overriding)

C#中的方法重写(Method Overriding)

C# SELF
第 21 级, 课程 2
可用

1. 入门

如果你现在一听到重写就有点慌,别怕——其实很简单还超好用!方法重写就是让你在子类里把父类的方法行为换成自己的。这样代码就变得灵活、可扩展,还能应对真实世界,比如每只动物都不想只是“发出某种声音”。

想让方法能被重写,父类要用关键字virtual标记它。子类想换实现,就用关键字override。

例子


public class Animal
{
    public string Name { get; set; }
    public virtual void MakeSound()
    {
        Console.WriteLine("某种通用的动物声音...");
    }
}
        

现在在Dog类里:


public class Dog : Animal
{
    public override void MakeSound()
    {
        Console.WriteLine("汪汪!");
    }
}
        
  • 父类用virtual。
  • 子类用override。
  • 方法签名(名字、返回类型、参数)必须一样。

可视化图解

Dog继承Animal,可以重写MakeSound()

2. 实际演示

来看看重写到底怎么用。我们创建几个动物,听听它们的声音:

Animal pet1 = new Animal { Name = "无名动物" };
Dog pet2 = new Dog { Name = "巴博斯" };

pet1.MakeSound(); // 输出:某种通用的动物声音...
pet2.MakeSound(); // 输出:汪汪!

现在来点难的:
如果我们把Dog存到Animal类型的变量里会怎样?

Animal pet3 = new Dog { Name = "沙里克" };
pet3.MakeSound(); // ???

你觉得会发生什么?
答案:会输出“汪汪!”
因为虽然变量类型是Animal,但它“指向”的其实是Dog对象,所以会调用重写后的方法!
这就是动态(或延迟)绑定的魔法。

3. 重写时用base关键字

有时候你不想完全替换方法实现,只想扩展一下——比如加点自己的东西,再执行原来的逻辑。这时就用base关键字。它能让你调用父类的方法版本。


public class Cat : Animal
{
    public override void MakeSound()
    {
        base.MakeSound(); // 调用父类实现
        Console.WriteLine("喵!");
    }
}
        

调用这个方法时,先会输出“某种通用的动物声音...”,然后“喵!”

4. 重写时方法选择的原理

想直观理解“底层”发生了啥,可以想象下面这张表(虚方法分派):

变量类型 对象类型 会调用哪个方法
Animal Animal Animal.MakeSound
Animal Dog Dog.MakeSound
Animal Labrador Labrador.MakeSound
Dog Labrador Labrador.MakeSound
Dog Dog Dog.MakeSound

核心规则:
变量类型只对编译器有用,运行时看的是实际对象类型(就是你用new创建的那个)。
这个机制叫动态(或延迟)绑定——多态就是靠它实现的(下节课会详细讲!)。

为啥要重写方法

  • 在GUI框架里:你有个窗口基类,重写方法来画具体控件。
  • 在游戏引擎里:有个Enemy基类,子类实现不同的行为。
  • 在单元测试里:可以为方法造“桩”(stubs, mocks)。

现代.NET框架大量用这个机制搞事件、模板代码、继承配置,甚至对象序列化(比如虚属性)。

5. 用new关键字隐藏方法

我们已经知道重写方法要用virtual/override这对组合。但C#还有一个和继承方法有关的修饰符——那就是new。

new有啥用?

new用在子类里声明和父类同签名的方法,但你不想重写虚方法,而是隐藏(遮住)父类的方法。

  • 这不是重写,是隐藏。
  • 这种方法是按变量类型调用的,不看实际对象类型(没有动态多态!)。
  • 如果你“无意”隐藏了方法但没写new,编译器会警告你。

例子:override和new的区别


public class Animal
{
    public virtual  void MakeSound()
    {
        Console.WriteLine("动物发出某种声音...");
    }
}

public class Dog : Animal
{
    // 隐藏父类方法(不是重写)
    public new void MakeSound()
    {
        Console.WriteLine("这不是override!只是Dog的方法。");
    }
}
        

现在看看行为:

Animal a = new Dog();
Dog d = new Dog();

a.MakeSound(); // "动物发出某种声音..."
d.MakeSound(); // "这不是override!只是Dog的方法。"
  • 变量类型是Dog——会调用Dog的方法。
  • 变量类型是Animal,即使里面装的是Dog——也会调用Animal的方法!

6. 反馈和实现细节

刚学编程时经常会遇到“明明重写了方法,怎么还是老样子”的困惑。通常原因很简单:父类没写virtual,或者子类方法用了new而不是override。第二种尤其坑——如果用父类变量调用,会走父类方法,不是重写的那个。所以一定要注意关键字用对了。

除了语法错误,新手有时还想在重写时改返回类型。比如父类方法返回object,子类想返回string。这样不行:方法签名必须完全一样。

对比表:override vs new

特点 override new
机制 重写虚方法 隐藏父类方法
延迟绑定 有——支持动态多态 没有——按变量类型
要求base里有... virtual、abstract或已经override 不需要
推荐使用 几乎总是 只有特殊情况

7. 重写方法和继承链

有了长继承链,重写才真正有意思:


public class Animal
{
    public virtual void MakeSound()
    {
        Console.WriteLine("动物在做点啥...");
    }
}

public class Dog : Animal
{
    public override void MakeSound()
    {
        Console.WriteLine("汪汪!");
    }
}

public class Labrador : Dog
{
    public override void MakeSound()
    {
        Console.WriteLine("我是拉布拉多:汪-汪!");
    }
}
        

如果我们写:

Animal pet = new Labrador();
pet.MakeSound(); //   => "我是拉布拉多:汪-汪!"

C#总是选“最深”的虚方法实现,也就是对象本身的那个。

8. 重写方法的常见错误

世界不完美,学生(还有老司机!)有时会踩坑。我们来提前避开最常见的“雷”:

1. 父类忘了virtual


public class Animal
{
    public void MakeSound() { ... } // 没有'virtual'
}

public class Dog : Animal
{
    // 编译错误!不能重写。
    public override void MakeSound()
    {
        Console.WriteLine("汪!");
    }
}
        

C#会直接报错:'Dog.MakeSound()': 没有可重写的方法

2. 签名不一致

一定要保证方法名、返回类型和参数都一样:


public class Animal
{
    public virtual void MakeSound() { ... }
}

public class Dog : Animal
{
    // 错误:签名不同(比如多了参数)
    public override void MakeSound(string sound)
    {
        Console.WriteLine(sound);
    }
}
        

3. 没必要别用new代替override

new关键字可以隐藏父类方法,但这不是重写,也不支持动态多态。一般没特殊需求别用它。

2
任务
C# SELF, 第 21 级, 课程 2
已锁定
重写时使用基类方法
重写时使用基类方法
2
任务
C# SELF, 第 21 级, 课程 2
已锁定
继承链的操作
继承链的操作
评论
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION