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

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. #Powerquery 利用M函数合并文件(CSV、Text、Xlsx)

    在日常工作中,我们往往会遇到多个文件需要合并的情况,本文一起探讨一下利用M函数合并文件的案例. 由于需要合并的文件的格式不同,也需要选择不同的M函数来进行合并,本文将分享三个格式的合并案例. 首先介绍 ...

  2. 2023-01-04:有三个题库A、B、C,每个题库均有n道题目,且题目都是从1到n进行编号 每个题目都有一个难度值 题库A中第i个题目的难度为ai 题库B中第i个题目的难度为bi 题库C中第i个题目

    2023-01-04:有三个题库A.B.C,每个题库均有n道题目,且题目都是从1到n进行编号 每个题目都有一个难度值 题库A中第i个题目的难度为ai 题库B中第i个题目的难度为bi 题库C中第i个题目 ...

  3. 2020-10-01:谈谈golang的空结构体。

    福哥答案2020-10-01:#福大大架构师每日一题# 1.map.value是空结构体,构造集合. 2.通道.只传递信号,不传递数据. 3.切片.不管切片多长,都不会占用空间. 4.仅包含方法的结构 ...

  4. flutter系列之:使用AnimationController来控制动画效果

    目录 简介 构建一个要动画的widget 让图像动起来 总结 简介 之前我们提到了flutter提供了比较简单好用的AnimatedContainer和SlideTransition来进行一些简单的动 ...

  5. drf——Request源码分析、序列化组件、序列化类的使用(字段类和参数)、反序列化校验和保存

    1.Request类源码分析 # APIView+Response写个接口 # 总结: 1.新的request有个data属性,以后只要是在请求body体中的数据,无论什么编码格式,无论什么请求方式 ...

  6. odoo部署安全性问题

    本文档描述在生产中或在面向Internet的服务器上设置Odoo的基本步骤.它是在安装之后进行的,对于没有在internet上公开的开发系统来说,它通常不是必需的.警告如果您正在设置公共服务器,请务必 ...

  7. Microsoft R 和 Open Source R,哪一个才最适合你?

    由于微信不允许外部链接,你需要点击文章尾部左下角的 "阅读原文",才能访问文中链接. R 是一个开源统计软件,在分析领域普及的非常快. 在过去几年中,无论业务规模如何,很多公司都采 ...

  8. Access+Trunk 配置

    实验拓扑 实验需求 按图示给各 PC 配置 IP 地址 在SW1和SW2上创建聚合组 PC1 与 PC3 属于 VLAN 10 : PC2 与 PC4 属于 VLAN 20 利用 Access 与 T ...

  9. 自研API 网关 - 媲美美团这套Shepherd网关架构!

    作者:小傅哥 博客:https://bugstack.cn 沉淀.分享.成长,让自己和他人都能有所收获! 我说:"很多互联网大厂,很少基于 SpringMVC 模块对外提供 WEB 服务的 ...

  10. Kubernetes(k8s)包管理工具Helm:Helm包管理

    目录 一.系统环境 二.前言 三.包管理工具Helm简介 四.安装部署helm 五.配置helm以及helm常用命令 六.使用helm安装应用 七.搭建helm私有仓库 八.总结 一.系统环境 本文主 ...