- A+
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/]