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));
}
setter 调用 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.FullName实例使用Type。
nameof 用于标识符,而不是路径。
首选 nameof,而非标识符字符串
在代码中按名称引用方法、属性、参数、类型或命名空间的任何位置,都使用 nameof 而不是字符串文本。 与硬编码字符串相比:
- 编译器验证符号是否存在。 拼写错误会变成构建错误,而不是悄无声息的运行时错误。
- 重命名重构会自动更新结果。 硬编码字符串会逐渐不同步。
- 结果是编译时常量,因此不会产生运行时成本。
此建议适用于记录消息、异常参数、属性参数、属性更改通知和绑定到成员名称的序列化密钥常量。