C# 10 新特性 CallerArgumentExpression
Intro
C# 10 支持使用 CallerArgumentExpression 來自動(dòng)地獲取調(diào)用方的信息,這可以簡化我們現(xiàn)在的一些代碼,讓代碼更加簡潔,一起看下面的示例吧
Caller Info
C# 在 5.0 的時(shí)候開始支持 Caller Info 自動(dòng)獲取調(diào)用方的一些信息,C# 5 開始支持的 Caller Info Attribute 有三個(gè),
[ CallerMemberName] - 調(diào)用方成員名稱,方法名或者屬性名.[ CallerFilePath] - 調(diào)用方源代碼所在文件路徑[ CallerLineNumber] - 調(diào)用方所在源代碼的行數(shù)信息
在方法參數(shù)中添加一個(gè) Attribute 來獲取調(diào)用方信息,使用示例如下:
public?static?void?MainTest()
{
????//?我是誰,我在哪兒
????DumpCallerInfo();
}
private?static?void?DumpCallerInfo(
????[CallerFilePath]?string??callerFilePath?=?null,
????[CallerLineNumber]?int??callerLineNumber?=?null,
????[CallerMemberName]?string??callerMemberName?=?null
)
{
????Console.WriteLine("Caller?info:");
????Console.WriteLine($@"CallerFilePath:?{callerFilePath}
CallerLineNumber:?{callerLineNumber}
CallerMemberName:?{callerMemberName}
");
}
針對 CallerLineNumber 的類型是 int,所以參數(shù)類型需要能夠直接接收 int,如上面的 [CallerLineNumber] int? callerLineNumber = null 也可以改成 [CallerLineNumber] int callerLineNumber = -1 或者 [CallerLineNumber] long callerLineNumber = -1但是不能改成 [CallerLineNumber] string callerLineNumber = null 或者 [CallerLineNumber] short callerLineNumber = -1``string 類型不兼容,short 不能隱式轉(zhuǎn)換上面代碼輸出結(jié)果類似下面:
Caller?info:
CallerFilePath:?C:\projects\sources\SamplesInPractice\CSharp10Sample\CallerInfo.cs
CallerLineNumber:?8
CallerMemberName:?MainTest

CallerArgumentExpression
CallerArgumentExpression 也是屬于一種 Caller Info下面這里是利用 CallerArgumentExpression 實(shí)現(xiàn)的幾個(gè)驗(yàn)證方法,如果參數(shù)不合法就拋出一個(gè)異常,通過 CallerArgumenExpression 來自動(dòng)的獲取調(diào)用方的參數(shù)表達(dá)式
public?static?class?Verify
{
????public?static?void?Argument(bool?condition,?string?message,?[CallerArgumentExpression("condition")]?string??conditionExpression?=?null)
????{
????????if?(!condition)?throw?new?ArgumentException(message:?message,?paramName:?conditionExpression);
????}
????public?static?void?NotNullOrEmpty(string?argument,?[CallerArgumentExpression("argument")]?string??argumentExpression?=?null)
????{
????????if?(string.IsNullOrEmpty(argument))
????????{
????????????throw?new?ArgumentException("Can?not?be?null?or?empty",?argumentExpression);
????????}
????}
????public?static?void?InRange(int?argument,?int?low,?int?high,
????????[CallerArgumentExpression("argument")]?string??argumentExpression?=?null,
????????[CallerArgumentExpression("low")]?string??lowExpression?=?null,
????????[CallerArgumentExpression("high")]?string??highExpression?=?null)
????{
????????if?(argument?????????{
????????????throw?new?ArgumentOutOfRangeException(paramName:?argumentExpression,
????????????????message:?$"{argumentExpression}?({argument})?cannot?be?less?than?{lowExpression}?({low}).");
????????}
????????if?(argument?>?high)
????????{
????????????throw?new?ArgumentOutOfRangeException(paramName:?argumentExpression,
????????????????message:?$"{argumentExpression}?({argument})?cannot?be?greater?than?{highExpression}?({high}).");
????????}
????}
????public?static?void?NotNull(T??argument,?[CallerArgumentExpression("argument")]?string??argumentExpression?=?null)
????????where?T?:?class
????{
????????ArgumentNullException.ThrowIfNull(argument,?argumentExpression);
????}
}
來看一個(gè)使用調(diào)用示例:
var?name?=?string.Empty;
InvokeHelper.TryInvoke(()?=>?Verify.NotNullOrEmpty(name));
上面的 InvokeHelper.TryInvoke 是封裝的一個(gè)方法,如果有異常會記錄一個(gè)日志
上面代碼執(zhí)行結(jié)果如下:
可以看到我們的名稱也是被記錄了下來 Parameter 名字就是我們傳入的變量名,不需要我們再手動(dòng)的額外加一個(gè) nameof(name) 了再來看一個(gè)調(diào)用示例,調(diào)用代碼如下:
var?num?=?10;
InvokeHelper.TryInvoke(()?=>?Verify.InRange(num,?2,?5));
InvokeHelper.TryInvoke(()?=>?Verify.InRange(num,?10?+?2,?10?+?5));
輸出結(jié)果如下:
如果沒有變量名或者屬性名等,就會直接用傳入進(jìn)來的 value 字面量,如果傳入進(jìn)來的是一個(gè)表達(dá)式,那么記錄下來的就是表達(dá)式本身,比如上面輸出的 5/10 + 2,而 num 是傳入的一個(gè)變量,就會獲取到變量的名字,是不是很神奇,很多驗(yàn)證的地方就可以簡化很多了
Sample
CallerArgumentExpression 有一個(gè)很典型的一個(gè)實(shí)際應(yīng)用就是 .NET 6 里新增的一個(gè) APIArgumentNullException.ThrowIfNull() 方法,這個(gè)方法的實(shí)現(xiàn)如下:
///? Throws?an? ?if? ?is?null.
///?The?reference?type?argument?to?validate?as?non-null.
///?The?name?of?the?parameter?with?which? ?corresponds.
public?static?void?ThrowIfNull([NotNull]?object??argument,?[CallerArgumentExpression("argument")]?string??paramName?=?null)
{
????if?(argument?is?null)
????{
????????Throw(paramName);
????}
}
[DoesNotReturn]
private?static?void?Throw(string??paramName)?=>
????throw?new?ArgumentNullException(paramName);
源碼可以從 Github 上看 https://github.com/dotnet/runtime/blob/v6.0.0/src/libraries/System.Private.CoreLib/src/System/ArgumentNullException.cs我們實(shí)際調(diào)用的時(shí)候就可以不傳參數(shù)名,會自動(dòng)的獲取參數(shù)名,示例如下:
object??xiaoMing?=?null;
InvokeHelper.TryInvoke(()?=>?ArgumentNullException.ThrowIfNull(xiaoMing));
輸出結(jié)果如下:
從上面的結(jié)果我們可以看到,參數(shù)名已經(jīng)自動(dòng)的解析出來了
More
升級 .NET 6 的小伙伴快用這個(gè)改造你的代碼吧,然后就是很多 null 檢查也可以使用新的 ArgumentNullException.ThrowIfNull 去簡化代碼吧。
想使用上述代碼測試,可以從Github 獲取 https://github.com/WeihanLi/SamplesInPractice/blob/master/CSharp10Sample/CallerInfo.cs
