C#方法的過載、重寫和隱藏

2020-07-16 10:04:46
方法的過載指的是同一個型別中,允許有同名的方法,但是,這些方法的輸入引數必須不同,例如,引數型別或引數個數。

注意:由或不由 ref/out 修飾,在編譯器眼中是一樣的。

如果牽扯到可變數量的引數,那麼帶有可變數量引數的方法,和普通方法不同。C# 會優先呼叫普通方法。例如:
public void NormalMethod(int a,params int[] b)
{
    Console.WriteLine("2");
}
public void NormalMethod(int a, intb)
{
    Console.WriteLine("1");
}
看看這兩個方法,其實第一個方法已經包括了第二個,但是編譯器並不會報錯。如果我們呼叫時,傳入 2 個整數,那麼會呼叫第二個方法列印 1 :
var a = new ExampleRef();
a.NormalMethod(1,2);
C# 會先查詢沒有使用 params 的方法,如果找不到匹配的方法,才會去找帶有 paiams 的方法。

提示:只有方法的最後一個引數才可以使用 params 關鍵字。

方法的重寫和隱藏涉及一對父子型別。如果父類別型有一個虛方法,那麼,子型別可以使用 override 顯式地重寫該方法,或者使用 new 隱藏該方法。

方法的重寫會使得派生型別的方法表中,重寫的成員抹掉基礎類別的虛方法,自己取而代之。

測試程式碼如下:
class Program
{
    static void Main(string[] args)
    {
        A c1 = new C();
        c1.Foo();

        C c2 = new C();
        c2.Foo();
        Console.ReadKey();
    }
}
class A
{
    public virtual void Foo()
    {
        Console.WriteLine("Call on A.Foo()");
    }
}
class B :A
{
    public override void Foo()
    {
        Console.WriteLine("Call on B.Foo()");
    }
}
class C :B
{
    public new void Foo()
    {
        Console.WriteLine("Call on C.Foo()");
    }
}
非常簡單直接,B 重寫了 A 的方法,而 C 隱藏了 B 的方法。

另外,可以使用 sealed 關鍵字配合 override 關鍵字,禁止子類對方法進一步重寫。

而方法的隱藏通過 new 關鍵字顯式實現(實際上,標不標 new 效果是一樣的,標 new 只會消除編譯器的警告),分兩種情況:
  • 父類別是虛方法,則子類的方法表包括子類和父類別的方法。
  • 父類別是普通方法,則子類的方法表不包括父類別的方法。

方法的隱藏沒什麼特別的,不過是父子類方法撞名了而已,和普通的不撞名情況沒有區別, 在執行時呼叫哪個,取決於物件編譯時型別是什麼。

從本質上來說,當父子類存在同名的方法時,有兩種隱藏策略:按名字 (hide by name) 和按簽名 (hide by signature),C# 使用後者,也就是說,只有方法名和輸入引數完全相同時,才構成隱藏。

在方法重寫時,子類方法的可存取性必須和父類別相同,不可以收緊也不可以放寬,這是因為,CLR 承諾子類總可以轉型為父類別,如果子類的方法存取和父類別條件不同,那麼這個轉型就會岀問題。不過,方法的隱藏沒有這個限制。

趁熱打鐵一把,來看下面的例子:
class Program
{
    static void Main(string[] args)
    {
        //父類別的物件存取父類別的方法,去A的方法表
        A a = new A();
        a.NormalMethod();
        a.NewMethod();
        //子類的物件存取子類的方法,去B的方法表
        B b = new B();
        b.NormalMethod();
        b.NewMethod();
        //出現這種情況時,a2 的執行時型別為B,它會去B的方法表
        //但它的編譯時物件為A
        A a2 = new B();
        //B的方法表已經重寫了該方法,存取不到父類別的方法,只能存取子類的方法
        a2.NormalMethod();
        //B的方法表中有兩個NewMethod,根據編譯時的型別,呼叫父類別方法
        a2.NewMethod();
        Console.ReadKey();
    }
}
class A
{
    public virtual void NormalMethod()
    {
        Console.WriteLine("A.NormalMethod");
    }
    public virtual void NewMethod()
    {
        Console.WriteLine("A.NewMethod");
    }
}
class B :A
{
    public override void NormalMethod()
    {
        Console.WriteLine("B.NormalMethod");
    }
    public new void NewMethod()
    {
        Console.WriteLine("B.NewMethod");
    }
}
輸出結果如下所示。

A.NormalMethod
A. NewMethod
B. NormalMethod
B.NewMethod
B.NormalMethod
A.NewMethod