O operador nameof

Tip

Este artigo faz parte da secção Fundamentos para programadores que já conhecem pelo menos uma linguagem de programação e estão a aprender C#. Se és novo na programação, começa primeiro pelos tutoriais para começar . Para a referência completa do operador, veja nameof na referência linguística.

Vem de outra língua? Outras línguas têm características semelhantes. a reflexão de Java Class.getSimpleName(), Function.name e Object.keys de JavaScript, __name__ e vars() do Python, e #function/#keyPath de Swift. Ao contrário da maioria delas, o nameof de C# é uma construção puramente de tempo de compilação. Não usa reflexão, não aloca nada em tempo de execução e produz uma constante string incorporada na assemblagem.

O operador nameof devolve o identificador textual de um símbolo, como uma variável, parâmetro, tipo, membro ou espaço de nomes, como uma constante de tempo string de compilação. Sempre que, de outro modo, escreveria diretamente um identificador como uma cadeia de caracteres, use nameof: o compilador verifica que o símbolo existe, e as refatorações de mudança de nome atualizam automaticamente o resultado.

O que nameof devolve

nameof corresponde ao identificador final do seu operando. Executa-se em tempo de compilação e não acarreta qualquer custo em tempo de execução.

// 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 pode também ser uma expressão qualificada, que utiliza o operador ponto para navegar de um escopo contendo para um membro, como customer.Name, System.Console, ou List<int>.Enumerator. Nesse caso, apenas o último identificador é capturado: nameof(customer.Name) devolve "Name", não "customer.Name".

Validação de argumentos

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

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 assistentes de exceção. Estes auxiliares, como ThrowIfNull, capturam automaticamente o nome do argumento através de CallerArgumentExpressionAttribute, pelo que não é necessário um separado 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);
    // ...
}

Utilize nameof nos casos que as funções auxiliares não abrangem: ArgumentException, ArgumentOutOfRangeException (ao validar algo que não seja um único argumento) e outras mensagens de validação.

Notificações de alteração de propriedade

Os tipos que implementam INotifyPropertyChanged geram um evento cujo payload inclui o nome da propriedade alterada. Definir o nome de forma fixa como texto cria um erro silencioso se a propriedade for renomeada e o texto não o for. Use nameof em vez disso:

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 mudança se mantenham sincronizados. 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 dos argumentos de atributos. O compilador resolve identificadores no âmbito circundante, incluindo os parâmetros do método que o atributo direciona. Esta é a forma idiomática de se referir a um parâmetro a partir 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 é 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 precisar do nome totalmente qualificado, use Type.FullName num Type exemplo. nameof é para identificadores, não para caminhos.

Prefira nameof a cadeias de identificadores

Sempre que se referir pelo nome, no código, a um método, propriedade, parâmetro, tipo ou namespace, utilize nameof em vez de uma cadeia literal. Comparado com uma cadeia codificada fixamente:

  • O compilador verifica que o símbolo existe. Um erro de digitação torna-se um erro de compilação, não um bug silencioso em tempo de execução.
  • As renomeações atualizam o resultado automaticamente. As cordas codificadas diretamente ficam fora de sincronia.
  • O resultado é uma constante de tempo de compilação, por isso não há custo em tempo de execução.

Esta recomendação aplica-se ao registo de mensagens, argumentos de exceção, argumentos de atributo, notificações de alteração de propriedade e constantes de chave de serialização ligadas a um nome de membro.

Consulte também