淺談 C# 可變參數 params
前言
在(zài)群裏看到(dào)群友寫了(le/liǎo)一(yī / yì /yí)個(gè)基礎框架,其中涉及到(dào)關于(yú)同一(yī / yì /yí)個(gè)詞語可以(yǐ)添加多個(gè)近義詞的(de)一(yī / yì /yí)個(gè)場景。當時(shí)群友的(de)設計是(shì)類似字典的(de)設計,直接添加k-v的(de)操作,本人(rén)看到(dào)後思考了(le/liǎo)一(yī / yì /yí)下覺得使用c#中的(de)params可以(yǐ)更優雅的(de)實現一(yī / yì /yí)個(gè)key同時(shí)添加一(yī / yì /yí)個(gè)集合的(de)操作,看起來(lái)會更優雅一(yī / yì /yí)點,這(zhè)期間還有群友說(shuō)道(dào)params和(hé / huò)數組有啥區别的(de)問題。本篇文章就(jiù)來(lái)大(dà)緻的(de)說(shuō)一(yī / yì /yí)下。
示例
params是(shì)c#的(de)一(yī / yì /yí)個(gè)關鍵字,用用漢語來(lái)說(shuō)的(de)話叫可變參數,這(zhè)裏的(de)可變,不(bù)是(shì)說(shuō)的(de)類型可變,而(ér)是(shì)指的(de)個(gè)數可變,這(zhè)是(shì)c#的(de)一(yī / yì /yí)個(gè)基礎關鍵字,相信大(dà)家都有一(yī / yì /yí)定的(de)了(le/liǎo)解,今天咱們就(jiù)來(lái)進一(yī / yì /yí)步看一(yī / yì /yí)下c#的(de)可變參數params。首先來(lái)看一(yī / yì /yí)下簡單的(de)自定義使用,随便定義一(yī / yì /yí)個(gè)方法
定義可變參數類型的(de)時(shí)候需要(yào / yāo)有幾個(gè)注意點•params修飾在(zài)參數的(de)前面且參數類型得是(shì)一(yī / yì /yí)維數組類型 params修飾的(de)參數默認是(shì)可以(yǐ)不(bù)傳遞的(de) params參數不(bù)能用ref或out修飾且不(bù)能手動給默認值調用的(de)時(shí)候更簡單了(le/liǎo),如下所示 由上(shàng)面的(de)示例可知,使用可變參數最大(dà)的(de)優勢就(jiù)是(shì)你可以(yǐ)傳遞一(yī / yì /yí)個(gè)不(bù)确定個(gè)數的(de)集合類型并且不(bù)用聲明單獨的(de)類型去包裝,這(zhè)種場景特别适合傳遞參數不(bù)确定的(de)場景,比如我們經常使用到(dào)的(de) 探究本質 通過上(shàng)面我們了(le/liǎo)解到(dào)的(de)params的(de)遍曆性,當集合參數個(gè)數不(bù)确定的(de)時(shí)候是(shì)使用可變參數的(de)最佳場景,看着很神奇很便捷,本質到(dào)底是(shì)什麽呢?之(zhī)前樓主也(yě)沒有在(zài)意這(zhè)個(gè)問題,直到(dào)前幾天懷揣着好奇的(de)心情看了(le/liǎo)一(yī / yì /yí)下。廢話不(bù)多說(shuō),我們直接借助 通過 通過上(shàng)面的(de)IL代碼可以(yǐ)看到(dào)确實是(shì)一(yī / yì /yí)個(gè)語法糖,編譯完之(zhī)後一(yī / yì /yí)切塵歸塵土歸土還是(shì)一(yī / yì /yí)個(gè)數組類型,類型是(shì)和(hé / huò)params修飾的(de)那個(gè)數組類型是(shì)一(yī / yì /yí)緻的(de)。接下來(lái)我們再來(lái)看一(yī / yì /yí)下ParamtesDemo這(zhè)個(gè)方法的(de)IL代碼是(shì)啥樣的(de) 一(yī / yì /yí)切了(le/liǎo)然,本質就(jiù)是(shì)那個(gè)數組。我們上(shàng)面還提到(dào)了(le/liǎo)params修飾的(de)參數默認不(bù)傳遞的(de)話也(yě)不(bù)會報錯,這(zhè)究竟是(shì)爲(wéi / wèi)什麽呢,我們就(jiù)用IL代碼來(lái)看一(yī / yì /yí)下究竟進行了(le/liǎo)何等操作吧 原來(lái)這(zhè)得感謝編譯器,如果默認不(bù)傳遞params修飾的(de)參數的(de)話,默認它會幫我們生成一(yī / yì /yí)個(gè)這(zhè)個(gè)類型的(de) 擴展知識 我們上(shàng)面提到(dào)了(le/liǎo) params參數也(yě)可以(yǐ)爲(wéi / wèi)null值,默認不(bù)會報錯,但是(shì)需要(yào / yāo)進行判斷,否則程序處理null可能會報錯。在(zài)這(zhè)裏我們可以(yǐ)看到(dào)把params參數傳遞給ParamsArray進行包裝,我們可以(yǐ)看一(yī / yì /yí)下ParamsArray類本身的(de)定義,這(zhè)個(gè)類是(shì)一(yī / yì /yí)個(gè)struct類型的(de) ParamsArray是(shì)一(yī / yì /yí)個(gè)值類型,目的(de)就(jiù)是(shì)爲(wéi / wèi)了(le/liǎo)把params參數的(de)值給包裝起來(lái)提供讀相關的(de)操作。根據二八法則來(lái)看,params大(dà)部分場景的(de)參數個(gè)數或者高頻訪問可能是(shì)存在(zài)于(yú)數組的(de)前幾位元素上(shàng),所以(yǐ)使用ParamsArray針對熱點元素提供了(le/liǎo)快速訪問的(de)方式,略微有一(yī / yì /yí)點像Java中的(de)IntegerCache的(de)設計。這(zhè)個(gè)結構體是(shì)internal類型的(de),默認程序集之(zhī)外是(shì)沒辦法訪問的(de),我當時(shí)看到(dào)的(de)時(shí)候比較好奇,就(jiù)多看了(le/liǎo)一(yī / yì /yí)眼,感覺設計思路還是(shì)考慮的(de)比較周到(dào)的(de)。 總結 本文主要(yào / yāo)簡單的(de)聊一(yī / yì /yí)下c#可變參數params的(de)本質,了(le/liǎo)解到(dào)了(le/liǎo)其實就(jiù)是(shì)一(yī / yì /yí)個(gè)語法糖,編譯完成之(zhī)後本質還是(shì)一(yī / yì /yí)個(gè) 新年伊始,聊一(yī / yì /yí)點個(gè)人(rén)針對學習的(de)看法。學習最理想的(de)結果就(jiù)是(shì)把接觸到(dào)的(de)知識進行一(yī / yì /yí)定的(de)抽象,轉換爲(wéi / wèi)概念或者一(yī / yì /yí)種思維方式,然後細化這(zhè)種思維,讓它成爲(wéi / wèi)細顆粒度的(de)知識點,然後我們通過不(bù)斷的(de)接觸不(bù)斷的(de)積累,後者不(bù)同領域的(de)接觸等,不(bù)斷吸收壯大(dà)這(zhè)個(gè)思維庫。然後當看到(dào)一(yī / yì /yí)個(gè)新的(de)問題的(de)時(shí)候,或者需要(yào / yāo)思考的(de)時(shí)候,能達到(dào)快速的(de)多角度的(de)整合這(zhè)些思維碎片,得到(dào)一(yī / yì /yí)個(gè)更好的(de)思路或解決問題的(de)辦法,這(zhè)也(yě)許是(shì)一(yī / yì /yí)種更行之(zhī)有效的(de)狀态。類比到(dào)我們架構設計上(shàng)來(lái)說(shuō),以(yǐ)前的(de)思維方式是(shì)一(yī / yì /yí)種類似單體應用的(de)方式,靈活性差擴展性更差,後來(lái)微服務概念大(dà)行其道(dào),更多獨立的(de)服務相互協調工作,形成一(yī / yì /yí)種更強大(dà)的(de)聚合力。static void ParamtesDemo(string className, params string[] names)
{
Console.WriteLine($"{className}的(de)學生有:{string.Join(",", names)}");
}ParamtesDemo("小四班", "jordan", "kobe", "james", "curry");
// 如果不(bù)傳遞值也(yě)不(bù)會報錯
// ParamtesDemo("小四班");string.Format
就(jiù)是(shì)使用的(de)可變參數類型。ILSpy
工具看一(yī / yì /yí)下反編譯之(zhī)後的(de)源碼[CompilerGenerated]
internal class Program
{
private static void <Main>$(string[] args)
{
//聲明了(le/liǎo)一(yī / yì /yí)個(gè)數組
ParamtesDemo("小四班", new string[4] { "jordan", "kobe", "james", "curry" });
Console.ReadKey();
//已經沒有params關鍵字了(le/liǎo),就(jiù)是(shì)一(yī / yì /yí)個(gè)數組
static void ParamtesDemo(string className, string[] names)
{
Console.WriteLine(className + "的(de)學生有:" + string.Join(",", names));
}
}
}ILSpy
反編譯的(de)源碼我們可以(yǐ)看到(dào)params是(shì)一(yī / yì /yí)個(gè)語法糖,其實就(jiù)是(shì)增加了(le/liǎo)編程效率,本質在(zài)編譯的(de)時(shí)候會被具體的(de)聲明的(de)數組類型替代,不(bù)參與到(dào)運行時(shí)。這(zhè)個(gè)時(shí)候如果你懷疑反編譯的(de)代碼有問題,可以(yǐ)直接通過ILSpy
看生成的(de)IL代碼,由于(yú)IL代碼比較長,首先看一(yī / yì /yí)下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可知确實是(shì)聲明了(le/liǎo)一(yī / yì /yí)個(gè)數組類型
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
// 這(zhè)個(gè)地(dì / de)方調用了(le/liǎo)ParamtesDemo,第二個(gè)參數确實是(shì)一(yī / yì /yí)個(gè)數組類型
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>$' //names也(yě)是(shì)一(yī / yì /yí)個(gè)數組
.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 + "的(de)學生有:" + string.Join(",", names));
IL_0001: ldarg.0
IL_0002: ldstr "的(de)學生有:"
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'// 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 "小四班"
// 本質是(shì)編譯的(de)時(shí)候幫我們聲明了(le/liǎo)一(yī / yì /yí)個(gè)空數組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>$'空數組
,這(zhè)裏需要(yào / yāo)注意的(de)不(bù)是(shì)null
,所以(yǐ)代碼不(bù)會報錯,隻是(shì)沒有數據。string.Format
也(yě)是(shì)基于(yú)params實現的(de),畢竟Format具體的(de)參數依賴于(yú)前面聲明的(de)字符串的(de)占位符個(gè)數。在(zài)翻看相關代碼的(de)時(shí)候還發現了(le/liǎo)一(yī / yì /yí)個(gè)ParamsArray
這(zhè)個(gè)類,用來(lái)包裝params可變參數,簡單的(de)來(lái)說(shuō)就(jiù)是(shì)便于(yú)快速操作params,這(zhè)個(gè)我是(shì)在(zài)Format方法中發現的(de),源代碼如下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));
}internal readonly struct ParamsArray
{
//定義是(shì)三個(gè)數組分别去承載當傳遞進來(lái)的(de)params不(bù)同個(gè)數時(shí)的(de)數據
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è)值分别存儲params的(de)第0、1、2個(gè)參數的(de)值
private readonly object? _arg0;
private readonly object? _arg1;
private readonly object? _arg2;
//承載最原始的(de)params值
private readonly object?[] _args;
//params值爲(wéi / wèi)1個(gè)的(de)時(shí)候
public ParamsArray(object? arg0)
{
_arg0 = arg0;
_arg1 = null;
_arg2 = null;
_args = s_oneArgArray;
}
//params值爲(wéi / wèi)2個(gè)的(de)時(shí)候
public ParamsArray(object? arg0, object? arg1)
{
_arg0 = arg0;
_arg1 = arg1;
_arg2 = null;
_args = s_twoArgArray;
}
//params值爲(wéi / wèi)3個(gè)的(de)時(shí)候
public ParamsArray(object? arg0, object? arg1, object? arg2)
{
_arg0 = arg0;
_arg1 = arg1;
_arg2 = arg2;
_args = s_threeArgArray;
}
//直接包裝整個(gè)params的(de)值
public ParamsArray(object?[] args)
{
//直接取出(chū)來(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);
//判斷是(shì)否從承載的(de)緩存中取值
private object? GetAtSlow(int index)
{
if (index == 1)
return _arg1;
if (index == 2)
return _arg2;
return _args[index];
}
}數組
。它的(de)好處就(jiù)是(shì)當我們不(bù)确定集合個(gè)數的(de)時(shí)候,可以(yǐ)靈活的(de)使用params進行參數傳遞,不(bù)用自行定義一(yī / yì /yí)個(gè)集合類型。然後微軟針對params在(zài)内部實現了(le/liǎo)一(yī / yì /yí)個(gè)ParamsArray結構體進行對params包裝,提升params類型的(de)訪問。