Operátor nameof

Tip

Tento článek je součástí části Základy pro vývojáře, kteří už znají aspoň jeden programovací jazyk a učí se jazyk C#. Pokud s programováním teprve začínáte, začněte nejprve kurzy Začínáme . Úplnou referenci operátorů najdete v referenci jazyka nameof.

Pochází z jiného jazyka? Jiné jazyky mají podobné funkce. reflexivní Class.getSimpleName() v Javě, Function.name a Object.keys v JavaScriptu, __name__ a vars() v Pythonu a #function/#keyPath ve Swiftu. Na rozdíl od většiny z nich je nameof v jazyce C# čistě konstruktem existujícím pouze v době kompilace. Nepoužívá žádnou reflexi, za běhu nic nealokuje a vytváří konstantu string, která je vložena přímo do sestavení.

Operátor nameof vrátí textový identifikátor symbolu, například proměnnou, parametr, typ, člen nebo obor názvů, jako konstantu kompilačního času string . Všude, kde byste jinak napevno zapisovali identifikátor v podobě řetězce, použijte nameof: kompilátor ověří, že symbol existuje, a přejmenování v rámci refaktoringu výsledek automaticky aktualizuje.

Co vrací nameof

nameof se vyhodnotí jako konečný identifikátor v jeho operandu. Spouští se v době kompilace a nemá žádné náklady za běhu.

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

Operandem může být také kvalifikovaný výraz, který používá operátor tečky k přechodu z nadřazeného oboru na člena, například customer.Name, System.Console nebo List<int>.Enumerator. V takovém případě je zaznamenán pouze poslední identifikátor: nameof(customer.Name) vrátí "Name", nikoli "customer.Name".

Ověření argumentu

Klasické použití vytváří název parametru v vyvolané výjimce. Předejte nameof(parameter) místo doslovného řetězce "parameter", aby při budoucím přejmenování nezůstala zpráva zastaralá:

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

Pro kontrolu hodnoty null zejména upřednostněte pomocné metody pro výjimky. Tyto pomocné metody, například ThrowIfNull, automaticky zachycují název argumentu prostřednictvím CallerArgumentExpressionAttribute, takže není potřeba samostatný 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);
    // ...
}

Použijte nameof v případech, které pomocné metody nepokrývají: ArgumentException, ArgumentOutOfRangeException (při ověřování něčeho jiného než jednoho argumentu) a další zprávy ochranných kontrol.

Oznámení o změnách vlastností

Typy, které implementují INotifyPropertyChanged, vyvolají událost, jejíž datová část obsahuje název změněné vlastnosti. Pevné zakódování názvu jako řetězec vytvoří tichou chybu, pokud je vlastnost přejmenována a řetězec není. Místo toho použijte 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));
}

Setter volá OnPropertyChanged(nameof(Name)), aby název vlastnosti a oznámení o změně zůstaly synchronizované. Spusťte příklad a uvidíte, jak se události vyvolají:

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

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

nameof v argumentech atributů

nameof je platný uvnitř argumentů atributu. Kompilátor vyhodnocuje identifikátory v okolním rozsahu platnosti, včetně parametrů metody, na kterou atribut cílí. Jedná se o idiomaticický způsob, jak odkazovat na parametr z atributu, například 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();

Pokud se parametr přejmenuje, argument nameof se v rámci stejného refaktoringu aktualizuje – atribut tak nemůže zastarat.

Kvalifikované názvy

U libovolného kvalifikovaného výrazu nameof vrátí pouze poslední identifikátor:

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

Pokud potřebujete plně kvalifikovaný název, použijte Type.FullName u instance Type. nameof je určen pro identifikátory, nikoli cesty.

Upřednostňujte nameof před řetězci identifikátorů

Kdykoli v kódu odkazujete názvem na metodu, vlastnost, parametr, typ nebo obor názvů, použijte nameof místo řetězcového literálu. Ve srovnání s pevně zakódovaným řetězcem:

  • Kompilátor ověří, že symbol existuje. Překlep se stane chybou sestavení, nikoli tichou chybou modulu runtime.
  • Refaktoringy přejmenování aktualizují výsledek automaticky. Pevně zakódované řetězce se nesynchronizují.
  • Výsledkem je konstanta doby kompilace, takže za běhu nejsou žádné náklady.

Toto doporučení platí pro protokolování zpráv, argumentů výjimek, argumentů atributů, oznámení o změnách vlastností a konstanty serializačního klíče svázané s názvem člena.

Viz také