日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當(dāng)前位置: 首頁 > 编程语言 > C# >内容正文

C#

C# 中的可变参数方法(VarArgs)

發(fā)布時間:2023/12/10 C# 46 豆豆
生活随笔 收集整理的這篇文章主要介紹了 C# 中的可变参数方法(VarArgs) 小編覺得挺不錯的,現(xiàn)在分享給大家,幫大家做個參考.

首先需要明確一點(diǎn):這里提到的可變參數(shù)方法,指的是具有?CallingConventions.VarArgs?調(diào)用約定的方法,而不是包含?params?參數(shù)的方法。可以通過MethodBase.CallingConvention 屬性來獲取某個方法的調(diào)用約定。

舉個常見的例子來說,C 語言的?printf?方法大多數(shù)人應(yīng)該都知道,它的作用是向標(biāo)準(zhǔn)輸出流(stdout)寫入格式化字符串,printf?的方法簽名是:

int?printf(const?char?* format, ...);

方法簽名中的?...,就表示這個方法是可變參數(shù)的,可以根據(jù)需要傳遞任意個數(shù)的參數(shù),參數(shù)的類型也可以互不相同。

C# 中的?params?參數(shù)則具有更強(qiáng)的約束,雖然參數(shù)個數(shù)可以不固定,但參數(shù)的類型必須都是相同的。而實(shí)際上,C# 中也可以聲明如 C 語言的那種可變參數(shù),只不過大多用于調(diào)用非托管 dll 提供的方法,而不是用于托管方法。本文會從 P/Invoke、C# 中可變參數(shù)方法的聲明、IL 代碼和 RuntimeArgumentHandle 四個方面介紹可變參數(shù)方法。

一、可變參數(shù)方法的 P/Invoke

如果一個非托管 dll 提供了一個可變參數(shù)方法,該如何在 C# 中調(diào)用它?

最簡單的辦法顯然是按需調(diào)用——盡管提供的方法是可變參數(shù)的,但我可能并不需要那么多的自由,只需要一種或幾種固定的參數(shù)就好。這種情況下,方法的簽名直接按照需要去寫就好,還是以?printf?為例:

1

2

3

4

5

6

7

[DllImport("msvcrt.dll", CallingConvention = CallingConvention.Cdecl)]

public?static?extern?int?printf(string?format,?string?text);

[DllImport("msvcrt.dll", CallingConvention = CallingConvention.Cdecl)]

public?static?extern?int?printf(string?format,?int?num,?int?x,?int?y);

// 調(diào)用方法

printf("Hello %s!\n",?"World");?// Hello World!

printf("Hello %d! is %d x %d\n", 42, 6, 7);?// Hello 42! is 6 x 7

需要注意的是,DllImport?需要顯式指定?CallingConvention = CallingConvention.Cdecl,這樣會由調(diào)用方清理堆棧,才能支持可變參數(shù)的方法。

如果的確需要完整的可變參數(shù)方法呢?可以使用一些特殊的關(guān)鍵字來做到這一點(diǎn),這些關(guān)鍵字并未給出官方文檔,但確實(shí)存在于 C# 編譯器中。如下定義printf?方法,注意參數(shù)使用的是?__arglist?關(guān)鍵字,并未指定任何參數(shù)類型和參數(shù)名稱:

1

2

[DllImport("msvcrt.dll", CallingConvention = CallingConvention.Cdecl)]

public?static?extern?int?printf(string?format, __arglist);

調(diào)用方法時,也必須將可變參數(shù)用?__arglist()?括起來:

1

2

printf("Hello %s!\n", __arglist("World"));?// Hello World!

printf("Hello %s! is %d x %c\n", __arglist("World", 6,?'7'));?// Hello World! is 6 x 7

這里要區(qū)分?__arglist?和?__arglist(),__arglist?是用于可變參數(shù)方法的聲明和方法體內(nèi)引用可變參數(shù)的,而?__arglist()?是用與可變參數(shù)方法的調(diào)用的。注意第二個示例,三個參數(shù)的類型各不相同,分別是字符串、整數(shù)和字符。

可變參數(shù)方法在調(diào)用時,也要特別注意即使不傳遞任何可選參數(shù),也必須寫?__arglist(),而不能省略掉。例如,上面的?printf?方法,即使沒有參數(shù),也要這樣寫才可以:printf("Hello World!", __arglist());。

二、C# 中的可變參數(shù)方法

上面說到了非托管 dll 能夠提供可變參數(shù)方法,C# 也能調(diào)用這樣的方法,那么 C# 自身是否能聲明這樣的方法?

答案其實(shí)很明顯,既然?__arglist?關(guān)鍵字能在 P/Invoke 方法中使用,顯然也能在普通的方法中使用。只不過這時需要使用?ArgIterator 結(jié)構(gòu)和TypedReference 結(jié)構(gòu)來訪問參數(shù),而不是普通的參數(shù)訪問方法。

先來看一段簡單的示例:

1

2

3

4

5

6

7

8

9

10

11

12

13

public?static?void?printf(string?format, __arglist) {

????Console.Write(format);

????ArgIterator args =?new?ArgIterator(__arglist);

????while?(args.GetRemainingCount() > 0) {

????????Console.WriteLine("{0}: {1}", Type.GetTypeFromHandle(args.GetNextArgType()),

????????????TypedReference.ToObject(args.GetNextArg()));

????}

}

printf("Hello %s! is %d x %c\n", __arglist("World", 6,?'7'));

// Hello %s! is %d x %c

// System.String: World

// System.Int32: 6

// System.Char: 7

這段示例中,已經(jīng)把可變參數(shù)的用法基本都展示出來了,下面再來簡單介紹一下。

首先是構(gòu)造?ArgIterator?實(shí)例,就是通過調(diào)用構(gòu)造函數(shù)?new ArgIterator(__arglist)。

然后是遍歷可變參數(shù),ArgIterator.GetRemainingCount 方法能夠返回可變參數(shù)列表中剩余的參數(shù)個數(shù),并且每次調(diào)用?ArgIterator.GetNextArg 方法獲取下一個參數(shù)時都會自動減一。

下一個參數(shù)的目標(biāo)類型可以利用?ArgIterator.GetNextArgType 方法獲取,這個方法不會使迭代前進(jìn)到下一個參數(shù)(比較類似于?Peek?方法)。需要注意得到的結(jié)果是?RuntimeTypeHandle 結(jié)構(gòu),需要使用?Type.GetTypeFromHandle 方法才能拿到能用的類型;而且并非是參數(shù)的實(shí)際類型,僅僅是調(diào)用方法時__arglist()?中指定的目標(biāo)參數(shù)類型。例如:

1

2

3

4

5

printf("Hello %s! is %d x %c\n", __arglist((IEnumerable<char>)"World", (object)6, (IComparable<char>)'7'));

// Hello %s! is %d x %c

// System.Collections.Generic.IEnumerable`1[System.Char]: World

// System.Object: 6

// System.IComparable`1[System.Char]: 7

得到的參數(shù)值的類型是?TypedReference,需要使用?TypedReference.ToObject 靜態(tài)方法才能得到參數(shù)的實(shí)際值。需要注意?TypedReference?的另外兩個靜態(tài)方法:GetTargetType?和?TargetTypeToken,它們與?ArgIterator.GetNextArgType 方法一樣只能得到調(diào)用時的目標(biāo)參數(shù)類型。

關(guān)于?TypedReference?還有一些未公布的關(guān)鍵字,但它們并不建議使用,因?yàn)橐话阌貌坏竭@些功能,或者有可替代的托管方法。

__makeref,用于創(chuàng)建?TypedReference?實(shí)例:

1

2

string?str =?"any value";

TypedReference typeRef = __makeref(str);

__refvalue,用于獲取或設(shè)置?TypedReference?實(shí)例的值,要求類型必須與?TypedReference?的目標(biāo)類型完全相同,而且用法完比較怪異:

1

2

__refvalue(typeRef,?string) =?"other value";

Console.WriteLine(__refvalue(typeRef,?string));

注意這里仍然是目標(biāo)類型,并非是值的實(shí)際類型:

1

2

3

4

object?str =?"any value";

TypedReference typeRef = __makeref(str);

__refvalue(typeRef,?object) =?"other value";

Console.WriteLine(__refvalue(typeRef,?object));

__reftype,用于獲取?TypedReference?的目標(biāo)類型,與?TypedReference.GetTargetType?等價(jià):

1

Console.WriteLine(__reftype(typeRef));

這里再強(qiáng)調(diào)一遍,除了?__arglist?關(guān)鍵字之外,其它關(guān)鍵字不建議使用。Visual Studio 2013 的語法檢查可以識別?__arglist?關(guān)鍵字,其它關(guān)鍵字會提示語法錯誤(但能夠編譯通過)。

C# 中的可變參數(shù)方法具有以下特點(diǎn):

  • 可變參數(shù)方法是不符合 CLS 的。
  • 接口可以聲明可變參數(shù)方法,可變參數(shù)方法也可以是 virtual 方法,并能夠由子類重寫。
  • 通過反射獲取的參數(shù)個數(shù),只會包含固定參數(shù)(__arglist?之前的參數(shù))。因?yàn)?__arglist?僅僅代表方法的調(diào)用約定,并不是實(shí)際的參數(shù)。
  • 可變參數(shù)方法可以包含?0?個固定參數(shù),即聲明類似?void MyMethod(__arglist)?的方法。
  • __arglist?不能用在委托中。

三、可變參數(shù)方法的 IL 代碼

上面從 C# 語言的角度介紹了可變參數(shù)方法,最后來剖析一下它的 IL 原理。

可變參數(shù)方法的調(diào)用,同樣是使用?call?指令和?callvirt?指令,但需要明確指定參數(shù)類型。例如printf("Hello %s! is %d x %c\n", __arglist("World", 6, '7'));?對應(yīng)的 IL 代碼如下所示:

IL_0000: ldstr "Hello %s! is %d x %c\n"

IL_0005: ldstr "World"

IL_000A: ldc.i4.6

IL_000B: ldc.i4.s 55

IL_000D: call void?Cyjb.TestProgram::printf(string, string, int32, char)

簡單解釋一下,就是按順序?qū)⑺膫€參數(shù)(一個固定參數(shù)和三個可變參數(shù))推送到堆棧上,最后調(diào)用方法。可以看到?__arglist()?的作用就是展開方法參數(shù),并且填充參數(shù)類型。注意這里將所有四個參數(shù)的類型都寫入了 IL,才能正確調(diào)用可變參數(shù)的方法,這也是為什么特別提供了?ILGenerator.EmitCall 方法來調(diào)用可變參數(shù)的方法。

public static void printf(string format, __arglist)?方法聲明的 IL 代碼如下所示:

.method public?hidebysig static?vararg void?printf (string?format) cil managed

注意這里方法的參數(shù)實(shí)際上只有一個固定參數(shù)?format,只不過在方法的簽名部分多了一個?vararg,表示方法是可變參數(shù)的,與反射得到的結(jié)果相同。

方法體中倒沒有什么特殊的地方,同樣是調(diào)用?ArgIterator?和?TypedReference?的相關(guān)方法,不過用到了?arglist?指令來為?ArgIterator 構(gòu)造函數(shù)?提供參數(shù),該指令就是由?__arglist?關(guān)鍵字而來的,其作用是返回指向可變參數(shù)列表的非托管指針。

上面提到的?__makeref、__refvalue?和?__reftype?關(guān)鍵字,則分別對應(yīng)于?mkrefany、refanyval?和?refanytype?指令,這里不再詳述。

四、RuntimeArgumentHandle

前面說到,委托中是不能使用?__arglist?關(guān)鍵字的,那么如果為可變參數(shù)方法創(chuàng)建委托呢?如果注意看?ArgIterator 的構(gòu)造函數(shù),可以發(fā)現(xiàn)它的參數(shù)是一個RuntimeArgumentHandle 結(jié)構(gòu),這個結(jié)構(gòu)中包含一個指向可變參數(shù)的參數(shù)列表的指針。

因此,完全可以使用?RuntimeArgumentHandle?來代替方法聲明中的?__arglist?關(guān)鍵字,如下所示:

1

2

3

4

public?static?void?printf(string?format, RuntimeArgumentHandle handle) {

????ArgIterator args =?new?ArgIterator(handle);

????// 其它代碼

}

與?public static void printf(string format, __arglist)?聲明具有完全相同的效果,而且?RuntimeArgumentHandle?完全可以用在任何地方。

但是這個?printf?方法的調(diào)用卻是個很大的問題,因?yàn)槲覀儫o法創(chuàng)建有效的?RuntimeArgumentHandle 結(jié)構(gòu)的實(shí)例(它沒有含帶參數(shù)的構(gòu)造函數(shù)),而且__arglist("World", 6, '7')?這樣使用也是不可以的(從上面的 IL 代碼可以看出,__arglist()?的作用是將參數(shù)展開)。

要調(diào)用這樣的方法,必須再包裝一層包含?__arglist?的方法:

1

2

3

public?static?void?Wrap(string?format, __arglist) {

????printf(format, __arglist);

}

可以認(rèn)為,方法體中的?__arglist?關(guān)鍵字就是一個隱式創(chuàng)建的?RuntimeArgumentHandle?實(shí)例,甚至可以直接?RuntimeArgumentHandle handle = __arglist;這樣使用。

這樣做看起來的確是多此一舉,但如果要調(diào)用包含?RuntimeArgumentHandle?參數(shù)的委托,也只有這一種辦法了,普通方法更適合繼續(xù)使用?__arglist。

作者:CYJB?

總結(jié)

以上是生活随笔為你收集整理的C# 中的可变参数方法(VarArgs)的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網(wǎng)站內(nèi)容還不錯,歡迎將生活随笔推薦給好友。