Operator nameof

Wskazówka

Ten artykuł jest częścią sekcji Podstawy dla deweloperów, którzy już znają co najmniej jeden język programowania i uczą się języka C#. Jeśli dopiero zaczynasz programować, najpierw zacznij od samouczków Wprowadzenie . Pełny opis operatorów znajduje się w nameof w dokumentacji języka.

Pochodzi z innego języka? Inne języki mają podobne funkcje. refleksyjne Class.getSimpleName() języka Java, Function.name i Object.keys języka JavaScript, __name__ i vars() języka Python oraz #function/#keyPath języka Swift. W przeciwieństwie do większości z nich, nameof w C# jest konstrukcją dostępną wyłącznie na etapie kompilacji. Nie używa refleksji, niczego nie alokuje w czasie wykonywania i generuje stałą string, która jest wkompilowana do asemblażu.

Operator nameof zwraca tekstowy identyfikator symbolu, taki jak zmienna, parametr, typ, składowa lub przestrzeń nazw, jako stała czasu string kompilacji. W dowolnym miejscu, w którym identyfikator jest w inny sposób zakodowany jako ciąg, użyj polecenia nameof: kompilator sprawdza, czy symbol istnieje, a refaktoryzacje zmiany nazw aktualizują wynik automatycznie.

Co nameof zwraca

nameof przyjmuje wartość ostatecznego identyfikatora w swoim operandzie. Jest uruchamiany w czasie kompilacji i nie ma kosztów środowiska uruchomieniowego.

// 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 może być również wyrażenie kwalifikowane, czyli takie, które wykorzystuje operator kropki do przechodzenia z zakresu obejmującego do składowej, na przykład customer.Name, System.Console lub List<int>.Enumerator. W takim przypadku przechwytywany jest tylko ostatni identyfikator: nameof(customer.Name) zwraca "Name", a nie "customer.Name".

Weryfikacja argumentów

Klasycznym zastosowaniem jest umieszczenie nazwy parametru w zgłaszanym wyjątku. Przekaż nameof(parameter) zamiast literału ciągu "parameter", aby przyszła zmiana nazwy nie pozostawiła nieaktualnego komunikatu:

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

W przypadku sprawdzania wartości null preferuj używanie pomocników wyjątków. Te pomocniki, takie jak ThrowIfNull, automatycznie pobierają nazwę argumentu za pomocą CallerArgumentExpressionAttribute, więc oddzielne nameof nie jest potrzebne:

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

Użyj nameof w przypadkach, których metody pomocnicze nie obejmują: ArgumentException, ArgumentOutOfRangeException (przy weryfikowaniu czegoś innego niż pojedynczy argument) oraz innych komunikatów strażników.

Powiadomienia o zmianie właściwości

Typy implementujące INotifyPropertyChanged wywołują zdarzenie, którego dane zawierają nazwę zmienionej właściwości. Zakodowanie nazwy na stałe w postaci ciągu znaków powoduje trudny do wykrycia błąd, jeśli nazwa właściwości zostanie zmieniona, a odpowiadający jej ciąg znaków nie. Użyj nameof zamiast tego:

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

Metoda ustawiająca wywołuje metodę OnPropertyChanged(nameof(Name)), aby nazwa właściwości i powiadomienie o zmianie pozostają zsynchronizowane. Uruchom przykład, aby zobaczyć, jak są wywoływane zdarzenia:

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

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

nameof w argumentach atrybutów

nameof jest prawidłowe w argumentach atrybutu. Kompilator rozpoznaje identyfikatory w otaczającym zakresie, w tym parametry metody docelowej atrybutu. Jest to idiomatyczny sposób odwoływania się do parametru z atrybutu, takiego jak 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();

Jeśli nazwa parametru zostanie zmieniona, nameof argument zostanie zaktualizowany przez tę samą refaktoryzację — atrybut nie może być nieaktualny.

Kwalifikowane nazwy

W przypadku dowolnego wyrażenia kwalifikowanego nameof zwraca tylko ostatni identyfikator:

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

Jeśli potrzebujesz w pełni kwalifikowanej nazwy, użyj jej Type.FullName w wystąpieniu Type . nameof jest przeznaczony dla identyfikatorów, a nie ścieżek.

Preferuj nameof zamiast ciągów identyfikatorów

Wszędzie tam, gdzie w kodzie odwołujesz się po nazwie do metody, właściwości, parametru, typu lub przestrzeni nazw, użyj nameof zamiast literału tekstowego. W porównaniu z zakodowanym ciągiem:

  • Kompilator sprawdza, czy symbol istnieje. Literówka staje się błędem kompilacji, a nie dyskretnym błędem środowiska uruchomieniowego.
  • Refaktoryzacje typu Zmień nazwę automatycznie aktualizują wynik. Ciągi znaków zakodowane na stałe przestają być zsynchronizowane.
  • Wynik jest stałą czasu kompilacji, więc nie ma kosztów środowiska uruchomieniowego.

To zalecenie dotyczy komunikatów rejestrowania, argumentów wyjątków, argumentów atrybutów, powiadomień o zmianie właściwości i stałych kluczy serializacji powiązanych z nazwą elementu członkowskiego.

Zobacz także