Tip
本文屬於 基礎部分, 適合已經至少懂一種程式語言並正在學習 C# 的開發者。 如果你是程式新手,建議先從 入門 教學開始。 如需完整的運算子參考資訊,請參閱語言參考中的 nameof。
來自另一種語言? 其他語言也有類似特徵。 Java 的反射式 Class.getSimpleName()、JavaScript 的 Function.name 和 Object.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.Name、System.Console 或 List<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);
// ...
}
在輔助函式未涵蓋的情況下,請使用 nameof:ArgumentException、ArgumentOutOfRangeException(在驗證非單一引數的內容時)以及其他防禦訊息。
屬性變更通知
實作 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.FullName。
nameof 是用於識別碼,而非路徑。
偏好使用 nameof,而非識別字串
在程式碼中,凡是以名稱參照方法、屬性、參數、型別或命名空間時,請使用 nameof,而不要使用字串常值。 與硬編碼字串相比:
- 編譯器會驗證該符號的存在。 打字錯誤會變成建置錯誤,而不是靜默的執行錯誤。
- 重命名會自動更新結果。 硬編碼的字串會漂移不同步。
- 結果是一個編譯時常數,因此沒有執行時間成本。
此建議適用於日誌訊息、例外參數、屬性參數、屬性變更通知,以及與成員名稱綁定的序列化鍵常數。