網站首頁 編程語言 正文
前言
前幾天在群里看到群友寫了一個基礎框架,其中設計到關于同一個詞語可以添加多個近義詞的一個場景。當時群友的設計是類似字典的設計,直接添加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.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::'<<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>$'
通過上面的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.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::'<<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: 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>$'
原來這得感謝編譯器,如果默認不傳遞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類型的訪問。
新年伊始,聊一點個人針對學習的看法。學習最理想的結果就是把接觸到的知識進行一定的抽象,轉換為概念或者一種思維方式,然后細化這種思維,讓它成為細顆粒度的知識點,然后我們通過不斷的接觸不斷的積累,后者不同領域的接觸等,不斷吸收壯大這個思維庫。然后當看到一個新的問題的時候,或者需要思考的時候,能達到快速的多角度的整合這些思維碎片,得到一個更好的思路或解決問題的辦法,這也許是一種更行之有效的狀態。類比到我們架構設計上來說,以前的思維方式是一種類似單體應用的方式,靈活性差擴展性更差,后來微服務概念大行其道,更多獨立的服務相互協調工作,形成一種更強大的聚合力。
原文鏈接:https://www.cnblogs.com/wucy/p/15870366.html
相關推薦
- 2022-07-14 C++實現一個簡單的線程池的示例代碼_C 語言
- 2021-12-12 超快速上手jupyter notebook快捷鍵操作(兩種模式一個快捷鍵)
- 2022-04-23 Python繪制燈籠的示例代碼_python
- 2022-09-21 Django中外鍵使用總結_python
- 2022-11-19 Compose?動畫藝術之屬性動畫探索_Android
- 2022-09-12 IOS開發自定義view方法規范示例_IOS
- 2023-03-25 React?數據獲取條件競爭原理解析_React
- 2022-12-29 解決React?hook?'useState'?cannot?be?called?in?a?clas
- 最近更新
-
- window11 系統安裝 yarn
- 超詳細win安裝深度學習環境2025年最新版(
- Linux 中運行的top命令 怎么退出?
- MySQL 中decimal 的用法? 存儲小
- get 、set 、toString 方法的使
- @Resource和 @Autowired注解
- Java基礎操作-- 運算符,流程控制 Flo
- 1. Int 和Integer 的區別,Jav
- spring @retryable不生效的一種
- Spring Security之認證信息的處理
- Spring Security之認證過濾器
- Spring Security概述快速入門
- Spring Security之配置體系
- 【SpringBoot】SpringCache
- Spring Security之基于方法配置權
- redisson分布式鎖中waittime的設
- maven:解決release錯誤:Artif
- restTemplate使用總結
- Spring Security之安全異常處理
- MybatisPlus優雅實現加密?
- Spring ioc容器與Bean的生命周期。
- 【探索SpringCloud】服務發現-Nac
- Spring Security之基于HttpR
- Redis 底層數據結構-簡單動態字符串(SD
- arthas操作spring被代理目標對象命令
- Spring中的單例模式應用詳解
- 聊聊消息隊列,發送消息的4種方式
- bootspring第三方資源配置管理
- GIT同步修改后的遠程分支