热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

C#可变参数params示例详解

params是c#的一个关键字,用用汉语来说的话叫可变参数,这里的可变,不是说的类型可变,而是指的个数可变,这是c#的一个基础关键字,相信大家都有一定的了解,本篇文章就来大致的说一下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 
$(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 '
$' ( 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.String IL_000b: dup IL_000c: ldc.i4.0 IL_000d: ldstr "jordan" IL_0012: stelem.ref IL_0013: dup IL_0014: ldc.i4.1 IL_0015: ldstr "kobe" IL_001a: stelem.ref IL_001b: dup IL_001c: ldc.i4.2 IL_001d: ldstr "james" IL_0022: stelem.ref IL_0023: dup IL_0024: ldc.i4.3 IL_0025: ldstr "curry" IL_002a: stelem.ref // 这个地方调用了ParamtesDemo,第二个参数确实是一个数组类型 IL_002b: call void Program::'<
$>g__ParamtesDemo|0_0'(string, string[]) // Console.ReadKey(); IL_0030: nop IL_0031: call valuetype [System.Console]System.ConsoleKeyInfo [System.Console]System.Console::ReadKey() IL_0036: pop // } IL_0037: nop IL_0038: ret } // end of method Program::'
$'

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

//names也是一个数组
.method assembly hidebysig static 
	void '<
$>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.0 IL_0002: ldstr "的学生有:" IL_0007: ldstr "," IL_000c: ldarg.1 IL_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: nop IL_001d: ret } // end of method Program::'<
$>g__ParamtesDemo|0_0'

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

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

原来这得感谢编译器,如果默认不传递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)
        _arg1 = arg1;
        _args = s_twoArgArray;
    //params值为3个的时候
    public ParamsArray(object? arg0, object? arg1, object? arg2)
        _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类型的访问。
新年伊始,聊一点个人针对学习的看法。学习最理想的结果就是把接触到的知识进行一定的抽象,转换为概念或者一种思维方式,然后细化这种思维,让它成为细颗粒度的知识点,然后我们通过不断的接触不断的积累,后者不同领域的接触等,不断吸收壮大这个思维库。然后当看到一个新的问题的时候,或者需要思考的时候,能达到快速的多角度的整合这些思维碎片,得到一个更好的思路或解决问题的办法,这也许是一种更行之有效的状态。类比到我们架构设计上来说,以前的思维方式是一种类似单体应用的方式,灵活性差扩展性更差,后来微服务概念大行其道,更多独立的服务相互协调工作,形成一种更强大的聚合力。

到此这篇关于C#可变参数params示例详解的文章就介绍到这了,更多相关C#可变参数params内容请搜索以前的文章或继续浏览下面的相关文章希望大家以后多多支持!


推荐阅读
  • 本文整理了315道Python基础题目及答案,帮助读者检验学习成果。文章介绍了学习Python的途径、Python与其他编程语言的对比、解释型和编译型编程语言的简述、Python解释器的种类和特点、位和字节的关系、以及至少5个PEP8规范。对于想要检验自己学习成果的读者,这些题目将是一个不错的选择。请注意,答案在视频中,本文不提供答案。 ... [详细]
  • Iamtryingtocreateanarrayofstructinstanceslikethis:我试图创建一个这样的struct实例数组:letinstallers: ... [详细]
  • Python中sys模块的功能及用法详解
    本文详细介绍了Python中sys模块的功能及用法,包括对解释器参数和功能的访问、命令行参数列表、字节顺序指示符、编译模块名称等。同时还介绍了sys模块中的新功能和call_tracing函数的用法。推荐学习《Python教程》以深入了解。 ... [详细]
  • 深入理解Java虚拟机的并发编程与性能优化
    本文主要介绍了Java内存模型与线程的相关概念,探讨了并发编程在服务端应用中的重要性。同时,介绍了Java语言和虚拟机提供的工具,帮助开发人员处理并发方面的问题,提高程序的并发能力和性能优化。文章指出,充分利用计算机处理器的能力和协调线程之间的并发操作是提高服务端程序性能的关键。 ... [详细]
  • Mono为何能跨平台
    概念JIT编译(JITcompilation),运行时需要代码时,将Microsoft中间语言(MSIL)转换为机器码的编译。CLR(CommonLa ... [详细]
  • 2018年人工智能大数据的爆发,学Java还是Python?
    本文介绍了2018年人工智能大数据的爆发以及学习Java和Python的相关知识。在人工智能和大数据时代,Java和Python这两门编程语言都很优秀且火爆。选择学习哪门语言要根据个人兴趣爱好来决定。Python是一门拥有简洁语法的高级编程语言,容易上手。其特色之一是强制使用空白符作为语句缩进,使得新手可以快速上手。目前,Python在人工智能领域有着广泛的应用。如果对Java、Python或大数据感兴趣,欢迎加入qq群458345782。 ... [详细]
  • 基于PgpoolII的PostgreSQL集群安装与配置教程
    本文介绍了基于PgpoolII的PostgreSQL集群的安装与配置教程。Pgpool-II是一个位于PostgreSQL服务器和PostgreSQL数据库客户端之间的中间件,提供了连接池、复制、负载均衡、缓存、看门狗、限制链接等功能,可以用于搭建高可用的PostgreSQL集群。文章详细介绍了通过yum安装Pgpool-II的步骤,并提供了相关的官方参考地址。 ... [详细]
  • GetWindowLong函数
    今天在看一个代码里头写了GetWindowLong(hwnd,0),我当时就有点费解,靠,上网搜索函数原型说明,死活找不到第 ... [详细]
  • 本文介绍了如何将CIM_DateTime解析为.Net DateTime,并分享了解析过程中可能遇到的问题和解决方法。通过使用DateTime.ParseExact方法和适当的格式字符串,可以成功解析CIM_DateTime字符串。同时还提供了关于WMI和字符串格式的相关信息。 ... [详细]
  • 知识图谱——机器大脑中的知识库
    本文介绍了知识图谱在机器大脑中的应用,以及搜索引擎在知识图谱方面的发展。以谷歌知识图谱为例,说明了知识图谱的智能化特点。通过搜索引擎用户可以获取更加智能化的答案,如搜索关键词"Marie Curie",会得到居里夫人的详细信息以及与之相关的历史人物。知识图谱的出现引起了搜索引擎行业的变革,不仅美国的微软必应,中国的百度、搜狗等搜索引擎公司也纷纷推出了自己的知识图谱。 ... [详细]
  • 本文介绍了Oracle数据库中tnsnames.ora文件的作用和配置方法。tnsnames.ora文件在数据库启动过程中会被读取,用于解析LOCAL_LISTENER,并且与侦听无关。文章还提供了配置LOCAL_LISTENER和1522端口的示例,并展示了listener.ora文件的内容。 ... [详细]
  • 推荐系统遇上深度学习(十七)详解推荐系统中的常用评测指标
    原创:石晓文小小挖掘机2018-06-18笔者是一个痴迷于挖掘数据中的价值的学习人,希望在平日的工作学习中,挖掘数据的价值, ... [详细]
  • 3.223.28周学习总结中的贪心作业收获及困惑
    本文是对3.223.28周学习总结中的贪心作业进行总结,作者在解题过程中参考了他人的代码,但前提是要先理解题目并有解题思路。作者分享了自己在贪心作业中的收获,同时提到了一道让他困惑的题目,即input details部分引发的疑惑。 ... [详细]
  • Java在运行已编译完成的类时,是通过java虚拟机来装载和执行的,java虚拟机通过操作系统命令JAVA_HOMEbinjava–option来启 ... [详细]
  • 本文介绍了iOS数据库Sqlite的SQL语句分类和常见约束关键字。SQL语句分为DDL、DML和DQL三种类型,其中DDL语句用于定义、删除和修改数据表,关键字包括create、drop和alter。常见约束关键字包括if not exists、if exists、primary key、autoincrement、not null和default。此外,还介绍了常见的数据库数据类型,包括integer、text和real。 ... [详细]
author-avatar
小song喪_774
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有