重载是许多编程语言支持的特性。所谓重载,就是指可以定义多个名称相同但参数(个数、类型和顺序)不同的方法(函数)。先来看一个例子:

void Main()
{
char cvalue = 'a';
male m = new male();
m.write(cvalue);
} class human
{
public void write(char value)
{
Console.WriteLine("char:" + value);
}
} class male : human
{
public void write(int value)
{
Console.WriteLine("int:" + value);
}
}

这个例子中,父类human中有个一个参数类型为char的函数write,子类(derived class)male中提供了参数类型为int的重载函数,在Main方法中实例化了一个子类male的对象m,对象m调用write方法,并传递char类型的参数。最终执行的结果是int:97,而不是char:a。为何在函数调用的时候,没有找到父类中参数类型完全匹配的函数,而是进行了类型转换呢?

有人说这是因为 .NET的类型推断(type inference)不够智能。其实,这里并不涉及类型推断,因为类型推断主要是针对隐式类型和泛型的。

这里是由重载决策机制决定的。重载决策是一种绑定时机制,用于在给定参数列表和一组候选函数成员的情况下选择要调用的最佳函数成员。c#中支持重载的有以下几种情况:

  • 方法(函数)重载
  • 构造函数重载
  • 索引器重载
  • 操作符重载

虽然上述四种情况都有自己独有地定义重载函数和参数列表的方式,但是重载决策选择最佳函数调用地过程是一致的。

  • 首先,根据给定的参数列表从候选函数集合中找到适用的函数成员,如果没有找到则会报编译错误
  • 然后,从适用的候选函数成员集中找到最佳函数成员。如果集合只包含一个函数成员,则该函数成员是最佳函数成员。否则,根据更好的函数成员规则,找到相对于其他函数成员更好的一个函数成员作为最佳函数,如果没有一个函数成员优于所有其他函数成员,则函数成员调用不明确,并且会发生绑定时错误。

根据C# Language Specification成员查找(Member lookup)的描述,方法调用的候选集不包括标记为override的方法。根据方法调用(Method invocations)的描述,子类中只要有一个方法适用,则父类的函数不是候选函数。这也就解释了开篇的例子中为何没有选择父类中参数类型完全匹配的函数。

适用的函数成员

当满足以下所有条件时,函数成员被称为与参数列表A的适用函数成员:

  • 参数列表A中的每实参都对应于函数成员声明中的一个参数,每个形参最多对应一个实参,并且任何没有实参对应的形参都是可选形参。
  • 参数列表A中的每个实参,实参的传递模式与对应形参的传递模式相同。
    • 对于值类型参数或者参数数组,允许实参到对应形参存在隐式转换
    • 对于带有ref或者out修饰符的参数,允许实参到对应形参存在恒等转换(identity conversion)
    • 对于带有in修饰符的参数,允许实参到对应形参存在恒等转换(identity conversion)
    • 对于in传递模式,如果没有带in修饰符,允许实参到对应形参存在隐式转换

接下来用几个例子来说明函数是否适用

void Main()
{
int i = 10; uint ui = 34U;var vi=5;dynamic di=5; M1(in i); // M1(in int)合适
M1(in ui); // 没有精准匹配, 因此M1(in int)不适用
M1(i); // M1(int) 和 M1(in int)都适用
M1(ui); // uint到int不存在隐式转换,所以M1(int) 不适用
M1(in vi); //恒等转换(identity conversion),M1(in int)适用 M2(ui); //uint隐式转换为long,因此M2(long)适用;同时传参没带in修饰符,允许装箱为object的隐式转换,M2(in object)适用
M2(di); //不适用
M2(in di) //恒等转换(identity conversion),M2(in object)适用
M2(in vi) //恒等转换(identity conversion),M2(in object)适用 M3(ui) //ref和out传递模式不支持忽略ref和out修饰符匹配,因此M3(ref object)不适用
} public static void M1(int p1) { Console.WriteLine("M1 int:"+p1); }
public static void M1(in int p1) { Console.WriteLine("M1 in int:"+p1); } public static void M2(long p1) { Console.WriteLine("M2 long:"+p1); }
public static void M2(in object p1) { Console.WriteLine("M2 in object"+p1); } public static void M3(ref object p1) { Console.WriteLine("M3 ref object" + p1); }

更好的成员函数

假设调用函数时传递的参数为{E₁, E₂, ..., Eᵥ},有两个适用的函数Mᵥ(P₁, P₂, ..., Pᵥ)Mₓ(Q₁, Q₂, ..., Qᵥ),满足以下条件时则认为Mᵥ是更合适的函数:

  • 对于每一个参数,从EᵥQᵥ的隐式转换没有比EᵥPᵥ的隐式转换更好
  • 至少有一个参数满足,从EᵥPᵥ的转换比EᵥQᵥ的转换好。

如果按照上述规则比较,函数Mᵥ(P₁, P₂, ..., Pᵥ)Mₓ(Q₁, Q₂, ..., Qᵥ)是等价的(例如每个PᵢQᵢ是恒等转换关系),则继续根据以下规则判断更好的函数:

  • 如果Mᵢ是非泛型方法,而Mₑ是泛型方法,则认为Mᵢ更合适
  • 如果Mᵢ是普通方法,而Mᵢ是扩展方法,则认为Mᵢ更合适
  • 如果MᵢMᵢ都是扩展方法,并且Mᵢ的参数更少,则认为Mᵢ更合适
  • 如果Mᵢ的参数中有比Mᵢ的对应参数更具体地类型,则认为Mᵢ更合适

更好的参数传递模式

当两个重载方法中对应的形参仅在形参传递模式上不同,并且两个函数形参中的一个具有值传递模式,例如

public static void M1(int p1) { ... }
public static void M1(in int p1) { ... }

前边适用的函数成员部分提到,调用M(10)方法时,两个重载方法都适用。这种情况下,值传递模式是更好的参数传递模式。

ref和out传递模式不支持忽略ref和out修饰符匹配,必须精准匹配

C#的重载决策的更多相关文章

  1. 17.C#类型判断和重载决策(九章9.4)

    今天来结束第九章,聊下我们经常忽略,但是编译器会帮我们完成的"类型判断和重载决策",理解编译器如何帮我们完成,相信在写代码时会更明确,避免一些编译出错,排查的问题,让我们开发更给力 ...

  2. java重载和重写的区别

    一.重载(Overloading) (1) 方法重载是让类以统一的方式处理不同类型数据的一种手段.多个同名函数同时存在,具有不同的参数个数/类型. 重载Overloading是一个类中多态性的一种表现 ...

  3. JAVA中的重载和重写

    重载(Overloading) (1) 方法重载是让类以统一的方式处理不同类型数据的一种手段.多个同名函数同时存在,具有不同的参数个数/类型. 重载(Overloading)是一个类中多态性的一种表现 ...

  4. java 重写 重载

    首先我们来讲讲:重载(Overloading) (1) 方法重载是让类以统一的方式处理不同类型数据的一种手段.多个同名函数同时存在,具有不同的参数个数/类型. 重载Overloading是一个类中多态 ...

  5. 重载(Overloading)以及模板(Template)

    继续<C++ premier plus>的学习 (1)函数重载,通俗来说,就是相同的函数名字名下,存在多个函数,要使得这成立,各个同名函数必须形参列表(也称为"签名", ...

  6. Java的重载和重写差别(面试常见)

    今天在看C#的基础知识,同是面向对象的语言.看到重载和重写.我突然想了半天.有点模糊了,立即度娘一番.回忆起自己在北京实习的项目,实际上,开发中经经常使用到重载和重写,自己不去总结罢了.今天找了一份比 ...

  7. java 重载与重写 【转】

    首先我们来讲讲:重载(Overloading) (1) 方法重载是让类以统一的方式处理不同类型数据的一种手段.多个同名函数同时存在,具有不同的参数个数/类型. 重载Overloading是一个类中多态 ...

  8. C++ 重载运算符和重载函数

    C++ 重载运算符和重载函数 C++ 允许在同一作用域中的某个函数和运算符指定多个定义,分别称为函数重载和运算符重载. 重载声明是指一个与之前已经在该作用域内声明过的函数或方法具有相同名称的声明,但是 ...

  9. C++解析七-重载运算符和重载函数

    重载运算符和重载函数C++ 允许在同一作用域中的某个函数和运算符指定多个定义,分别称为函数重载和运算符重载.重载声明是指一个与之前已经在该作用域内声明过的函数或方法具有相同名称的声明,但是它们的参数列 ...

  10. c++程序设计中的函数重载

    函数重载的意思是在一个作用域内(命名空间内)定义了某个或某些具有相同名称的函数,但是他们的参数列表和定义(实现)不相同,如果相同的话,就没啥意义了.当调用一个重载函数时,编译器会通过所使用的参数类型. ...

随机推荐

  1. 揭秘Karmada百倍集群规模多云基础设施体系

    摘要:本文结合Karmada社区对大规模场景的思考,揭示Karmada稳定支持100个大规模集群.管理超过50万个节点和200万个Pod背后的原理 本文分享自华为云社区<Karmada百倍集群规 ...

  2. SDK日志上传性能优化

    问题描述 在SDK初始化时,会在init方法中开启一个倒计时,在5s倒计时结束后使用子线程将本地保存的历史日志信息上传到后台. 因业务需要,在日志在发送上传前,对日志数据上传时需要对日志数据做编码和特 ...

  3. 2023-01-07:hyper/docker-registry-web是registry的web界面工具之一。请问部署在k3s中,yaml如何写?

    2023-01-07:hyper/docker-registry-web是registry的web界面工具之一.请问部署在k3s中,yaml如何写? 答案2023-01-07: yaml如下: api ...

  4. 2022-09-27:给定一个棵树, 树上每个节点都有自己的值,记录在数组nums里, 比如nums[4] = 10,表示4号点的值是10, 给定树上的每一条边,记录在二维数组edges里, 比如ed

    2022-09-27:给定一个棵树, 树上每个节点都有自己的值,记录在数组nums里, 比如nums[4] = 10,表示4号点的值是10, 给定树上的每一条边,记录在二维数组edges里, 比如ed ...

  5. 2022-05-04:比如,str = “ayxbx“, 有以下4种切法 : a | yxbx、ay | xbx、ayx | bx、ayxb | x, 其中第1、3、4种切法符合:x和y的个数,至少在

    2022-05-04:比如,str = "ayxbx", 有以下4种切法 : a | yxbx.ay | xbx.ayx | bx.ayxb | x, 其中第1.3.4种切法符合: ...

  6. 6R机械臂运动规划及仿真

    博客地址:https://www.cnblogs.com/zylyehuo/ 参考链接 Moveit!机械臂控制 文件下载-古月ROS教程视频配套资料 解决Could not find a packa ...

  7. [xctf] 江苏工匠杯easyphp

    [xctf] 江苏工匠杯easyphp 考点 PHP弱类型 一.题目 <?php highlight_file(__FILE__); $key1 = 0; $key2 = 0; $a = $_G ...

  8. 有JSDoc还需要TypeScript吗

    这听起来是不是很耳熟:你想写一个小型脚本,不管是为页面.命令行工具,还是其他什么类型.你从JavaScript开始,直到你想起写代码时没有类型是多么痛苦.所以你把文件从.js重命名为.ts.然后意识到 ...

  9. 用 Python 帮运营妹纸快速搞定 Excel 文档

    Microsoft Office 被广泛用于商务和运营分析中, 其中 Excel 尤其受欢迎.Excel 可以用于存储表格数据.创建报告.图形趋势等.在深入研究用 Python 处理 Excel 文档 ...

  10. 攻防世界_ezmaze

    题目:ezmaze re选手投递区 链接:https://adworld.xctf.org.cn/challenges/details?hash=8254ba70-6bfd-11ed-ab28-000 ...