几个Caller-特性的妙用

news2024/11/25 12:21:50

System.Runtime.CompilerServices命名空间下有4个以“Caller”为前缀命名的Attribute,我们可以将它标注到方法参数上自动获取当前调用上下文的信息,比如当前的方法名、某个参数的表达式、当前源文件的路径,以及当前代码在源文件中的行号。

一、CallerMemberNameAttribute

顾名思义,如果当我们将CallerMemberNameAttribute特性标注到“可缺省参数”上,调用方无需显式指定参数值就可以将表示当前调用方法名赋值给该参数。如下面的代码片段所示,我们为ActivitySource定义了一个名为StartNewActivity的扩展方法,表示Activity名称的name参数是一个“可缺省参数”。我们在该参数上标准了CallerMemberNameAttribute特性,意味着当前调用的方法名将自动作为参数值。

public static class Extensions
{
    public static Activity? StartNewActivity(this ActivitySource activitySource, ActivityKind kind = ActivityKind.Internal, [CallerMemberName] string name = "")
   => activitySource.StartActivity(name: name, kind: kind);
}

以Activity/ActivitySource/ActivityListener为核心的模型实际上是对OpenTelemetry的实现,所有我们可以利用上面定义的这个StartNewActivity创建一个代码跟踪操作的Activity(对应OpenTelemetry下的Span)。针对StartNewActivity方法调用体现在如下这个Invoker类型中,它的构造函数中注入了ActivitySource 对象。InvokeAsync方法内部调用了私有方法FooAsync、后者又调用了BarAsync方法,调用链InvokeAsync->FooAsync->BarAsync的跟踪通过调用ActivitySource的StartNewActivity扩展方法被记录下来,我们在调用此方法时并没有指定参数。

public class Invoker
{
    private readonly ActivitySource _activitySource;
    public Invoker(ActivitySource activitySource) => _activitySource = activitySource;

    public async Task InvokeAsync()
    {
        using (_activitySource.StartNewActivity())
        {
            await Task.Delay(100);
            await FooAsync();
        }
    }

    private async Task FooAsync()
    {
        using (_activitySource.StartNewActivity())
        {
            await Task.Delay(100);
            await BarAsync();
        }
    }

    private Task BarAsync()
    {
        using (_activitySource.StartNewActivity())
        {
            return Task.Delay(100);
        }
    }
}

我们利用如下的代码利用依赖注入框架将Invoker对象创建出来,并调用其Invoke方法。

ActivitySource.AddActivityListener(new ActivityListener
{
    ShouldListenTo = _ => true,
    Sample = (ref ActivityCreationOptions<ActivityContext> options) => ActivitySamplingResult.AllData,
    ActivityStopped = activity => {
        Console.WriteLine(activity.DisplayName);
        Console.WriteLine($"\tTraceId:{activity.TraceId}");
        Console.WriteLine($"\tSpanId:{activity.SpanId}");
        Console.WriteLine($"\tDuration:{activity.Duration}");
        foreach (var kv in activity.TagObjects)
        {
            Console.WriteLine($"\t{kv.Key}:{kv.Value}");
        }
        Console.WriteLine();
    }
});

await new ServiceCollection()
   .AddSingleton(new ActivitySource("App"))
   .AddSingleton<Invoker>()
   .BuildServiceProvider()
   .GetRequiredService<Invoker>()
   .InvokeAsync();

我们利用注册的ActivityListener在Activity终止时将Activity相关跟踪信息(操作名称、SpanId、ParentId、执行时间和Tag)打印在控制台上,具体输出如下所示。

二、CallerArgumentExpressionAttribute

CallerArgumentExpressionAttribute特性里利用目标参数将当前方法调用的某个参数(构造函数的参数表示该参数的名称)的表达式保存下来。如果指定的是一个变量(或者参数),捕获到的就是变量名。比如我们定义了如下这个用来验证参数并确保它不能为Null的ArgumentNotNull<T>。除了第一个表示参数值的argumentValue参数,它还具有一个表示参数名的argumentName参数,抛出的ArgumentNullException异常的参数名就来源于此。

public static class Guard
{
    public static T ArgumentNotNull<T>(T argumentValue, [CallerArgumentExpression("argumentValue")] string argumentName = "") where T:class
    {
        if (argumentValue is null) throw new ArgumentNullException(argumentName);
        return argumentValue;
    }
}

我们修改了Invoker的构造函数,并按照如下的方式添加了针对输出参数(ActivitySource对象)的验证,以避免后续抛出NullReferenceException异常。可以看出,我们调用ArgumentNotNull方法时并没有执行表示参数名称的第二个参数。

var invoker = new Invoker(null);

public class Invoker
{
    private readonly ActivitySource _activitySource;
    public Invoker(ActivitySource activitySource) => _activitySource = Guard.ArgumentNotNull(activitySource);
   ...
}

如果我们按照如上的方式调用Invoker的构造函数,并将Null作为参数,此时会抛出如下的异常,可以看到抛出的ArgumentNullException异常被赋予了正确的参数名。

三、CallerFilePathAttribute &CallerLineNumberAttribute

CallerFilePathAttribute 和CallerLineNumberAttribute特性会将源代码的两个属性赋值给目标参数。具体来说,前者会将当前源文件的路径绑定到目标参数,后者绑定的则是当前执行代码在源文件中的行数。下面的代码为StartNewActivity扩展方法额外添加了两个参数,并标注了如上两个特性,我们将对应的参数值作为Tag添加到创建的Activity中。

public static class Extensions
{
    public static Activity? StartNewActivity(
        this ActivitySource activitySource,
        ActivityKind kind = ActivityKind.Internal,
        [CallerMemberName] string name = "",
        [CallerFilePath] string? filePath = default,
        [CallerLineNumber] int lineNumber = default)
    => activitySource
        .StartActivity(name: name, kind: kind)
        ?.AddTag("CallerFilePath", filePath)
        ?.AddTag("CallerLineNumber", lineNumber);
}

再次执行我们的程序,控制台上就会输出添加的两个Tag。

四、”魔法”的背后

其实这四个Attribute背后并没有什么魔法,“语法糖”而已。对于Invoker的三个方法(InvokeAsync、FooAsync和BarAsync)针对StartNewActivity扩展方法的调用。虽然我们并没有指定任何参数,但是编译器在编译后会帮助我们将参数补齐,完整的代码如下所示。

using System.Diagnostics;
using System.Threading.Tasks;

public class Invoker
{
    private readonly ActivitySource _activitySource;

    public Invoker(ActivitySource activitySource)
    {
        _activitySource = Guard.ArgumentNotNull(activitySource, "activitySource");
    }

    public async Task InvokeAsync()
    {
        using (_activitySource.StartNewActivity(ActivityKind.Internal, "InvokeAsync", "D:\\Projects\\App\\App\\Program.cs", 40))
        {
            await Task.Delay(100);
            await FooAsync();
        }
    }

    private async Task FooAsync()
    {
        using (_activitySource.StartNewActivity(ActivityKind.Internal, "FooAsync", "D:\\Projects\\App\\App\\Program.cs", 49))
        {
            await Task.Delay(100);
            await BarAsync();
        }
    }

    private Task BarAsync()
    {
        using (_activitySource.StartNewActivity(ActivityKind.Internal, "BarAsync", "D:\\Projects\\App\\App\\Program.cs", 58))
        {
            return Task.Delay(100);
        }
    }
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/651923.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

RedisSon高并发分布式锁实战RedisSon源码解读

Redis高并发分布式锁实战 1.分布式场景下的synchronized失效的问题–用redis实现分布式锁 synchronized是通过monitor实现的jvm级别的锁&#xff0c;如果是分布式系统&#xff0c;跑在不同的虚拟机上的tomcat上&#xff0c;会导致synchronized无法锁住对象 ----------- 需要分…

读数据压缩入门笔记05_字典转换

1. 瓶颈 1.1. 在网络带宽有限、存储昂贵的时期 1.2. 移动设备正日益成为人们访问互联网的首选的今天 1.3. 数据压缩成了缓解这些瓶颈的关键 2. 字典转换 2.1. dictionary transforms 2.2. 完全改变了人们对数据压缩的认知 2.2.1. 压缩变成了一种对各种类型的数据都有用的…

《C和指针》读书笔记(第十一章 动态内存分配)

目录 0 简介1 为什么使用动态内存分配2 malloc和free3 calloc和realloc4 使用动态分配的内存5 常见的动态内存错误6 内存分配实例6.1 排序一列整型值6.2 复制字符串6.3 变体记录的创建与销毁 7 总结 0 简介 在实际开发中&#xff08;C语言&#xff09;&#xff0c;数组的元素存…

JDK,JRE,JVM有什么区别?跨平台?跨语言?

JDK Java Development Kit&#xff08;Java开发工具包&#xff09;&#xff0c;提供了Java的开发环境和运行环境。包含Java源文件的编译器Javac&#xff0c;还有调试和分析工具。 JRE Java Runtime Environment&#xff08;Java运行环境&#xff09;包含了Java虚拟机&#xff…

WPF开发txt阅读器10:语音播报快进快退

文章目录 MySpeech类快进 文章目录 MySpeech类快进 txt阅读器系列&#xff1a; 需求分析和文件读写目录提取类&#x1f48e;列表控件与目录字体控件绑定&#x1f48e;前景/背景颜色书籍管理系统&#x1f48e;用树形图管理书籍语音播放&#x1f48e;播放进度显示 MySpeech类 …

MySQL 中有哪些锁?

数据库中锁的设计初衷处理并发问题&#xff0c;作为多用户共享资源&#xff0c;当出现并发访问的时候&#xff0c;数据库需要合理控制资源访问规则。锁就是实现这些访问规则中的重要数据。 锁的分类 根据加锁范围&#xff0c;MySQL 里面的锁可以分成全局锁、表级锁、行锁三类…

计算机视觉算法——BEV Perception算法总结

计算机视觉算法——BEV Perception算法总结 计算机视觉算法——BEV Perception算法总结1. Homograph Based——3D LaneNet2. Depth Based——LSS3. MLP Based——PON4. Transformer Based——BEVFormer5. Transformer Based——Translating Image into Maps 计算机视觉算法——…

急于生成人工智能是有风险的:如何保护数据

每天的业务用户都在尝试使用 ChatGPT 和其他生成式 AI 工具。事实上&#xff0c; Gartner 预测&#xff0c; 到 2025 年&#xff0c;30% 的营销内容将由生成式人工智能创建并由人类增强。 然而&#xff0c;像三星这样的公司已经发现&#xff0c;不了解新技术风险的用户正在成…

linux(线程控制)

目录&#xff1a; 1.线程创建 2.线程等待 3.线程终止 4.线程分离 5.线程ID -------------------------------------------------------------------------------------------------------------------------------- 1.线程创建 pthread_create pthread_t *pthread 是一个输出型…

Nucleo-F411RE (STM32F411)LL库体验 5 - 通用定时器TIM2的使用

Nucleo-F411RE &#xff08;STM32F411&#xff09;LL库体验 5 - 通用定时器TIM2的使用 1、简述 设定TIM2&#xff0c;计数周期为10KHZ&#xff0c;即计时1s需要10000次&#xff0c;通过shell命令动态修改reload值&#xff0c;来更改定时器的频率。 假定设定TIM2 counter cloc…

第五章 部署PKI与证书服务

❄️作者介绍&#xff1a;奇妙的大歪❄️ &#x1f380;个人名言&#xff1a;但行前路&#xff0c;不负韶华&#xff01;&#x1f380; &#x1f43d;个人简介&#xff1a;云计算网络运维专业人员&#x1f43d; 前言 PKI&#xff08;公钥加密基础结构&#xff09;&#xff1a;通…

【C++】list的使用和模拟实现

目录 1.什么是list2.list的一些接口3.list的模拟实现3.1 迭代器3.2 list主体3.2.1 构造函数3.2.2 拷贝构造、赋值重载3.2.3 主体内引入迭代器3.2.4 insert和erase3.2.5 clear和析构函数 3.3 const迭代器的实现3.4 实现迭代器的operator-> 4.总结list迭代器的实现 1.什么是li…

领域驱动应用架构实践

一个合适的应用架构不仅能促使项目朝着好的方向发展&#xff0c;易于维护&#xff0c;也能指导团队成员有效协作。 DDD是站在领域的角度来驱动应用架构的落地&#xff0c;接下来将介绍一种落地方案。 架构分层 首先在架构层次方面&#xff0c;在遵循DDD的分层架构模式的同时&…

STM32单片机(六)TIM定时器 -> 第五节:TIM输入捕获

❤️ 专栏简介&#xff1a;本专栏记录了从零学习单片机的过程&#xff0c;其中包括51单片机和STM32单片机两部分&#xff1b;建议先学习51单片机&#xff0c;其是STM32等高级单片机的基础&#xff1b;这样再学习STM32时才能融会贯通。 ☀️ 专栏适用人群 &#xff1a;适用于想要…

Nucleo-F411RE (STM32F411)LL库体验 4 -Letter Shell移植与调试

Nucleo-F411RE &#xff08;STM32F411&#xff09;LL库体验 4 -Letter Shell移植与使用 1、串口的初始化 Nucleo-F411RE自带st-link&#xff0c;并支持虚拟串口的功能&#xff0c;根据原理图&#xff0c;st-link的rx tx接到了Nucleo-F411RE的PA2 PA3&#xff0c;所以我们要初…

以太网MII、RMII、GMII、RGMII(三)

目录 一、MII 二、RMII 三、GMII 四、RGMII 以太网硬件主要包括OSI的最下面两层&#xff0c;物理层和数据链路层&#xff0c;前者的芯片为PHY&#xff0c;后者的芯片为MAC控制器。而MAC与PHY之间的常用的数据传输接口有MII、RMII、GMII、RGMII。 模式 时钟 位宽 速率 M…

pytorch笔记:transformer 源码

来自B站视频&#xff0c;API查阅&#xff0c;TORCH.NN seq2seq 可以是 CNN&#xff0c;RNN&#xff0c;transformer nn.Transformer 关键源码&#xff1a; encoder_layer TransformerEncoderLayer(d_model, nhead, dim_feedforward, dropout,activation, layer_norm_eps, ba…

5.vue3医疗在线问诊项目 - _极速问诊-前置准备 ==> 需求分析、枚举类型、pinia仓库的初始化

5.vue3医疗在线问诊项目 - _极速问诊-前置准备 > 需求分析、枚举类型、pinia仓库的初始化 极速问诊-需求分析{#consult-product} 极速问诊阶段流程分析 线下看病流程&#xff1a; 选择医院&#xff08;三甲、普通&#xff09;》挂号》选择科室 》选择医生&#xff08;专家…

牛客网专项练习——C语言错题集(5)

文章目录 指针的值指针与数组、函数的组合空结构体* 和 的优先级 指针的值 指针的值是一个地址&#xff0c;题目中的字符串 “girl” 应该是 *p 的值&#xff0c;即指针 p 所指地址存储的数据的值。 指针与数组、函数的组合 int *p[n] 等价于 int (*)p[n]&#xff0c;是一个…

xinput1_3.dll丢失怎么办?xinput1_3.dll丢失的修复方法

xinput1_3.dll是电脑文件中的dll文件&#xff08;动态链接库文件&#xff09;。如果计算机中丢失了某个dll文件&#xff0c;可能会导致某些软件和游戏等程序无法正常启动运行&#xff0c;并且导致电脑系统弹窗报错。 在我们打开软件或者游戏的时候&#xff0c;电脑提示xinput1_…