C#中重载方法的一些总结
新建一个.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#中如果类的扩展方法和类本身的方法签名相同,那么会优先调用类本身的方法