C#10 新特性 [調用方參數表達式] 解決了我七年前的困惑
一、問題
時間拉回到 2015 年,那年 3 月,我還沒有畢業,不過已經在公司里實習了,從大三暑假開始,到那時候,已經快實習一年了(畢業后才能轉正)。對於工作還是比較滿意的,九點多上班(看班車什么時候到),十一點可以吃午飯,吃完飯周邊散個步,然后回公司午休,下午基本坐 5 點四十 的班車回家,雙休;當時組里的小伙伴們氣氛也比較好,組長也比較好,我們主要負責公司內部二十多個 OA 系統(全公司一兩千人),任務安排得也不是很緊;本來大學學的是 Java,公選課學了 C# 就愛上了,實習用的是現在早已過時的 Webform,當然還有 SQL;實習嘛,經常也是邊學邊做,經常在網上找解決方案,用麥庫記事(已倒閉)做筆記,還有用問答網站進行提問,用得比較多的就是待會兒要出場的 “思否”,偶爾用的還有曇花一現的 “德問”。
當時有一個業務,具體的忘了,只記得用到了反射,當時為了寫更少的代碼,想要在方法中獲取調用者傳參時的實參的變量名,不知道怎么弄,於是在 segmentfault.com(思否)網站上提了這么一個問題 ——“C# 如何通過形參獲得實參的名字?”:
經過一番討論與思考,當時我妥協了,認識到這是不可能實現的:
二、轉機
直到昨天看到有人轉載了一篇 微軟中國 MSDN 的公眾號文章《C# 10 的新特性》,在最后部分寫了這么一段(灰色的原文鏈接有誤,后面會給出正確的):
當看到下圖框出的字符 b 時,我的思緒一下被拉到了七年前,這不就是我當時說服了自己把它當作不可能的事嗎,現在竟然變成了可能!只能說,微軟 NB!
而且,上圖沒有框出的另外兩個例子還展示了這個功能更強大的地方 —— 不光是形參名稱(或者應該叫字面量?),任何表達式它都能原樣獲取,比如 “true” 和 “a > 5”。
那么這個強大的功能叫什么名字呢?它就是 CallerArgumentExpressionAttribute,可以稱之為 “調用方參數表達式特性”。上面公眾號文章截圖中的參考鏈接有誤,正確的應該是這個 ——https://docs.microsoft.com/zh-cn/dotnet/csharp/language-reference/attributes/caller-information#argument-expressions :
可以看到它也是在 System.Runtime.CompilerServices 命名空間中,相當於擴充了原先的調用方信息,之前的調用方信息有三項,分別是 文件路徑、行號、方法名,現在新增了參數表達式。關於舊的調用方信息三巨頭的使用,可以參考我之前的文章《C# 在自定義的控制台輸出重定向類中整合調用方信息》。
三、實踐
下面開始實踐,例子都來源於微軟,上面也都提到了。
1、演示輸出各種形式的參數表達式
首先就給我來了個下馬威,我用 VS2022 打開之前的解決方案總是有各種問題:項目都被卸載了,也重新加載不了;點擊重新加載具有依賴項的項目也不行;點擊安裝缺少的功能,提示已安裝,點擊啟動就會又啟動一個 VS2022,打開解決方案還是這樣;感覺就是有 Bug。
然后用 VS2019 進行開發,代碼都寫完了,運行也沒有報錯,但是沒有效果:
當然,這可能不能怪 VS2019,因為公眾號文章開頭是這樣說的:
我們很高興地宣布 C# 10 作為 .NET 6 和 Visual Studio 2022 的一部分已經發布了。
就是說應該是需要滿足 .NET 6 和 VS2022 這兩個條件的。然后既然 Visual Studio 2022 不爭氣,那么我們來試試 Rider:
果然成功了!jetbrains NB!
2、參數不符合條件時拋出異常
幫助方法如下:
using System; using System.Runtime.CompilerServices; namespace WPFPractice.Utils { public class MethodHelper { /// <summary> /// 驗證參數(不符合條件則拋出異常) /// </summary> /// <param name="parameterName">參數名</param> /// <param name="condition">條件結果(調用時傳條件表達式)</param> /// <param name="message">消息(無需填寫,通過調用方參數表達式功能自動獲取條件參數的輸入表達式)</param> public static void ValidateArgument(string parameterName, bool condition, [CallerArgumentExpression("condition")] string? message = null) { if (!condition) { throw new ArgumentException($"Argument failed validation: <{message}>", parameterName); } } } }
當然,如果只是為了判斷參數是否為 null,且為 null 時拋出異常,微軟公眾號文章中提到了一個現成的方法:
void MyMethod(object value) { ArgumentNullException.ThrowIfNull(value); }
反編譯后可以看到是微軟庫 Microsoft.NETCore.App6.0.2System.Private.CoreLib.dll 中的功能,也是用到了 “調用方參數表達式”:
3、獲取調用擴展方法的表達式
因為擴展方法也可以當做靜態方法來使用( var sequence = ExtensionTester.GetNewSequence (Enumerable.Range (0, 10), 100); ),所以也就很好理解了。另外,微軟的例子中沒有我后面加的那句 ToList () 的操作,我試了是不行的,因為 Linq 的延遲執行的特性(要實際用到才會執行),如果沒有那句,本例的擴展方法不會被執行。
四、結語
就像開頭講述的那樣,實際上我昨天看到這個功能時還是挺激動的,雖然只是個不起眼的小功能,但是那種感覺就像是:一件塵封多年的懸案,因為時代的局限,基本被視作無法找到真相了,突然有一天,由於科技的進步(比如指紋技術或者 DNA 檢測技術),終於真相大白。
好了,有點晚了,本文明天再發布,明天是情人節,祝我好運吧,也不知道我這個人生的 “懸案” 什么時候能告破。
最后給出源碼地址:https://gitee.com/dlgcy/DLGCY_WPFPractice/tree/Blog20220213
感謝閱讀。
原創文章,轉載請注明: 轉載自 獨立觀察員
本文鏈接地址: C#10 新特性 [調用方參數表達式] 解決了我七年前的困惑 [http://dlgcy.com/c-sharp-callerargumentexpressionattribute/]