虚方法和隐藏方法

虚方法(Virtual Method)

定义

在 C# 中,虚方法是在基类中使用virtual关键字修饰的方法。它允许派生类重写(override)该方法的实现。虚方法提供了一种多态性的机制,使得在运行时可以根据对象的实际类型来调用相应的方法。

  • 示例
  • 考虑一个简单的动物类层次结构。首先有一个基类Animal:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    class Animal
    {
    public virtual void MakeSound()
    {
    Console.WriteLine("The animal makes a sound.");
    }
    }
    然后有一个派生类Dog,它重写了MakeSound方法:
    csharp
    class Dog : Animal
    {
    public override void MakeSound()
    {
    Console.WriteLine("The dog barks.");
    }
    }

    调用方式

    当通过基类引用指向派生类对象时,调用虚方法会根据对象的实际类型(即派生类类型)来执行相应的方法。例如:

    1
    2
    Animal animal = new Dog();
    animal.MakeSound(); // 输出 "The dog barks."

    这展示了多态性,因为即使animal被声明为Animal类型,但实际执行的是Dog类中重写后的MakeSound方法。

    注意事项

    虚方法在基类中定义了一个基本的方法框架,派生类可以根据自己的需求进行定制化的重写。重写方法时,必须使用override关键字,并且方法签名(方法名称、参数列表和返回类型)必须与基类中的虚方法完全一致。


    隐藏方法(Method Hiding)

    定义

    当派生类中定义了一个与基类中方法同名的方法,但没有使用override关键字(或者基类方法不是虚方法)时,就发生了方法隐藏。方法隐藏是通过在派生类方法前使用new关键字来显式地表示这种隐藏意图。

  • 示例
  • 假设还是刚才的Animal类,但是这次MakeSound方法不是虚方法:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    class Animal
    {
    public void MakeSound()
    {
    Console.WriteLine("The animal makes a sound.");
    }
    }
    然后在派生类Cat中隐藏这个方法:
    csharp
    class Cat : Animal
    {
    new public void MakeSound()
    {
    Console.WriteLine("The cat meows.");
    }
    }

    调用方式

    与虚方法不同,隐藏方法的调用取决于引用变量的类型,而不是对象的实际类型。例如:

    1
    2
    3
    4
    Animal animal = new Cat();
    animal.MakeSound(); // 输出 "The animal makes a sound."
    Cat cat = new Cat();
    cat.MakeSound(); // 输出 "The cat meows."

    注意事项

    方法隐藏可能会导致一些意外的行为,因为它不像虚方法那样遵循多态性原则。一般来说,如果想要实现多态性,应该优先考虑使用虚方法和重写,而方法隐藏主要用于在派生类中提供一个与基类同名但行为不同的方法,并且明确地表明这种行为是独立于基类方法的意图。

    抽象类(Abstract Class)

    定义与特点

    抽象类是使用 abstract 关键字修饰的类,它不能被实例化,主要用于作为其他类的基类,为派生类提供一个通用的模板或框架。

    抽象类可以包含抽象方法、非抽象方法、属性、字段等成员。抽象方法是使用 abstract 关键字修饰的方法,它只有方法声明,没有方法体,派生类必须实现这些抽象方法。

  • 示例代码
  • 1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    // 定义抽象类 Animal
    abstract class Animal
    {
    // 抽象方法,派生类必须实现
    public abstract void MakeSound();

    // 非抽象方法
    public void Eat()
    {
    Console.WriteLine("The animal is eating.");
    }
    }

    // 派生类 Dog 继承自抽象类 Animal
    class Dog : Animal
    {
    // 实现抽象方法 MakeSound
    public override void MakeSound()
    {
    Console.WriteLine("The dog barks.");
    }
    }

    class Program
    {
    static void Main()
    {
    // 不能实例化抽象类
    // Animal animal = new Animal(); // 这行代码会编译错误

    // 可以使用派生类实例化
    Dog dog = new Dog();
    dog.MakeSound(); // 输出: The dog barks.
    dog.Eat(); // 输出: The animal is eating.
    }
    }

    代码解释

    Animal 是一个抽象类,其中包含一个抽象方法 MakeSound 和一个非抽象方法 Eat。
    Dog 类继承自 Animal 类,必须实现 MakeSound 方法。
    在 Main 方法中,不能直接实例化抽象类 Animal,但可以实例化派生类 Dog,并调用其方法。

    使用场景

    当你需要定义一组相关类的通用行为,但某些行为的具体实现需要由派生类来完成时,可以使用抽象类。例如,图形类可以定义为抽象类,其中包含计算面积的抽象方法,不同的图形(如圆形、矩形)继承自该抽象类并实现具体的计算方法。

    密封类(Sealed Class)

    定义与特点

    密封类是使用 sealed 关键字修饰的类,它不能被继承,即不能有派生类。
    密封类通常用于防止其他类继承和修改其行为,从而保证类的安全性和稳定性。密封类可以包含各种成员,与普通类的区别在于它不能作为基类。

  • 示例代码
  • 1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    // 定义密封类 Circle
    sealed class Circle
    {
    private double radius;

    public Circle(double radius)
    {
    this.radius = radius;
    }

    public double CalculateArea()
    {
    return Math.PI * radius * radius;
    }
    }

    // 尝试继承密封类,会导致编译错误
    // class DerivedCircle : Circle { }

    class Program
    {
    static void Main()
    {
    Circle circle = new Circle(5);
    double area = circle.CalculateArea();
    Console.WriteLine($"The area of the circle is: {area}");
    }
    }

    代码解释

    Circle 是一个密封类,它包含一个私有字段 radius、一个构造函数和一个计算面积的方法 CalculateArea。
    尝试定义一个继承自 Circle 的类 DerivedCircle 会导致编译错误,因为密封类不能被继承。
    在 Main 方法中,可以正常实例化密封类 Circle 并调用其方法。

    使用场景

    当你希望某个类的实现细节不被其他类修改,或者某个类的设计已经是最终版本,不需要再进行扩展时,可以使用密封类。例如,一些工具类、单例类等可以设计为密封类。
    综上所述,抽象类用于提供通用的抽象模板,促进代码的复用和多态性;而密封类用于限制类的继承,保证类的安全性和稳定性。