新建一個.NET Core控制台項目,我們來看看C#中重載方法的一些注意事項。
C#中多個重載方法之間的參數如果有繼承關系,那么調用方法時,會調用與傳入參數類型最接近的重載方法
我們來舉個例子,下面我們定義了兩個重載方法Do,它們的參數類型A和B是繼承關系,類B繼承類A,那么我們在調用Do方法時,到底調用的是哪一個重載呢?
代碼如下:
using System; namespace NetCoreOverloadParameters { /// <summary> /// 類A /// </summary> class A { } /// <summary> /// 類B繼承類A /// </summary> class B : A { } class Program { /// <summary> /// 方法Do /// </summary> /// <param name="a">參數類型為A</param> static void Do(A a) { Console.WriteLine("Do A"); } /// <summary> /// 方法Do /// </summary> /// <param name="a">參數類型為B</param> static void Do(B a) { Console.WriteLine("Do B"); } static void Main(string[] args) { A a = new B(); B b = new B(); Do(a);//因為傳入Do方法的類型是A,所以這里調用Do(A a) Do(b);//因為傳入Do方法的類型是B,所以這里調用Do(B a) Do(new A());//因為傳入Do方法的類型是A,所以這里調用Do(A a) Do(new B());//因為傳入Do方法的類型是B,所以這里調用Do(B a) Do(null);//當傳入null給Do方法時,這里調用的是Do(B a),說明優先調用的是繼承鏈中參數為子類B的重載方法 Do((A)null);//因為傳入Do方法的類型是A,所以這里調用Do(A a) Console.WriteLine("Press any key to end..."); Console.ReadKey(); } } }
執行結果如下:
所以我們可以看到,實際上在每次調用Do方法時,C#會選擇調用和傳入參數類型最接近的重載方法。
如果我們現在注釋掉代碼中的重載方法Do(B a),由於現在代碼中只有一個Do方法了,所以所有的調用都會調用Do(A a):
using System; namespace NetCoreOverloadParameters { /// <summary> /// 類A /// </summary> class A { } /// <summary> /// 類B繼承類A /// </summary> class B : A { } class Program { /// <summary> /// 方法Do /// </summary> /// <param name="a">參數類型為A</param> static void Do(A a) { Console.WriteLine("Do A"); } /// <summary> /// 方法Do /// </summary> /// <param name="a">參數類型為B</param> //static void Do(B a) //{ // Console.WriteLine("Do B"); //} static void Main(string[] args) { A a = new B(); B b = new B(); Do(a); Do(b); Do(new A()); Do(new B()); Do(null); Do((A)null); Console.WriteLine("Press any key to end..."); Console.ReadKey(); } } }
執行結果如下:
C#中如果有簽名相同的泛型方法和非泛型方法,那么C#會優先考慮調用非泛型方法
我們來看看下面這個例子,我們在代碼中,定義了三個Do方法,其中一個非泛型方法和兩個泛型方法,那么我們在調用Do方法時,C#會優先考慮調用非泛型方法:
using System; namespace NetCoreOverloadParameters { /// <summary> /// 類A /// </summary> class A { } /// <summary> /// 泛型靜態類Container<T1> /// </summary> static class Container<T1> { /// <summary> /// 方法Do /// </summary> /// <param name="a">參數類型為A</param> public static void Do(A a) { Console.WriteLine("Do A"); } /// <summary> /// 方法Do /// </summary> /// <param name="t1">參數類型為T1</param> public static void Do(T1 t1) { Console.WriteLine("Do T1"); } /// <summary> /// 方法Do /// </summary> /// <typeparam name="T2">類型參數T2</typeparam> /// <param name="t2">參數類型為T2</param> public static void Do<T2>(T2 t2) { Console.WriteLine("Do T2"); } } class Program { static void Main(string[] args) { A a = new A(); Container<A>.Do(a);//C#會優先考慮非泛型的方法,來匹配傳入Do方法的類型,所以這里會調用重載方法Do(A a),這會導致泛型重載方法Do(T1 t1),無法被調用到 Container<object>.Do((object)a);//將靜態類Container<T1>的類型參數<T1>定義為object后,再給Do方法傳入object類型的參數,這樣C#就會優先調用泛型重載方法Do(T1 t1)了 Container<A>.Do<A>(a);//由於這里我們顯式聲明了類型參數<T2>,所以C#知道我們在這里要調用的是重載方法Do<T2>(T2 t2) Console.WriteLine("Press any key to end..."); Console.ReadKey(); } } }
執行結果如下:
在這個例子中,我們可以看到,由於我們在Main方法中調用Container<A>.Do(a)時,其可以匹配兩個簽名相同的重載方法,一個是非泛型方法Do(A a),另一個是泛型方法Do(T1 t1),但是C#優先選擇的是非泛型方法Do(A a),這導致了當傳入Do方法的參數類型是A時,泛型方法Do(T1 t1)無法被調用到。
關於泛型方法的重載問題,可以參考:Generic methods and method overloading
C#中擴展方法和非擴展方法的重載
關於這個討論,可以參考:C#中如果類的擴展方法和類本身的方法簽名相同,那么會優先調用類本身的方法