使用 C# 编译器解释的属性确定调用方信息
使用信息属性,可以获取有关方法调用方的信息。 可以获取源代码的文件路径、源代码中的行号和调用方的成员名称。 若要获取成员调用方信息,可以使用应用于可选参数的特性。 每个可选参数指定一个默认值。 下表列出在 System.Runtime.CompilerServices 命名空间中定义的调用方信息特性:
特性 | 说明 | 类型 |
---|---|---|
CallerFilePathAttribute | 包含调用方的源文件的完整路径。 完整路径是编译时的路径。 | String |
CallerLineNumberAttribute | 源文件中调用方法的行号。 | Integer |
CallerMemberNameAttribute | 调用方的方法名称或属性名称。 | String |
CallerArgumentExpressionAttribute | 参数表达式的字符串表示形式。 | String |
此信息可帮助你进行跟踪和调试,以及创建诊断工具。 下面的示例演示如何使用调用方信息特性。 每次调用 TraceMessage
方法时,都会将调用方信息插入到可选参数的变量中。
public void DoProcessing()
{
TraceMessage("Something happened.");
}
public void TraceMessage(string message,
[CallerMemberName] string memberName = "",
[CallerFilePath] string sourceFilePath = "",
[CallerLineNumber] int sourceLineNumber = 0)
{
Trace.WriteLine("message: " + message);
Trace.WriteLine("member name: " + memberName);
Trace.WriteLine("source file path: " + sourceFilePath);
Trace.WriteLine("source line number: " + sourceLineNumber);
}
// Sample Output:
// message: Something happened.
// member name: DoProcessing
// source file path: c:\Visual Studio Projects\CallerInfoCS\CallerInfoCS\Form1.cs
// source line number: 31
为每个可选参数指定显式默认值。 不能将调用方信息特性应用于未指定为可选的参数。 调用方信息特性不会使参数成为可选参数。 相反,它们会在忽略此参数时影响传入的默认值。 在编译时,调用方信息值将作为文本传入中间语言 (IL)。 与异常的 StackTrace 属性的结果不同,这些结果不受模糊处理的影响。 你可显式提供可选参数来控制调用方信息或隐藏调用方信息。
成员名称
可以使用 CallerMemberName
特性来避免将成员名称指定为所调用的方法的 String
参数。 通过使用这种技术,可以避免“重命名重构”不更改 值的问题。 此好处对于以下任务特别有用:
- 使用跟踪和诊断例程。
- 在绑定数据时实现 INotifyPropertyChanged 接口。 此接口允许对象的属性通知绑定控件该属性已更改。 控件可以显示更新后的信息。 如果没有
CallerMemberName
特性,则必须将属性名称指定为文本。
以下图表显示在使用 CallerMemberName
特性时返回的成员名称。
调用发生中 | 成员名称结果 |
---|---|
方法、属性或事件 | 从中发起调用的方法、属性或事件的名称。 |
构造函数 | 字符串“.ctor” |
静态构造函数 | 字符串“.cctor” |
终结器 | 字符串“Finalize” |
用户定义的运算符或转换 | 为成员生成的名称,例如,“op_Addition”。 |
特性构造函数 | 要应用特性的方法或属性的名称。 如果该特性是成员中的任何元素(如参数、返回值或泛型参数),则此结果是与该元素关联的成员的名称。 |
无包含的成员(例如,程序集级别或应用于类型的特性) | 可选参数的默认值。 |
参数表达式
如果想要将表达式作为参数传递,请使用 System.Runtime.CompilerServices.CallerArgumentExpressionAttribute。 诊断库可能需要提供有关传递给参数的表达式的更多详细信息。 通过提供触发诊断的表达式以及参数名称,开发人员可以更加详细地了解触发诊断的条件。 这些额外的信息使修复变得更容易。
以下示例显示如何在参数无效时提供有关参数的详细信息:
public static void ValidateArgument(string parameterName, bool condition, [CallerArgumentExpression("condition")] string? message=null)
{
if (!condition)
{
throw new ArgumentException($"Argument failed validation: <{message}>", parameterName);
}
}
可以调用参数,如以下示例所示:
public void Operation(Action func)
{
Utilities.ValidateArgument(nameof(func), func is not null);
func();
}
用于 condition
的表达式由编译器注入到 message
参数中。 当开发者使用 null
参数调用 Operation
时,以下消息将存储在 ArgumentException
中:
Argument failed validation: <func is not null>
此属性让你能够编写可提供更多详细信息的诊断实用工具。 开发人员可以更快地了解需要进行哪些更改。 也可使用 CallerArgumentExpressionAttribute 来确定哪个表达式用作扩展方法的接收方。 以下方法定期对序列进行采样。 如果序列中包含的元素少于频率,则会报错:
public static IEnumerable<T> Sample<T>(this IEnumerable<T> sequence, int frequency,
[CallerArgumentExpression(nameof(sequence))] string? message = null)
{
if (sequence.Count() < frequency)
throw new ArgumentException($"Expression doesn't have enough elements: {message}", nameof(sequence));
int i = 0;
foreach (T item in sequence)
{
if (i++ % frequency == 0)
yield return item;
}
}
上一个示例为 sequence
参数使用 nameof
运算符。 此功能在 C# 11 中提供。 在 C# 11 之前,需要将参数的名称作为为字符串键入。 可以按如下所示调用此方法:
sample = Enumerable.Range(0, 10).Sample(100);
前面的示例将引发一个 ArgumentException,其消息如以下文本所示:
Expression doesn't have enough elements: Enumerable.Range(0, 10) (Parameter 'sequence')