C#可變參數(shù)params示例詳解
前言
前幾天在群里看到群友寫了一個(gè)基礎(chǔ)框架,其中設(shè)計(jì)到關(guān)于同一個(gè)詞語(yǔ)可以添加多個(gè)近義詞的一個(gè)場(chǎng)景。當(dāng)時(shí)群友的設(shè)計(jì)是類似字典的設(shè)計(jì),直接添加k-v的操作,本人看到后思考了一下覺(jué)得使用c#中的params可以更優(yōu)雅的實(shí)現(xiàn)一個(gè)key同時(shí)添加一個(gè)集合的操作,看起來(lái)會(huì)更優(yōu)雅一點(diǎn),這期間還有群友說(shuō)道params和數(shù)組有啥區(qū)別的問(wèn)題。本篇文章就來(lái)大致的說(shuō)一下。
示例
params是c#的一個(gè)關(guān)鍵字,用用漢語(yǔ)來(lái)說(shuō)的話叫可變參數(shù),這里的可變,不是說(shuō)的類型可變,而是指的個(gè)數(shù)可變,這是c#的一個(gè)基礎(chǔ)關(guān)鍵字,相信大家都有一定的了解,今天咱們就來(lái)進(jìn)一步看一下c#的可變參數(shù)params。首先來(lái)看一下簡(jiǎn)單的自定義使用,隨便定義一個(gè)方法
static void ParamtesDemo(string className, params string[] names) { Console.WriteLine($"{className}的學(xué)生有:{string.Join(",", names)}"); }
定義可變參數(shù)類型的時(shí)候需要有幾個(gè)注意點(diǎn)
- params修飾在參數(shù)的前面且參數(shù)類型得是一維數(shù)組類型
- params修飾的參數(shù)默認(rèn)是可以不傳遞的
- params參數(shù)不能用ref或out修飾且不能手動(dòng)給默認(rèn)值
調(diào)用的時(shí)候更簡(jiǎn)單了,如下所示
ParamtesDemo("小四班", "jordan", "kobe", "james", "curry"); // 如果不傳遞值也不會(huì)報(bào)錯(cuò) // ParamtesDemo("小四班");
由上面的示例可知,使用可變參數(shù)最大的優(yōu)勢(shì)就是你可以傳遞一個(gè)不確定個(gè)數(shù)的集合類型并且不用聲明單獨(dú)的類型去包裝,這種場(chǎng)景特別適合傳遞參數(shù)不確定的場(chǎng)景,比如我們經(jīng)常使用到的string.Format
就是使用的可變參數(shù)類型。
探究本質(zhì)
通過(guò)上面我們了解到的params的遍歷性,當(dāng)集合參數(shù)個(gè)數(shù)不確定的時(shí)候是使用可變參數(shù)的最佳場(chǎng)景,看著很神奇很便捷,本質(zhì)到底是什么呢?之前樓主也沒(méi)有在意這個(gè)問(wèn)題,直到前幾天懷揣著好奇的心情看了一下。廢話不多說(shuō),我們直接借助ILSpy
工具看一下反編譯之后的源碼
[CompilerGenerated] internal class Program { private static void <Main>$(string[] args) { //聲明了一個(gè)數(shù)組 ParamtesDemo("小四班", new string[4] { "jordan", "kobe", "james", "curry" }); Console.ReadKey(); //已經(jīng)沒(méi)有params關(guān)鍵字了,就是一個(gè)數(shù)組 static void ParamtesDemo(string className, string[] names) { Console.WriteLine(className + "的學(xué)生有:" + string.Join(",", names)); } } }
通過(guò)ILSpy
反編譯的源碼我們可以看到params是一個(gè)語(yǔ)法糖,其實(shí)就是增加了編程效率,本質(zhì)在編譯的時(shí)候會(huì)被具體的聲明的數(shù)組類型替代,不參與到運(yùn)行時(shí)。這個(gè)時(shí)候如果你懷疑反編譯的代碼有問(wèn)題,可以直接通過(guò)ILSpy
看生成的IL代碼,由于IL代碼比較長(zhǎng),首先看一下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 //通過(guò)newarr可知確實(shí)是聲明了一個(gè)數(shù)組類型 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 // 這個(gè)地方調(diào)用了ParamtesDemo,第二個(gè)參數(shù)確實(shí)是一個(gè)數(shù)組類型 IL_002b: call void Program::'<<Main>$>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::'<Main>$'
通過(guò)上面的IL代碼可以看到確實(shí)是一個(gè)語(yǔ)法糖,編譯完之后一切塵歸塵土歸土還是一個(gè)數(shù)組類型,類型是和params修飾的那個(gè)數(shù)組類型是一致的。接下來(lái)我們?cè)賮?lái)看一下ParamtesDemo這個(gè)方法的IL代碼是啥樣的
//names也是一個(gè)數(shù)組 .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 + "的學(xué)生有:" + string.Join(",", names)); IL_0001: ldarg.0 IL_0002: ldstr "的學(xué)生有:" 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::'<<Main>$>g__ParamtesDemo|0_0'
一切了然,本質(zhì)就是那個(gè)數(shù)組。我們上面還提到了params修飾的參數(shù)默認(rèn)不傳遞的話也不會(huì)報(bào)錯(cuò),這究竟是為什么呢,我們就用IL代碼來(lái)看一下究竟進(jìn)行了何等操作吧
// 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 "小四班" // 本質(zhì)是編譯的時(shí)候幫我們聲明了一個(gè)空數(shù)組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: 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::'<Main>$'
原來(lái)這得感謝編譯器,如果默認(rèn)不傳遞params修飾的參數(shù)的話,默認(rèn)它會(huì)幫我們生成一個(gè)這個(gè)類型的空數(shù)組
,這里需要注意的不是null
,所以代碼不會(huì)報(bào)錯(cuò),只是沒(méi)有數(shù)據(jù)。
擴(kuò)展知識(shí)
我們上面提到了string.Format
也是基于params實(shí)現(xiàn)的,畢竟Format具體的參數(shù)依賴于前面聲明的字符串的占位符個(gè)數(shù)。在翻看相關(guān)代碼的時(shí)候還發(fā)現(xiàn)了一個(gè)ParamsArray
這個(gè)類,用來(lái)包裝params可變參數(shù),簡(jiǎn)單的來(lái)說(shuō)就是便于快速操作params,這個(gè)我是在Format方法中發(fā)現(xiàn)的,源代碼如下
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參數(shù)也可以為null值,默認(rèn)不會(huì)報(bào)錯(cuò),但是需要進(jìn)行判斷,否則程序處理null可能會(huì)報(bào)錯(cuò)。在這里我們可以看到把params參數(shù)傳遞給ParamsArray進(jìn)行包裝,我們可以看一下ParamsArray類本身的定義,這個(gè)類是一個(gè)struct類型的
internal readonly struct ParamsArray { //定義是三個(gè)數(shù)組分別去承載當(dāng)傳遞進(jìn)來(lái)的params不同個(gè)數(shù)時(shí)的數(shù)據(jù) 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]; //定義三個(gè)值分別存儲(chǔ)params的第0、1、2個(gè)參數(shù)的值 private readonly object? _arg0; private readonly object? _arg1; private readonly object? _arg2; //承載最原始的params值 private readonly object?[] _args; //params值為1個(gè)的時(shí)候 public ParamsArray(object? arg0) { _arg0 = arg0; _arg1 = null; _arg2 = null; _args = s_oneArgArray; } //params值為2個(gè)的時(shí)候 public ParamsArray(object? arg0, object? arg1) _arg1 = arg1; _args = s_twoArgArray; //params值為3個(gè)的時(shí)候 public ParamsArray(object? arg0, object? arg1, object? arg2) _arg2 = arg2; _args = s_threeArgArray; //直接包裝整個(gè)params的值 public ParamsArray(object?[] args) //直接取出來(lái)值緩存 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是一個(gè)值類型,目的就是為了把params參數(shù)的值給包裝起來(lái)提供讀相關(guān)的操作。根據(jù)二八法則來(lái)看,params大部分場(chǎng)景的參數(shù)個(gè)數(shù)或者高頻訪問(wèn)可能是存在于數(shù)組的前幾位元素上,所以使用ParamsArray針對(duì)熱點(diǎn)元素提供了快速訪問(wèn)的方式,略微有一點(diǎn)像Java中的IntegerCache的設(shè)計(jì)。這個(gè)結(jié)構(gòu)體是internal類型的,默認(rèn)程序集之外是沒(méi)辦法訪問(wèn)的,我當(dāng)時(shí)看到的時(shí)候比較好奇,就多看了一眼,感覺(jué)設(shè)計(jì)思路還是考慮的比較周到的。
總結(jié)
本文主要簡(jiǎn)單的聊一下c#可變參數(shù)params的本質(zhì),了解到了其實(shí)就是一個(gè)語(yǔ)法糖,編譯完成之后本質(zhì)還是一個(gè)數(shù)組
。它的好處就是當(dāng)我們不確定集合個(gè)數(shù)的時(shí)候,可以靈活的使用params進(jìn)行參數(shù)傳遞,不用自行定義一個(gè)集合類型。然后微軟針對(duì)params在內(nèi)部實(shí)現(xiàn)了一個(gè)ParamsArray結(jié)構(gòu)體進(jìn)行對(duì)params包裝,提升params類型的訪問(wèn)。
新年伊始,聊一點(diǎn)個(gè)人針對(duì)學(xué)習(xí)的看法。學(xué)習(xí)最理想的結(jié)果就是把接觸到的知識(shí)進(jìn)行一定的抽象,轉(zhuǎn)換為概念或者一種思維方式,然后細(xì)化這種思維,讓它成為細(xì)顆粒度的知識(shí)點(diǎn),然后我們通過(guò)不斷的接觸不斷的積累,后者不同領(lǐng)域的接觸等,不斷吸收壯大這個(gè)思維庫(kù)。然后當(dāng)看到一個(gè)新的問(wèn)題的時(shí)候,或者需要思考的時(shí)候,能達(dá)到快速的多角度的整合這些思維碎片,得到一個(gè)更好的思路或解決問(wèn)題的辦法,這也許是一種更行之有效的狀態(tài)。類比到我們架構(gòu)設(shè)計(jì)上來(lái)說(shuō),以前的思維方式是一種類似單體應(yīng)用的方式,靈活性差擴(kuò)展性更差,后來(lái)微服務(wù)概念大行其道,更多獨(dú)立的服務(wù)相互協(xié)調(diào)工作,形成一種更強(qiáng)大的聚合力。
到此這篇關(guān)于C#可變參數(shù)params示例詳解的文章就介紹到這了,更多相關(guān)C#可變參數(shù)params內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
C#實(shí)現(xiàn)客戶端彈出消息框封裝類實(shí)例
這篇文章主要介紹了C#實(shí)現(xiàn)客戶端彈出消息框封裝類,實(shí)例分析了C#彈出窗口的實(shí)現(xiàn)技巧,非常具有實(shí)用價(jià)值,需要的朋友可以參考下2015-03-03C#編程實(shí)現(xiàn)簡(jiǎn)易圖片瀏覽器的方法
這篇文章主要介紹了C#編程實(shí)現(xiàn)簡(jiǎn)易圖片瀏覽器的方法,涉及C#基于WinForm操作圖片實(shí)現(xiàn)預(yù)覽功能的相關(guān)技巧,具有一定參考借鑒價(jià)值,需要的朋友可以參考下2015-11-11c#通過(guò)進(jìn)程調(diào)用cmd判斷登錄用戶權(quán)限代碼分享
最近自己開(kāi)發(fā)軟件需要讀取本地配置文件,因?yàn)榈卿浻脩舻臋?quán)限不夠會(huì)導(dǎo)致無(wú)法讀取文件進(jìn)而導(dǎo)致程序崩潰,查了一些解決方法,代碼分享如下2013-12-12用C#在本地創(chuàng)建一個(gè)Windows帳戶(DOS命令)
用C#在本地創(chuàng)建一個(gè)Windows帳戶(DOS命令)...2007-03-03