几个Caller-特性的妙用
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);
}
}
}
几个Caller-特性的妙用的更多相关文章
- CSS3 Media Queries 特性的妙用
第一招: 在网页中,pixel与point比值称为 device-pixel-ratio,普通设备都是1,iPhone 4是2,有些Android机型是1.5. 那么-webkit-min-devic ...
- Net中Attribute特性的高级使用及自定义验证实现
好久没写博客了,今天在百忙之中抽空来写篇文章,记录一下最近深入学习Attribute特性的笔记及心得.~~ 一.什么是特性? 特性(Attribute)是用于在运行时传递程序中各种元素(比如类.方法. ...
- Nginx之进程间的通信机制(信号、信号量、文件锁)
1. 信号 Nginx 在管理 master 进程和 worker 进程时大量使用了信号.Linux 定义的前 31 个信号是最常用的,Nginx 则通过重定义其中一些信号的处理方法来使用吸纳后,如接 ...
- 妙用HTML5的八大特性来开发移动webAPP
webAPP的实现基础就是html5+js+css3.可是webAPP还是基于浏览器的微站点开发.正是如此,我们必需要深入的了解html5的特性,这样才干方便我们在开发和设计APP的时候.更合理的採用 ...
- .Net 特性分析与妙用
一.特性是什么 1.想象很多小伙伴们都看过在一个类上方.或者在控制器见过类似的东东,加上之后就可以标识这个类或者方法就具备了某些特点 ,那我们就进入它的内心一探究竟吧. 2.我们进入某个特性之后,可以 ...
- bind,apply,call,caller,callee还傻傻分不清楚?
先介绍每个的语法: 1. bind() 语法:fn.bind(thisObj[, arg1[, arg2[, ...]]]) fn:是想要改变this指向的函数 thisObj:表示fn中this指针 ...
- JavaScript的妙与乐(一)之 函数优化
JavaScript的妙与乐系列文章主要是展示一些JavaScript上面比较好玩一点的特性和一些有用的技巧,里面很多内容都是我曾经在项目中使用过的一些内容(当然,未必所有技巧的使用频率都很高^_^) ...
- ES6新特性概览
本文基于lukehoban/es6features ,同时参考了大量博客资料,具体见文末引用. ES6(ECMAScript 6)是即将到来的新版本JavaScript语言的标准,代号harmony( ...
- 【转】【C#】C# 5.0 新特性——Async和Await使异步编程更简单
一.引言 在之前的C#基础知识系列文章中只介绍了从C#1.0到C#4.0中主要的特性,然而.NET 4.5 的推出,对于C#又有了新特性的增加--就是C#5.0中async和await两个关键字,这两 ...
- 妙味课堂——HTML+CSS基础笔记
妙味课堂的课程讲得非常的清楚,受益匪浅.先把HTML和CSS基础课程部分视频的学习笔记记录如下: padding #PS基础 ##前端需要的PS技能 - PS技能(前端需要):切图.修图.测量 - P ...
随机推荐
- 从零开始Blazor Server(1)--项目搭建
项目介绍 本次项目准备搭建一个使用Furion框架,Blazor的UI使用BootstrapBlazor.数据库ORM使用Freesql的后台管理系统. 目前的规划是实现简单的注册,登录.增加管理员跟 ...
- 使用Python3.7+Tornado5.1集成新浪微博三方登录(无需企业资质)
原文转载自「刘悦的技术博客」https://v3u.cn/a_id_137 新浪微博:山寨版的twitter,各种粉丝的集散地,天朝人民的最爱,基本上网民都人手一个微博账号,所以使用新浪微博账号进行三 ...
- you need to load the kernel first
背景:在用第三方软件备份win10系统时,提示you need to load the kernel first 1.进BIOS把硬盘AHCI 模式调整成 SATA. 2.检查硬盘数据线是否插紧.主板 ...
- 万答#18,MySQL8.0 如何快速回收膨胀的UNDO表空间
欢迎来到 GreatSQL社区分享的MySQL技术文章,如有疑问或想学习的内容,可以在下方评论区留言,看到后会进行解答 GreatSQL社区原创内容未经授权不得随意使用,转载请联系小编并注明来源. 背 ...
- 基于ABP的AppUser对象扩展
在ABP中AppUser表的数据字段是有限的,现在有个场景是和小程序对接,需要在AppUser表中添加一个OpenId字段.今天有个小伙伴在群中遇到的问题是基于ABP的AppUser对象扩展后,用 ...
- ArkUI 自定义组件
基础入门 组件可以把一段代码封装起来,目的是提高这一段代码的复用率,并且也可以减少开发人员多次编写相同的代码.一旦组件定义好了之后,在页面中只需要通过<element /> 标签引入进来就 ...
- 漂亮简洁的PHP导航源码-蘑菇导航
蘑菇导航 蘑菇导航是根据SimpleWebNavigation修改而来的一个php网址导航.支持php8,支持左侧锚点,支持自定义fontawesome图标. 可以作为群组导航.图床导航.vps导航等 ...
- Sentinel控制台1.8.3修改源码,修改配置后推送到Nacos
目录 1. 接着上一篇 2. 思路 3. 下载Sentinel源码 4. 看Gateway里面读取的配置信息 5. 修改Sentinel控制台源码 6. 熔断规则测试 7. 限流规则测试 8. 打包使 ...
- Html飞机大战(二):面向对象绘制背景
好家伙, 我们为了后续工作的顺利进行,我试着把每一个模块封装为对象 但冻手之前还是要构思一下 我们把天空封装成一个类: 1.来搞一手简单的对象分析: 属性方面的都好理解 来说明一下方法: (1) p ...
- js函数( 普通函数、箭头函数 ) 内部this的指向
- 普通函数 | 具名普通函数.匿名普通函数,在不作为对象的属性值的情况下,其内部的 this 总是指向代码运行环境下的全局对象 ( 例如,浏览器中的 window ). 示例: (functio ...