几个Caller-特性的妙用( 二 )

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

几个Caller-特性的妙用

文章插图
二、CallerArgumentExpressionAttributeCallerArgumentExpressionAttribute特性里利用目标参数将当前方法调用的某个参数(构造函数的参数表示该参数的名称)的表达式保存下来 。如果指定的是一个变量(或者参数) , 捕获到的就是变量名 。比如我们定义了如下这个用来验证参数并确保它不能为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异常被赋予了正确的参数名 。
几个Caller-特性的妙用

文章插图
三、CallerFilePathAttribute &CallerLineNumberAttributeCallerFilePathAttribute 和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 。
几个Caller-特性的妙用

文章插图
四、”魔法”的背后其实这四个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);        }    }}

推荐阅读