浅谈C#可变参数params

前言

    前几天在群里看到群友写了一个基础框架,其中涉及到关于同一个词语可以添加多个近义词的一个场景。当时群友的设计是类似字典的设计,直接添加k-v的操作,本人看到后思考了一下觉得使用c#中的params可以更优雅的实现一个key同时添加一个集合的操作,看起来会更优雅一点,这期间还有群友说道params和数组有啥区别的问题。本篇文章就来大致的说一下。

示例

params是c#的一个关键字,用用汉语来说的话叫可变参数,这里的可变,不是说的类型可变,而是指的个数可变,这是c#的一个基础关键字,相信大家都有一定的了解,今天咱们就来进一步看一下c#的可变参数params。首先来看一下简单的自定义使用,随便定义一个方法

static void ParamtesDemo(string className, params string[] names)
{Console.WriteLine($"{className}的学生有:{string.Join(",", names)}");
}

定义可变参数类型的时候需要有几个注意点

•params修饰在参数的前面且参数类型得是一维数组类型•params修饰的参数默认是可以不传递的•params参数不能用ref或out修饰且不能手动给默认值

调用的时候更简单了,如下所示

ParamtesDemo("小四班", "jordan", "kobe", "james", "curry");
// 如果不传递值也不会报错
// ParamtesDemo("小四班");

由上面的示例可知,使用可变参数最大的优势就是你可以传递一个不确定个数的集合类型并且不用声明单独的类型去包装,这种场景特别适合传递参数不确定的场景,比如我们经常使用到的string.Format就是使用的可变参数类型。

探究本质

通过上面我们了解到的params的遍历性,当集合参数个数不确定的时候是使用可变参数的最佳场景,看着很神奇很便捷,本质到底是什么呢?之前楼主也没有在意这个问题,直到前几天怀揣着好奇的心情看了一下。废话不多说,我们直接借助ILSpy工具看一下反编译之后的源码

[CompilerGenerated]
internal class Program
{private static void <Main>$(string[] args){//声明了一个数组ParamtesDemo("小四班", new string[4] { "jordan", "kobe", "james", "curry" });Console.ReadKey();//已经没有params关键字了,就是一个数组static void ParamtesDemo(string className, string[] names){Console.WriteLine(className + "的学生有:" + string.Join(",", names));}}
}

通过ILSpy反编译的源码我们可以看到params是一个语法糖,其实就是增加了编程效率,本质在编译的时候会被具体的声明的数组类型替代,不参与到运行时。这个时候如果你怀疑反编译的代码有问题,可以直接通过ILSpy看生成的IL代码,由于IL代码比较长,首先看一下Main方法

// Methods
.method private hidebysig static void '<Main>$' (string[] args) cil managed 
{// Method begins at RVA 0x2092// Header size: 1// Code size: 57 (0x39).maxstack 8.entrypoint// ParamtesDemo("小四班", new string[4] { "jordan", "kobe", "james", "curry" });IL_0000: ldstr "小四班"IL_0005: ldc.i4.4//通过newarr可知确实是声明了一个数组类型IL_0006: newarr [System.Runtime]System.StringIL_000b: dupIL_000c: ldc.i4.0IL_000d: ldstr "jordan"IL_0012: stelem.refIL_0013: dupIL_0014: ldc.i4.1IL_0015: ldstr "kobe"IL_001a: stelem.refIL_001b: dupIL_001c: ldc.i4.2IL_001d: ldstr "james"IL_0022: stelem.refIL_0023: dupIL_0024: ldc.i4.3IL_0025: ldstr "curry"IL_002a: stelem.ref// 这个地方调用了ParamtesDemo,第二个参数确实是一个数组类型IL_002b: call void Program::'<<Main>$>g__ParamtesDemo|0_0'(string, string[])// Console.ReadKey();IL_0030: nopIL_0031: call valuetype [System.Console]System.ConsoleKeyInfo [System.Console]System.Console::ReadKey()IL_0036: pop// }IL_0037: nopIL_0038: ret
} // end of method Program::'<Main>$'

通过上面的IL代码可以看到确实是一个语法糖,编译完之后一切尘归尘土归土还是一个数组类型,类型是和params修饰的那个数组类型是一致的。接下来我们再来看一下ParamtesDemo这个方法的IL代码是啥样的

//names也是一个数组
.method assembly hidebysig static void '<<Main>$>g__ParamtesDemo|0_0' (string className,string[] names) cil managed 
{.custom instance void System.Runtime.CompilerServices.NullableContextAttribute::.ctor(uint8) = (01 00 01 00 00).custom instance void [System.Runtime]System.Runtime.CompilerServices.CompilerGeneratedAttribute::.ctor() = (01 00 00 00)// Method begins at RVA 0x20d5// Header size: 1// Code size: 30 (0x1e).maxstack 8// {IL_0000: nop// Console.WriteLine(className + "的学生有:" + string.Join(",", names));IL_0001: ldarg.0IL_0002: ldstr "的学生有:"IL_0007: ldstr ","IL_000c: ldarg.1IL_000d: call string [System.Runtime]System.String::Join(string, string[])IL_0012: call string [System.Runtime]System.String::Concat(string, string, string)IL_0017: call void [System.Console]System.Console::WriteLine(string)// }IL_001c: nopIL_001d: ret
} // end of method Program::'<<Main>$>g__ParamtesDemo|0_0'

一切了然,本质就是那个数组。我们上面还提到了params修饰的参数默认不传递的话也不会报错,这究竟是为什么呢,我们就用IL代码来看一下究竟进行了何等操作吧

// Methods
.method private hidebysig static void '<Main>$' (string[] args) cil managed 
{// Method begins at RVA 0x2092// Header size: 1// Code size: 24 (0x18).maxstack 8.entrypoint// ParamtesDemo("小四班", Array.Empty<string>());IL_0000: ldstr "小四班"// 本质是编译的时候帮我们声明了一个空数组Array::Empty<string>IL_0005: call !!0[] [System.Runtime]System.Array::Empty<string>()IL_000a: call void Program::'<<Main>$>g__ParamtesDemo|0_0'(string, string[])// Console.ReadKey();IL_000f: nopIL_0010: call valuetype [System.Console]System.ConsoleKeyInfo [System.Console]System.Console::ReadKey()IL_0015: pop// }IL_0016: nopIL_0017: ret
} // end of method Program::'<Main>$'

原来这得感谢编译器,如果默认不传递params修饰的参数的话,默认它会帮我们生成一个这个类型的空数组,这里需要注意的不是null,所以代码不会报错,只是没有数据。

扩展知识

我们上面提到了string.Format也是基于params实现的,毕竟Format具体的参数依赖于前面声明的字符串的占位符个数。在翻看相关代码的时候还发现了一个ParamsArray这个类,用来包装params可变参数,简单的来说就是便于快速操作params,这个我是在Format方法中发现的,源代码如下

public static string Format(string format, params object?[] args)
{if (args == null){throw new ArgumentNullException((format == null) ? nameof(format) : nameof(args));}return FormatHelper(null, format, new ParamsArray(args));
}

params参数也可以为null值,默认不会报错,但是需要进行判断,否则程序处理null可能会报错。在这里我们可以看到把params参数传递给ParamsArray进行包装,我们可以看一下ParamsArray类本身的定义,这个类是一个struct类型的

internal readonly struct ParamsArray
{//定义是三个数组分别去承载当传递进来的params不同个数时的数据private static readonly object?[] s_oneArgArray = new object?[1];private static readonly object?[] s_twoArgArray = new object?[2];private static readonly object?[] s_threeArgArray = new object?[3];//定义三个值分别存储params的第0、1、2个参数的值private readonly object? _arg0;private readonly object? _arg1;private readonly object? _arg2;//承载最原始的params值private readonly object?[] _args;//params值为1个的时候public ParamsArray(object? arg0){_arg0 = arg0;_arg1 = null;_arg2 = null;_args = s_oneArgArray;}//params值为2个的时候public ParamsArray(object? arg0, object? arg1){_arg0 = arg0;_arg1 = arg1;_arg2 = null;_args = s_twoArgArray;}//params值为3个的时候public ParamsArray(object? arg0, object? arg1, object? arg2){_arg0 = arg0;_arg1 = arg1;_arg2 = arg2;_args = s_threeArgArray;}//直接包装整个params的值public ParamsArray(object?[] args){//直接取出来值缓存int len = args.Length;_arg0 = len > 0 ? args[0] : null;_arg1 = len > 1 ? args[1] : null;_arg2 = len > 2 ? args[2] : null;_args = args;}public int Length => _args.Length;public object? this[int index] => index == 0 ? _arg0 : GetAtSlow(index);//判断是否从承载的缓存中取值private object? GetAtSlow(int index){if (index == 1)return _arg1;if (index == 2)return _arg2;return _args[index];}
}

ParamsArray是一个值类型,目的就是为了把params参数的值给包装起来提供读相关的操作。根据二八法则来看,params大部分场景的参数个数或者高频访问可能是存在于数组的前几位元素上,所以使用ParamsArray针对热点元素提供了快速访问的方式,略微有一点像Java中的IntegerCache的设计。这个结构体是internal类型的,默认程序集之外是没办法访问的,我当时看到的时候比较好奇,就多看了一眼,感觉设计思路还是考虑的比较周到的。

总结

    本文主要简单的聊一下c#可变参数params的本质,了解到了其实就是一个语法糖,编译完成之后本质还是一个数组。它的好处就是当我们不确定集合个数的时候,可以灵活的使用params进行参数传递,不用自行定义一个集合类型。然后微软针对params在内部实现了一个ParamsArray结构体进行对params包装,提升params类型的访问。    新年伊始,聊一点个人针对学习的看法。学习最理想的结果就是把接触到的知识进行一定的抽象,转换为概念或者一种思维方式,然后细化这种思维,让它成为细颗粒度的知识点,然后我们通过不断的接触不断的积累,后者不同领域的接触等,不断吸收壮大这个思维库。然后当看到一个新的问题的时候,或者需要思考的时候,能达到快速的多角度的整合这些思维碎片,得到一个更好的思路或解决问题的办法,这也许是一种更行之有效的状态。类比到我们架构设计上来说,以前的思维方式是一种类似单体应用的方式,灵活性差扩展性更差,后来微服务概念大行其道,更多独立的服务相互协调工作,形成一种更强大的聚合力。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/291689.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Html、Css-----当有文字和图片的时候,需要文字和图片居中,怎么实现?不想文字换行怎么设置...

1 当有文字和图片的时候&#xff0c;需要文字和图片居中&#xff0c;怎么实现&#xff1f; <a href#" target"aa" style"white-space:nowrap;"><img src"img.jpg" align"absmiddle"/>文字</a> 在img标签中加入…

linux网络编程之怎么配置好unp.h文件

1、获取unp源码 下载地址:http://www.unpbook.com/src.html 然后用tar -zxvf unpv13e.tar.gz命令解压 2、进入unpv13e目录执行configure cd unpv13e ,然后执configure文件 3、打开README文件,使用make命令 打开README文件

Win10下安装wireshark不能正常使用,cmd管理员身份调用net start npf命令显示无法启动该服务

我安装wireshark完成后&#xff0c;刚开始运行wireshark并开始捕获时也不能正常捕获&#xff0c;然后发现是winpcap的原因。 我把我安装的wireshark版本和winpcap的版本资源和我个人出现问题的解决办法及经验已打包上传资源&#xff0c;伙伴们有需要的可以去参考借鉴一下~ PS…

CMD、AMD、commonJs 规范的写法

比较好的文章&#xff1a; http://www.jianshu.com/p/d67b...AMD 是 RequireJS 在推广过程中对模块定义的规范化产出。CMD 是 SeaJS 在推广过程中对模块定义的规范化产出。 //AMD 规范 /*** define(id?, dependencies?, factory); id 和 dependencies 是可选的。** define([d…

mft文件记录属性头包括_关于NTFS-MFT

一、Ntfs文件系统在磁盘上的分布一个ntfs文件系统由引导扇区、MFT(包含MFT元数据)和数据区组成。NTFS中存储了两份MFT备份以防MFT文件损坏&#xff0c;两个MFT备份的具体起始位置都存储在引导扇区中。image.png二、引导扇区($Boot)引导扇区是从NTFS文件系统的第一个扇区开始&am…

ffmpeg avformat_open_input返回失败的解决办法

用ffmpeg做的第一个程序&#xff0c;参考网上的代码&#xff0c;就出现了一些问题&#xff0c;其中avformat_open_input返回失败。 下面是我在网上收集到的失败信息的相关解决&#xff1a; 很多朋友在使用新版本的ffmpeg时&#xff0c;都遇到了avformat_open_input返回失败的问…

客户端禁用Keep-Alive, 服务端开启Keep-Alive,会怎么样?

最近部署的web程序&#xff0c;服务器上出现不少time_wait的tcp连接状态&#xff0c;占用了tcp端口&#xff0c;花费几天时间排查。之前我有结论&#xff1a;HTTP keep-alive 是在应用层对TCP连接的滑动续约复用&#xff0c;如果客户端、服务器稳定续约&#xff0c;就成了名副其…

linux网络编程之一般应用采用的协议和不同套接字的地址结构以及用户进程和内核通过哪些函数传递套接字的地址结构

1、一般应用采用的协议 2、不同套接字的地址结构 3、用户进程和内核通过哪些函数传递套接字的地址结构 从进程到内核传递套接字的地址结构函数有3个 bind、connect、sendto函数 从内核到进程传递套接字的地址结构函数有4个函数 accept、recvfrom 、getsockname 、getpeername…

四则运算2测试

这是测试程序在输入任意字符时能否正常运行 1&#xff09;按程序提示正确输入 结果无错 2)当输入错误的字符&#xff0c;如字母等&#xff0c;程序出错&#xff08;错误提示无限循环&#xff09; 这一错误我经过长时间反正为解决&#xff0c;于是请教了其他同学&#xff0c;发现…

2020-11-04关于出现tomcat启动失败的一种原因

点击run on server后出现了如下所示&#xff1a; 本来是运行正常的&#xff0c;后来因为我将exp5里面所有的文件都复制了一遍&#xff0c;放到了exp5_2里面后&#xff0c;如下所示&#xff1a; 此时&#xff08;复制文件夹之前&#xff09;若tomcat已经启动&#xff0c;则不会出…

HTTP协议快速入门

一、定义 The Hypertext Transfer Protocol (HTTP) is an application protocol for distributed, collaborative, hypermedia information systems. HTTP is the foundation of data communication for the World Wide Web. Hypertext is structured text that uses logical l…

删除含有关键词的文件_AweEraser——macOS Catalina最佳的文件粉碎机

您是否正在寻找适用于macOS Catalina的好的文件粉碎机&#xff1f;今天macdown为大家推荐一种永久删除数据的软件——AweEraser。有时&#xff0c;你要销毁或擦除计算机上的所有私人文件&#xff0c;这意味着这些数据必须受到保护&#xff0c;免受他人的侵害。本地硬盘或外部硬…

Cocos2d-JS v3.0 alpha

Cocos2d-JS是整合了Cocos2d-html5 v3.0 alpha和Cocos2d-x JSBinding的新JS引擎仓库。整合之后的核心优势在于Html5和JSB的开发流程及API现在变得高度统一&#xff0c;在使用同一套JS游戏代码的基础上&#xff0c;我们的工具也极大的简化了对于不同目标平台的编译发布流程 核心特…

linux网络编程之用一张图片说明函数inet_ntop、inet_pton、inet_addr、inet_ntoa 、inet_aton函数之间的关系

1、inet_ntop、inet_pton、inet_addr、inet_ntoa 、inet_aton函数之间的关系 2、inet_ntop、inet_pton函数的源代码 1、inet_pton函数源码 int inet_pton(int family, const char *strptr, void *addrptr) {if (family == AF_INET) {struct in_addr in_val;if (inet_aton(s…

聊一聊如何用C#轻松完成一个TCC分布式事务

背景 银行跨行转账业务是一个典型分布式事务场景&#xff0c;假设 A 需要跨行转账给 B&#xff0c;那么就涉及两个银行的数据&#xff0c;无法通过一个数据库的本地事务保证转账的 ACID &#xff0c;只能够通过分布式事务来解决。在 聊一聊如何用C#轻松完成一个SAGA分布式事务…

Xcode6.1 模拟器路径

Xcode 5的iOS模拟器的应用的目录是在~/Library/Application Support/iPhone Simulator/<iOS_Version>/Applications/{Application_ID} Xcode 6的目录改为~/Library/Developer/CoreSimulator/Devices/{Device_ID}/data/Containers/Bundle/Application/{Application_ID}/这…

安卓效率微商_微商人脉通app下载-微商人脉通 安卓版v2.5.5-PC6安卓网

微商人脉通app是一款很好用的微商营销软件&#xff0c;微商人脉通app可以帮你高效获取客源&#xff0c;微商人脉通app还具有丰富微商各类工具&#xff0c;微商人脉通app是做微商生意第一选择的工具&#xff01;软件介绍微商人脉通app致力于打造微商营销推广行业优秀产品&#x…

linux之如何快速查看文件的大小

1、linux之如何快速查看文件的大小 命令如下&#xff0c;每次都容易忘记&#xff0c;希望下次记住 du -h

新年伊始 .Net7 preview1 发布!

虎年伊始&#xff0c;.NET 7.0就要来了&#xff0c;还学的动吗&#xff1f;从github能看到&#xff0c;截止到2月8号&#xff0c;.NET 7.0 Preview1已经全部开发完成&#xff0c;连Preview2也完成了85%&#xff0c;这进度杠杠的&#xff01;微软这几年大力推进.NET稳定更新&…