C#10 新特性 [调用方参数表达式] 解决了我七年前的困惑

C#10 新特性 [调用方参数表达式] 解决了我七年前的困惑

C#10 新特性 [调用方参数表达式] 解决了我七年前的困惑

独立观察员 2022 年 2 月 13 日

 

一、问题

时间拉回到 2015 年,那年 3 月,我还没有毕业,不过已经在公司里实习了,从大三暑假开始,到那时候,已经快实习一年了(毕业后才能转正)。对于工作还是比较满意的,九点多上班(看班车什么时候到),十一点可以吃午饭,吃完饭周边散个步,然后回公司午休,下午基本坐 5 点四十 的班车回家,双休;当时组里的小伙伴们气氛也比较好,组长也比较好,我们主要负责公司内部二十多个 OA 系统(全公司一两千人),任务安排得也不是很紧;本来大学学的是 Java,公选课学了 C# 就爱上了,实习用的是现在早已过时的 Webform,当然还有 SQL;实习嘛,经常也是边学边做,经常在网上找解决方案,用麦库记事(已倒闭)做笔记,还有用问答网站进行提问,用得比较多的就是待会儿要出场的 “思否”,偶尔用的还有昙花一现的 “德问”。

 

当时有一个业务,具体的忘了,只记得用到了反射,当时为了写更少的代码,想要在方法中获取调用者传参时的实参的变量名,不知道怎么弄,于是在 segmentfault.com(思否)网站上提了这么一个问题 ——“C# 如何通过形参获得实参的名字?”:

C#10 新特性 [调用方参数表达式] 解决了我七年前的困惑插图

 

经过一番讨论与思考,当时我妥协了,认识到这是不可能实现的:

C#10 新特性 [调用方参数表达式] 解决了我七年前的困惑插图1

 

二、转机

直到昨天看到有人转载了一篇 微软中国 MSDN 的公众号文章《C# 10 的新特性》,在最后部分写了这么一段(灰色的原文链接有误,后面会给出正确的):

C#10 新特性 [调用方参数表达式] 解决了我七年前的困惑插图2

 

当看到下图框出的字符 b 时,我的思绪一下被拉到了七年前,这不就是我当时说服了自己把它当作不可能的事吗,现在竟然变成了可能!只能说,微软 NB!

C#10 新特性 [调用方参数表达式] 解决了我七年前的困惑插图3

 

而且,上图没有框出的另外两个例子还展示了这个功能更强大的地方 —— 不光是形参名称(或者应该叫字面量?),任何表达式它都能原样获取,比如 “true” 和 “a > 5”。

 

那么这个强大的功能叫什么名字呢?它就是 CallerArgumentExpressionAttribute,可以称之为 “调用方参数表达式特性”。上面公众号文章截图中的参考链接有误,正确的应该是这个 ——https://docs.microsoft.com/zh-cn/dotnet/csharp/language-reference/attributes/caller-information#argument-expressions :

C#10 新特性 [调用方参数表达式] 解决了我七年前的困惑插图4

 

可以看到它也是在 System.Runtime.CompilerServices 命名空间中,相当于扩充了原先的调用方信息,之前的调用方信息有三项,分别是 文件路径、行号、方法名,现在新增了参数表达式。关于旧的调用方信息三巨头的使用,可以参考我之前的文章《C# 在自定义的控制台输出重定向类中整合调用方信息》。

 

三、实践

下面开始实践,例子都来源于微软,上面也都提到了。

 

1、演示输出各种形式的参数表达式

首先就给我来了个下马威,我用 VS2022 打开之前的解决方案总是有各种问题:项目都被卸载了,也重新加载不了;点击重新加载具有依赖项的项目也不行;点击安装缺少的功能,提示已安装,点击启动就会又启动一个 VS2022,打开解决方案还是这样;感觉就是有 Bug。

C#10 新特性 [调用方参数表达式] 解决了我七年前的困惑插图5

 

然后用 VS2019 进行开发,代码都写完了,运行也没有报错,但是没有效果:

C#10 新特性 [调用方参数表达式] 解决了我七年前的困惑插图6

 

当然,这可能不能怪 VS2019,因为公众号文章开头是这样说的:

我们很高兴地宣布 C# 10 作为 .NET 6 和 Visual Studio 2022 的一部分已经发布了。

 

就是说应该是需要满足 .NET 6 和 VS2022 这两个条件的。然后既然 Visual Studio 2022 不争气,那么我们来试试 Rider:

C#10 新特性 [调用方参数表达式] 解决了我七年前的困惑插图7

 

果然成功了!jetbrains NB!

C#10 新特性 [调用方参数表达式] 解决了我七年前的困惑插图8

 

2、参数不符合条件时抛出异常

C#10 新特性 [调用方参数表达式] 解决了我七年前的困惑插图9

 

帮助方法如下:

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​ 中的功能,也是用到了 “调用方参数表达式”:

C#10 新特性 [调用方参数表达式] 解决了我七年前的困惑插图10

 

3、获取调用扩展方法的表达式

C#10 新特性 [调用方参数表达式] 解决了我七年前的困惑插图11

 

因为扩展方法也可以当做静态方法来使用( var sequence = ExtensionTester.GetNewSequence (Enumerable.Range (0, 10), 100); ),所以也就很好理解了。另外,微软的例子中没有我后面加的那句 ToList () 的操作,我试了是不行的,因为 Linq 的延迟执行的特性(要实际用到才会执行),如果没有那句,本例的扩展方法不会被执行。

 

四、结语

就像开头讲述的那样,实际上我昨天看到这个功能时还是挺激动的,虽然只是个不起眼的小功能,但是那种感觉就像是:一件尘封多年的悬案,因为时代的局限,基本被视作无法找到真相了,突然有一天,由于科技的进步(比如指纹技术或者 DNA 检测技术),终于真相大白。

 

好了,有点晚了,本文明天再发布,明天是情人节,祝我好运吧,也不知道我这个人生的 “悬案” 什么时候能告破。

 

最后给出源码地址:https://gitee.com/dlgcy/DLGCY_WPFPractice/tree/Blog20220213 

感谢阅读。

 

2 Comments

ALan Posted on14:11 - 2022年7月24日

居然有人和我有一样的需求吗,而且还困惑了7年,感谢你的辛勤付出!难得c#的爱好者,希望能交个朋友

    独立观察员 Posted on19:45 - 2022年7月24日

    哈哈哈,可以关注我的公众号“独立观察员博客”,可以加微信交流

Leave a Reply