虛擬覆蓋新

虛擬和覆蓋

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
    {
    }
}