虚拟覆盖新

虚拟和覆盖

virtual 关键字允许方法,属性,索引器或事件被派生类覆盖并呈现多态行为。 (默认情况下,成员在 C#中是非虚拟的)

public class BaseClass
{
    public virtual void Foo()
    {
        Console.WriteLine("Foo from BaseClass");
    }
}

为了覆盖成员,override 关键字用于派生类。 (注意成员的签名必须相同)

public class DerivedClass: BaseClass
{
    public override void Foo()
    {
        Console.WriteLine("Foo from DerivedClass");
    }
}

虚拟成员的多态行为意味着在调用时,正在执行的实际成员是在运行时而不是在编译时确定的。在最派生类中的重写成员,特定对象是将被执行的实例。

简而言之,对象可以在编译时声明类型为 BaseClass,但如果在运行时它是 DerivedClass 的实例,则将执行被覆盖的成员:

BaseClass obj1 = new BaseClass();
obj1.Foo(); //Outputs "Foo from BaseClass"

obj1 = new DerivedClass();
obj1.Foo(); //Outputs "Foo from DerivedClass"    

覆盖方法是可选的:

public class SecondDerivedClass: DerivedClass {}

var obj1 = new SecondDerivedClass();
obj1.Foo(); //Outputs "Foo from DerivedClass"    

由于只有定义为 virtual 的成员是可覆盖和多态的,因此重新定义非虚拟成员的派生类可能会导致意外结果。

public class BaseClass
{
    public void Foo()
    {
        Console.WriteLine("Foo from BaseClass");
    }
}

public class DerivedClass: BaseClass
{
    public void Foo()
    {
        Console.WriteLine("Foo from DerivedClass");
    }
}

BaseClass obj1 = new BaseClass();
obj1.Foo(); //Outputs "Foo from BaseClass"

obj1 = new DerivedClass();
obj1.Foo(); //Outputs "Foo from BaseClass" too!    

发生这种情况时,始终根据对象的类型在编译时确定执行的成员。

  • 如果对象声明为 BaseClass 类型(即使在运行时是派生类),则执行 BaseClass 的方法
  • 如果对象声明为 DerivedClass 类型,则执行 DerivedClass 的方法。

这通常是一个意外(在将相同的成员添加到派生类型后将成员添加到基本类型中)并且在这些方案中生成编译器警告 CS0108

如果是故意的,那么 new 关键字用于抑制编译器警告(并告知其他开发人员你的意图!)。行为保持不变,new 关键字只是抑制编译器警告。

public class BaseClass
{
    public void Foo()
    {
        Console.WriteLine("Foo from BaseClass");
    }
}

public class DerivedClass: BaseClass
{
    public new void Foo()
    {
        Console.WriteLine("Foo from DerivedClass");
    }
}

BaseClass obj1 = new BaseClass();
obj1.Foo(); //Outputs "Foo from BaseClass"

obj1 = new DerivedClass();
obj1.Foo(); //Outputs "Foo from BaseClass" too! 

覆盖的使用不是可选的

与 C++不同,override 关键字的使用不是可选的:

public class A
{
    public virtual void Foo()
    {
    }
}

public class B : A
{
    public void Foo() // Generates CS0108
    {
    }
}

以上示例也会导致警告 CS0108 ,因为 B.Foo() 不会自动覆盖 A.Foo()。当意图覆盖基类并导致多态行为时添加 override,当你需要非多态行为时添加 new 并使用静态类型解析调用。后者应谨慎使用,因为它可能会导致严重的混淆。

以下代码甚至导致错误:

public class A
{
    public void Foo()
    {
    }
}

public class B : A
{
    public override void Foo() // Error: Nothing to override
    {
    }
}

派生类可以引入多态性

以下代码完全有效(尽管很少见):

    public class A
    {
        public void Foo()
        {
            Console.WriteLine("A");
        }
    }

    public class B : A
    {
        public new virtual void Foo() 
        {
            Console.WriteLine("B");
        }
    }

现在所有具有静态引用 B(及其衍生物)的对象都使用多态来解析 Foo(),而 A 的引用使用 A.Foo()

A a = new A();
a.Foo(); // Prints "A";
a = new B();
a.Foo(); // Prints "A";
B b = new B();
b.Foo(); // Prints "B";

虚拟方法不能是私有的

C#编译器严格防止无意义的构造。标记为 virtual 的方法不能是私有的。因为无法从派生类型中看到私有方法,所以也无法覆盖它。这无法编译:

public class A
{
    private virtual void Foo() // Error: virtual methods cannot be private
    {
    }
}