3月31日,有博主曝光一段疑似演员宋宁峰出轨“Q女士”的录音,多个相关词条登上热搜榜。 4月1日,宋宁峰发长文承认出轨。他表示,录音中的人确实是自己:...
2026-04-01 6
System.Runtime.CompilerServices命名空间下有4个以“Caller”为前缀命名的Attribute,我们可以将它标注到方法参数上自动获取当前调用上下文的信息,比如当前的方法名、某个参数的表达式、当前源文件的路径,以及当前代码在源文件中的行号。
一、CallerMemberNameAttribute
顾名思义,如果当我们将CallerMemberNameAttribute特性标注到“可缺省参数”上,调用方无需显式指定参数值就可以将表示当前调用方法名赋值给该参数。如下面的代码片段所示,我们为ActivitySource定义了一个名为StartNewActivity的扩展方法,表示Activity名称的name参数是一个“可缺省参数”。我们在该参数上标准了CallerMemberNameAttribute特性,意味着当前调用的方法名将自动作为参数值。
publicstaticclassExtensions
publicstaticActivity? StartNewActivity(
thisActivitySource activitySource,
ActivityKind kind = ActivityKind.Internal,
[ CallerMemberName] stringname = "")
=> 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扩展方法被记录下来,我们在调用此方法时并没有指定参数。
publicclassInvoker
privatereadonlyActivitySource _activitySource;
publicInvoker( ActivitySource activitySource)
展开全文
=> _activitySource = activitySource;
publicasyncTask InvokeAsync( )
using(_activitySource.StartNewActivity)
awaitTask.Delay( 100);
awaitFooAsync;
privateasyncTask FooAsync( )
using(_activitySource.StartNewActivity)
awaitTask.Delay( 100);
awaitBarAsync;
privateTask BarAsync( )
using(_activitySource.StartNewActivity)
returnTask.Delay( 100);
我们利用如下的代码利用依赖注入框架将Invoker对象创建出来,并调用其Invoke方法。
ActivitySource.AddActivityListener( newActivityListener
ShouldListenTo = _ => true,
Sample = ( refActivityCreationOptions<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( varkv inactivity.TagObjects)
Console.WriteLine( $"\t {kv.Key}: {kv.Value}" );
Console.WriteLine;
awaitnewServiceCollection
.AddSingleton( newActivitySource( "App"))
.AddSingleton<Invoker>
.BuildServiceProvider
.GetRequiredService<Invoker>
.InvokeAsync;
我们利用注册的ActivityListener在Activity终止时将Activity相关跟踪信息(操作名称、SpanId、ParentId、执行时间和Tag)打印在控制台上,具体输出如下所示。
二、CallerArgumentExpressionAttribute
CallerArgumentExpressionAttribute特性里利用目标参数将当前方法调用的某个参数(构造函数的参数表示该参数的名称)的表达式保存下来。如果指定的是一个变量(或者参数),捕获到的就是变量名。比如我们定义了如下这个用来验证参数并确保它不能为Null的ArgumentNotNull<T>。除了第一个表示参数值的argumentValue参数,它还具有一个表示参数名的argumentName参数,抛出的ArgumentNullException异常的参数名就来源于此。
publicstaticclassGuard
publicstaticT ArgumentNotNull<T>(
T argumentValue,
[ CallerArgumentExpression( "argumentValue") ]
stringargumentName = "") whereT: class
if(argumentValue isnull)
thrownewArgumentNullException(argumentName);
returnargumentValue;
我们修改了Invoker的构造函数,并按照如下的方式添加了针对输出参数(ActivitySource对象)的验证,以避免后续抛出NullReferenceException异常。可以看出,我们调用ArgumentNotNull方法时并没有执行表示参数名称的第二个参数。
varinvoker = newInvoker( null);
publicclassInvoker
privatereadonlyActivitySource _activitySource;
publicInvoker( ActivitySource activitySource)
=> _activitySource = Guard.ArgumentNotNull(activitySource);
如果我们按照如上的方式调用Invoker的构造函数,并将Null作为参数,此时会抛出如下的异常,可以看到抛出的ArgumentNullException异常被赋予了正确的参数名。
三、CallerFilePathAttribute &CallerLineNumberAttribute
CallerFilePathAttribute 和CallerLineNumberAttribute特性会将源代码的两个属性赋值给目标参数。具体来说,前者会将当前源文件的路径绑定到目标参数,后者绑定的则是当前执行代码在源文件中的行数。下面的代码为StartNewActivity扩展方法额外添加了两个参数,并标注了如上两个特性,我们将对应的参数值作为Tag添加到创建的Activity中。
publicstaticclassExtensions
publicstaticActivity? StartNewActivity(
thisActivitySource activitySource,
ActivityKind kind = ActivityKind.Internal,
[ CallerMemberName] stringname = "",
[ CallerFilePath] string? filePath = default,
[ CallerLineNumber] intlineNumber = default)
=> activitySource
.StartActivity(name: name, kind: kind)
?.AddTag( "CallerFilePath", filePath)
?.AddTag( "CallerLineNumber", lineNumber);
再次执行我们的程序,控制台上就会输出添加的两个Tag。
四、”魔法”的背后
其实这四个Attribute背后并没有什么魔法,“语法糖”而已。对于Invoker的三个方法(InvokeAsync、FooAsync和BarAsync)针对StartNewActivity扩展方法的调用。虽然我们并没有指定任何参数,但是编译器在编译后会帮助我们将参数补齐,完整的代码如下所示。
usingSystem.Diagnostics;
usingSystem.Threading.Tasks;
publicclassInvoker
privatereadonlyActivitySource _activitySource;
publicInvoker( ActivitySource activitySource)
_activitySource = Guard.ArgumentNotNull(activitySource, "activitySource");
publicasyncTask InvokeAsync( )
using(_activitySource.StartNewActivity(ActivityKind.Internal, "InvokeAsync", "D:\\Projects\\App\\App\\Program.cs", 40))
awaitTask.Delay( 100);
awaitFooAsync;
privateasyncTask FooAsync( )
using(_activitySource.StartNewActivity(ActivityKind.Internal, "FooAsync", "D:\\Projects\\App\\App\\Program.cs", 49))
awaitTask.Delay( 100);
awaitBarAsync;
privateTask BarAsync( )
using(_activitySource.StartNewActivity(ActivityKind.Internal, "BarAsync", "D:\\Projects\\App\\App\\Program.cs", 58))
returnTask.Delay( 100);
相关文章
3月31日,有博主曝光一段疑似演员宋宁峰出轨“Q女士”的录音,多个相关词条登上热搜榜。 4月1日,宋宁峰发长文承认出轨。他表示,录音中的人确实是自己:...
2026-04-01 6
日本的外交政策正在面临前所未有的考验。两位“邻居”——俄罗斯和中国,似乎达成了一种默契,齐齐对日本发起了攻势,令本就岌岌可危的日本在短时间内倍感压力。...
2026-04-01 4
自从美国和以色列联合对伊朗展开军事行动并成功击毙前最高领袖阿里・哈梅内伊以来,伊朗政权的稳定性面临严峻考验。在这样的背景下,新的最高领袖穆杰塔巴・哈梅...
2026-04-01 4
【文/观察者网 陈思佳】过去几个月,在美国的调解下,俄罗斯和乌克兰已举行多轮谈判,但始终没有取得进展。据俄罗斯塔斯社3月29日报道,俄罗斯总统助理乌沙...
2026-03-30 6
齐鲁晚报·齐鲁壹点 高雅洁 3月27日,青岛市举行“清风寄思 沧海为念”2026年海上追思暨殡仪馆开放日活动,100名海葬逝者家属出海参与海上追思。据...
2026-03-27 4
歼20战斗机 本文综合财新、光明网、中国网、澎湃新闻、上观新闻等 又有军工集团原高管在两院院士名单中“消失”。 近日,有媒体发现,中国科学院官网已经低...
2026-03-27 5
北京时间3月27日消息,2026国际足联系列赛展开争夺,中国男足在悉尼澳大利亚体育场迎战库拉索。上半场比赛场面略显沉闷,韦世豪补时阶段打破僵局,下半场...
2026-03-27 4
3月26日,有网友发布消息称广西脾胃病领域大师黄贵华教授因心梗去世,其社交账号头像已变灰。3月27日,广西中医药大学第一附属医院工作人员向记者证实,消...
2026-03-27 3
发表评论