nameof 運算子

Tip

本文屬於 基礎部分, 適合已經至少懂一種程式語言並正在學習 C# 的開發者。 如果你是程式新手,建議先從 入門 教學開始。 如需完整的運算子參考資訊,請參閱語言參考中的 nameof

來自另一種語言? 其他語言也有類似特徵。 Java 的反射式 Class.getSimpleName()、JavaScript 的 Function.nameObject.keys,Python 的 __name__vars(),以及 Swift 的 #function/#keyPath。 不同於其中大多數,C# 的 nameof 是一種純粹的編譯期構造。 它不使用反射,執行時不分配任何東西,並產生一個常數 string ,並內建在組裝中。

運算子 nameof 會回傳符號的文字識別碼,例如變數、參數、型別、成員或命名空間,作為編譯時 string 常數。 在你本來會把識別碼硬編碼成字串的地方,可以用 nameof:編譯器會驗證符號的存在,重命名則會自動更新結果。

nameof 會傳回的內容

nameof 會求值為其運算元中的 最終 識別碼。 它在編譯時執行,且不需執行時成本。

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

運算元也可以是 限定運算式,也就是使用點運算子從包含它的範圍存取成員的運算式,例如 customer.NameSystem.ConsoleList<int>.Enumerator。 此時,僅捕捉最後一個識別碼: nameof(customer.Name) 返回 "Name",而非 "customer.Name"

論證驗證

經典的用途是在拋出的例外中產生參數名稱。 請傳入 nameof(parameter),不要使用字面字串 "parameter",這樣未來重新命名時,訊息才不會殘留舊名稱:

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

針對 null 檢查,建議使用例外輔助工具。 這些輔助程式,例如 ThrowIfNull,會透過 CallerArgumentExpressionAttribute 自動擷取引數名稱,因此不需要另外使用 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);
    // ...
}

在輔助函式未涵蓋的情況下,請使用 nameofArgumentExceptionArgumentOutOfRangeException(在驗證非單一引數的內容時)以及其他防禦訊息。

屬性變更通知

實作 INotifyPropertyChanged 的類型會提出一個事件,其有效載荷包含變更屬性的名稱。 把名稱硬編碼成字串會產生靜默錯誤,如果屬性被重新命名而字串沒有被重新命名。 建議改用 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));
}

設定器會呼叫 OnPropertyChanged(nameof(Name)),讓屬性名稱和變更通知保持同步。執行此範例以查看事件如何觸發:

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

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

nameof 在屬性引數中

nameof 在屬性參數內有效。 編譯器解析周圍範圍內的識別碼,包括屬性所針對方法的參數。 這是用來指代屬性參數的慣用語方式,例如 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();

如果參數被重新命名, nameof 參數也會透過相同的重構方式更新——屬性不會過時。

合格名稱

對於任何限定表達式, nameof 僅回傳 最後一個 識別碼:

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

如果您需要完整限定名稱,請在 Type 執行個體上使用 Type.FullNamenameof 是用於識別碼,而非路徑。

偏好使用 nameof,而非識別字串

在程式碼中,凡是以名稱參照方法、屬性、參數、型別或命名空間時,請使用 nameof,而不要使用字串常值。 與硬編碼字串相比:

  • 編譯器會驗證該符號的存在。 打字錯誤會變成建置錯誤,而不是靜默的執行錯誤。
  • 重命名會自動更新結果。 硬編碼的字串會漂移不同步。
  • 結果是一個編譯時常數,因此沒有執行時間成本。

此建議適用於日誌訊息、例外參數、屬性參數、屬性變更通知,以及與成員名稱綁定的序列化鍵常數。

參見