Определение сведений о вызывающем объекте с помощью атрибутов, интерпретируемых компилятором 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 вызываемому методу. Этот прием позволяет избежать проблемы, заключающейся в том, что операция рефакторинга и переименования не изменяет значений 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 с аргументом null, следующее сообщение сохраняется в 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')

См. также