O operador nameof

Gorjeta

Este artigo faz parte da seção Conceitos Básicos para desenvolvedores que já conhecem pelo menos uma linguagem de programação e estão aprendendo C#. Se você é novo em programação, comece com os tutoriais Comece agora. Para obter a referência completa do operador, consulte nameof na referência da linguagem.

Vindo de outro idioma? Outros idiomas têm recursos semelhantes. Class.getSimpleName() reflexivo do Java, Function.name e Object.keys do JavaScript, __name__ e vars() do Python, e #function/#keyPath do Swift. Ao contrário da maioria deles, o C#'s nameof é um constructo de tempo de compilação puro. Não usa reflexão, não aloca nada em tempo de execução e produz uma constante string incorporada ao assembly.

O nameof operador retorna o identificador textual de um símbolo, como uma variável, parâmetro, tipo, membro ou namespace, como uma constante de tempo string de compilação. Sempre que você precisaria codificar manualmente um identificador como string, use nameof: o compilador verifica se o símbolo existe, e as refatorações de renomeação atualizam o resultado automaticamente.

O que nameof retorna

nameof avalia para o identificador final em seu operando. Ele é executado em tempo de compilação e não tem nenhum custo de runtime.

// 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

O operando também pode ser uma expressão qualificada, isto é, uma expressão que usa o operador de ponto para navegar de um escopo contêiner para um membro, como customer.Name, System.Console ou List<int>.Enumerator. Nesse caso, somente o último identificador é capturado: nameof(customer.Name) retorna "Name", não "customer.Name".

Validação de argumento

O uso clássico consiste em produzir o nome do parâmetro em uma exceção lançada. Passe nameof(parameter) em vez da cadeia de caracteres literal "parameter" para que uma renomeação futura não possa deixar a mensagem mentindo:

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}!");
}

Para verificações nulas especificamente, prefira usar auxiliares de exceção. Esses auxiliares, como ThrowIfNull, capturam automaticamente o nome do argumento por meio de CallerArgumentExpressionAttribute, portanto, um nameof separado não é necessário:

// 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);
    // ...
}

Use nameof para os casos que os auxiliares não cobrem: ArgumentException, ArgumentOutOfRangeException (ao validar algo diferente de um único argumento) e outras mensagens de proteção.

Notificações de alteração de propriedade

Tipos que implementam INotifyPropertyChanged geram um evento cujo conteúdo inclui o nome da propriedade alterada. Codificar o nome como uma cadeia de caracteres criará um bug silencioso se a propriedade for renomeada e a cadeia de caracteres não for. Em vez disso, use 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));
}

O setter chama OnPropertyChanged(nameof(Name)) para que o nome da propriedade e a notificação de alteração permaneçam em sincronia. Execute o exemplo para ver os eventos serem acionados:

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

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

nameof em argumentos de atributo

nameof é válido dentro de argumentos de atributo. O compilador resolve os identificadores no escopo circundante, incluindo os parâmetros do método ao qual o atributo se aplica. Essa é a maneira idiomática de se referir a um parâmetro de um atributo como 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();

Se o parâmetro for renomeado, o nameof argumento será atualizado pela mesma refatoração . O atributo não pode ficar desatualizado.

Nomes qualificados

Para qualquer expressão qualificada, nameof retorna apenas o último identificador:

// 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

Se você precisar do nome totalmente qualificado, use Type.FullName em uma instância de Type. nameof é para identificadores, não para caminhos.

Preferir nameof o identificador de cadeias de caracteres

Sempre que você se referir pelo nome, no código, a um método, propriedade, parâmetro, tipo ou namespace, use nameof em vez de um literal de cadeia de caracteres. Em comparação com uma cadeia de caracteres codificada:

  • O compilador verifica se o símbolo existe. Um erro de digitação se torna um erro de build, não um bug silencioso em runtime.
  • As refatorações de renomeação atualizam o resultado automaticamente. Cadeias de caracteres codificadas saem da sincronização.
  • O resultado é uma constante de tempo de compilação, portanto, não há nenhum custo de runtime.

Essa recomendação se aplica a mensagens de registro em log, argumentos de exceção, argumentos de atributo, notificações de alteração de propriedade e constantes de chave de serialização vinculadas a um nome de membro.

Consulte também