使用 C# 编译器解释的属性确定调用方信息

使用信息属性,可以获取有关方法调用方的信息。 获取源代码的文件路径、源代码中的行号和调用方的成员名称。 若要获取成员调用方信息,请使用应用于可选参数的属性。 每个可选参数指定默认值。 下表列出了命名空间中 System.Runtime.CompilerServices 定义的调用方信息属性:

特征 DESCRIPTION 类型
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 所调用方法的参数。 使用此方法可以避免 重命名重构 不会更改 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 。 当开发人员使用参数调用Operation时,以下消息存储在以下消息中ArgumentExceptionnull

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;
    }
}

上一个示例使用 nameof 参数的 sequence运算符。 此功能在 C# 11 中可用。 在 C# 11 之前,需要将参数的名称键入为字符串。 可以按如下所示调用此方法:

sample = Enumerable.Range(0, 10).Sample(100);

前面的示例将引发 ArgumentException 其消息为以下文本:

Expression doesn't have enough elements: Enumerable.Range(0, 10) (Parameter 'sequence')

另请参阅