Fenner
A linguagem

nameof — o nome como texto verificado

nameof(X) vira o nome de X como constante de texto; renomear sem atualizar quebra a compilação em vez de deixar texto velho.

O que é e por que existe#

Mensagens de erro, nomes de coluna e logs costumam repetir à mão o nome de um campo ou parâmetro. Quando o campo é renomeado, o texto fica desatualizado e ninguém percebe. nameof(X) devolve, em compilação, o nome de X como constante de texto, e o compilador confere que X existe. Renomeou sem atualizar? Erro de compilação, e não um texto mentiroso.

Fenner
if AValue < 0 then
  WriteLn('erro: ', nameof(AValue), ' não pode ser negativo');

X não é avaliado e não gera código.

O que pode ser X#

Tipo (inclusive embutido e apelido), variável, parâmetro, campo, property, método, rotina, constante, valor de enum, módulo (System.Remainder dá Remainder), tipo genérico (TList<Int32> dá TList), método genérico, e caminhos qualificados como TFoo.Bar, Obj.Campo, Self.FCampo, Unit.Simbolo. Dos qualificados vale o último componente.

rc-nameof.fxp
program NameOf;
// Demo 34: nameof - o nome de um símbolo como texto, em tempo de compilação.
//
// Escrever 'Idade' à mão num texto é frágil: se renomear o campo, o texto
// fica desatualizado. nameof(Idade) é verificado pelo compilador (nome
// inexistente é erro) e acompanha as renomeações. O argumento NÃO é executado.

type
  TColor = (Red, Green, Blue);

  TCustomer = class
  public
    Age: Int32;
    function Validate(AValue: Int32): Bool8;
  end class;

function TCustomer.Validate(AValue: Int32): Bool8;
begin
  if AValue < 0 then
  begin
    // mensagem de erro que cita o parâmetro sem escrevê-lo "no braço"
    WriteLn('  erro: ', nameof(AValue), ' não pode ser negativo');
    Exit(False);
  end if;
  Result := True;
end function;

const
  cTypeName = nameof(TCustomer);        // vale até em constantes

var
  LCustomer: TCustomer;
begin
  WriteLn(nameof(TCustomer));
  WriteLn(nameof(TCustomer.Age));        // caminho qualificado
  WriteLn(nameof(TCustomer.Validate));   // método
  WriteLn(nameof(Int32));                // tipo embutido
  WriteLn(nameof(TColor.Green));         // valor de enumeração
  WriteLn(cTypeName);

  LCustomer := TCustomer.Create;
  WriteLn('validou -5? ', LCustomer.Validate(-5));
  // nameof(LCustomer.Age) não lê o campo: só devolve o nome
  WriteLn('variável: ', nameof(LCustomer), ' campo: ', nameof(LCustomer.Age));
end.
Saída
TCustomer
Age
Validate
Int32
Green
TCustomer
  erro: AValue não pode ser negativo
validou -5? FALSE
variável: LCustomer campo: Age

Demo 34. Tipo, campo, método, tipo embutido, valor de enum, constante e variável; nada é executado.

O resultado é um literal de texto comum: converte para UString/LString conforme o contexto, concatena, entra em const, em argumento de atributo ([Alias(nameof(TFoo))]), em static_assert e em interpolação. No {$if}, vem já comparado com um texto: {$if nameof(TFoo.Campo) = 'Campo'}.

Erros típicos#

  • Nome inexistente: FE2070 ("nameof: "Totall" não está declarado"). Outros da família: FE2071 a FE2078.
  • Não valem chamada, índice, literal nem operador (FE2073/FE2075).
  • nameof é contextual e só em minúsculas: só é o operador quando vem seguido de ( e nenhum símbolo visível tem esse nome. NameOf e NAMEOF são identificadores comuns.
  • Dentro de um genérico, nameof(T) dá o nome do tipo da instância (Int32), porque o corpo é reescrito com o argumento antes de ser lido.

Ferramentas#

O argumento conta como uso do símbolo: o fc --symbols (achar referências, renomear) e o aviso de "declarada e nunca usada" o enxergam. Por isso o renomear do editor atualiza o nameof junto.

Veja também#