Оператор nameof

Подсказка

Эта статья является частью раздела "Основы" для разработчиков , которые уже знают хотя бы один язык программирования и учат C#. Если вы не знакомы с программированием, сначала начните с учебных пособий по началу работы. Полное справочное описание оператора см. в разделе nameof справочника по языку.

Вы пришли из другого языка? Другие языки имеют аналогичные функции. рефлексивный Class.getSimpleName() в Java, Function.name и Object.keys в JavaScript, __name__ и vars() в Python и #function/#keyPath в Swift. В отличие от большинства из них, nameof в C# является исключительно компиляционной конструкцией. Не использует рефлексию, ничего не выделяет во время выполнения и создает константу string, встроенную в сборку.

Оператор nameof возвращает текстовый идентификатор символа, например переменную, параметр, тип, член или пространство имен в виде константы во время string компиляции. Везде, где иначе пришлось бы жёстко задавать идентификатор строкой, используйте nameof: компилятор проверяет, что символ существует, а рефакторинг переименования автоматически обновляет результат.

Что nameof возвращается

nameof принимает значение последнего идентификатора в своём операнде. Это происходит на этапе компиляции и не влечёт накладных расходов во время выполнения.

// nameof produces the textual identifier of a symbol at compile time.
Console.WriteLine(nameof(Customer));        // Customer
Console.WriteLine(nameof(Customer.Name));   // Name

var customer = new Customer("Ada");
Console.WriteLine(nameof(customer));        // customer
Console.WriteLine(nameof(customer.Name));   // Name

Операнды также могут быть квалифицированным выражением, который использует оператор dot для перехода из содержащей области в элемент, например customer.Name, System.Consoleили List<int>.Enumerator. В этом случае фиксируется только последний идентификатор: nameof(customer.Name) возвращается "Name", а не "customer.Name".

Проверка аргументов

Классический вариант использования заключается в указании имени параметра в выбрасываемом исключении. Передайте nameof(parameter) вместо строкового литерала "parameter", чтобы будущее переименование не сделало сообщение недостоверным:

try
{
    Greet("");
}
catch (ArgumentException ex)
{
    // The exception's ParamName is the literal "name", produced by nameof at compile time.
    Console.WriteLine($"{ex.ParamName}: {ex.Message}");
}

static void Greet(string name)
{
    if (string.IsNullOrWhiteSpace(name))
    {
        throw new ArgumentException("Name must be non-empty.", nameof(name));
    }
    Console.WriteLine($"Hello, {name}!");
}

В частности для проверок на null предпочтительно использовать вспомогательные методы для создания исключений. Эти вспомогательные средства, такие как ThrowIfNull, автоматически захватывают имя аргумента с помощью CallerArgumentExpressionAttribute, поэтому отдельный nameof не требуется:

// ArgumentNullException.ThrowIfNull captures the argument's name automatically
// through [CallerArgumentExpression], so a separate nameof isn't required for
// the null check. Use nameof for cases the helpers don't cover.
Customer? maybeCustomer = null;

try
{
    Save(maybeCustomer);
}
catch (ArgumentNullException ex)
{
    Console.WriteLine(ex.ParamName);   // customer
}

static void Save(Customer? customer)
{
    ArgumentNullException.ThrowIfNull(customer);
    // ...
}

Используйте nameof в случаях, когда вспомогательные средства не охватывают: ArgumentExceptionArgumentOutOfRangeException(при проверке чего-либо другого, кроме одного аргумента), а также других сообщений защиты.

Уведомления об изменении свойств

Типы, реализующие INotifyPropertyChanged, вызывают событие, полезная нагрузка которого включает имя измененного свойства. Жёсткое задание имени в виде строки создаёт скрытую ошибку, если свойство переименуют, а строку — нет. Используйте nameof вместо этого:

public sealed class Person : INotifyPropertyChanged
{
    private string _name = "";

    public string Name
    {
        get => _name;
        set
        {
            if (_name == value) return;
            _name = value;
            // nameof keeps the property name and the change notification in sync.
            // Renaming the property automatically updates this argument.
            OnPropertyChanged(nameof(Name));
        }
    }

    public event PropertyChangedEventHandler? PropertyChanged;

    private void OnPropertyChanged([CallerMemberName] string? propertyName = null) =>
        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}

Сеттер вызывает OnPropertyChanged(nameof(Name)), поэтому имя свойства и уведомление об изменении остаются синхронизированными. Запустите пример, чтобы увидеть, как срабатывают события:

var person = new Person();
person.PropertyChanged += (_, e) => Console.WriteLine($"changed: {e.PropertyName}");

person.Name = "Ada";    // changed: Name
person.Name = "Grace";  // changed: Name

nameof в аргументах атрибутов

nameof допустим в аргументах атрибутов. Компилятор разрешает идентификаторы в окружающей области, включая параметры метода, целевые объекты атрибута. Это идиоматический способ ссылаться на параметр из атрибута, NotNullIfNotNullAttributeнапример:

// nameof works inside attribute arguments. The compiler resolves the
// identifier even when the attribute targets a method or its parameters.
Console.WriteLine(NormalizeOrNull("  hi  ") ?? "<null>");   // hi
Console.WriteLine(NormalizeOrNull(null) ?? "<null>");        // <null>

[return: NotNullIfNotNull(nameof(input))]
static string? NormalizeOrNull(string? input) => input?.Trim();

Если параметр переименовывается, аргумент nameof обновляется при том же рефакторинге — атрибут не может устареть.

Квалифицированные имена

Для любого квалифицированного выражения nameof возвращает только последний идентификатор:

// For a qualified expression, nameof returns only the final identifier.
Console.WriteLine(nameof(System.Collections.Generic.List<int>)); // List
Console.WriteLine(nameof(Customer.Name));                        // Name

Если вам нужно полное имя, используйте Type.FullName для экземпляра Type. nameof предназначен для идентификаторов, а не путей.

Используйте nameof вместо строковых идентификаторов

В любом месте, где вы ссылаетесь на метод, свойство, параметр, тип или пространство имен по имени в коде, используйте nameof вместо строкового литерала. По сравнению с жестко закодированной строкой:

  • Компилятор проверяет, существует ли символ. Опечатка приводит к ошибке сборки, а не к незаметному багу во время выполнения.
  • Рефакторинг переименования автоматически обновляет результат. Жестко закодированные строки не синхронизируются.
  • Результат представляет собой константу на этапе компиляции, поэтому во время выполнения дополнительные затраты отсутствуют.

Эта рекомендация применяется к сообщениям журнала, аргументам исключений, аргументам атрибутов, уведомлениям об изменении свойств и константам ключей сериализации, привязанным к имени члена.

См. также