nameof的目的是什么?

370

版本6.0增加了一个新功能nameof,但我不理解它的目的,因为它只是在编译时将变量名更改为字符串。

我以为在使用<T>时可能有一些用途,但当我尝试nameof(T)时,它只会打印出T而不是所使用的类型。

对此有什么想法吗?


3
请参阅 https://msdn.microsoft.com/library/dn986596.aspx。 - Corak
31
以前没有获得那个“T”的方法。以前有一种方法可以获得使用过的类型。 - Jon Hanna
21
nameof 中重构/更改名称时,这个方法确实很有用。它还有助于防止拼写错误。 - bvj
nameof在nunit 3的TestCaseData中非常方便。文档展示了一个通过字符串来表示方法名的方式 [Test, TestCaseSource(typeof(MyDataClass), "TestCases")],可以被替换为 [Test, TestCaseSource(typeof(MyDataClass), nameof(MyDataClass.TestCases))]。更加优雅。 - Jeremy
4
nameof 的官方文档已经迁移到此处:https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/keywords/nameof,它还列出了关键用例,这些用例可以作为相当不错的答案回答相关问题。 - markus s
17个回答

423

如果需要重复使用属性名称,例如基于属性名称抛出异常或处理PropertyChanged事件等情况,该怎么办?有许多情况下你需要获取属性的名称。

以此示例为例:

switch (e.PropertyName)
{
    case nameof(SomeProperty):
    { break; }

    // opposed to
    case "SomeOtherProperty":
    { break; }
}

在第一种情况下,如果您不更改属性定义和nameof(SomeProperty)表达式,则重命名SomeProperty将导致编译错误。在第二种情况下,重命名SomeOtherProperty或更改"SomeOtherProperty"字符串将导致运行时行为默默中断,构建时没有错误或警告。
这是保持代码编译和无错(有点)的非常有用的方法。
Eric Lippert的一篇非常好的文章解释了为什么infoof未能成为标准,而nameof成为了标准)

2
我理解您的意思,只是想补充一下,Resharper在重构名称时会更改字符串,不确定VS是否具有类似的功能。 - Ash Burlaczenko
12
有。但是无论是Resharper还是VS都不能在项目中工作,而这个可以。事实上,这是更好的解决方案。 - Patrick Hofman
61
另一个常见的用例是在MVC中使用nameof和动作名称进行路由,而不是使用硬编码的字符串。 - RJ Cuthbertson
2
@sotn 我不确定我理解你的问题。 没有什么可以阻止您像 public class MyController { public ActionResult Index() { return View(nameof(Index)); } } 那样使用它 - 并且您可以在非静态成员上使用 nameof(例如,您可以使用上面的类调用 nameof(MyController.Index),它将发出 "Index")。 请查看 https://msdn.microsoft.com/zh-cn/library/dn986596.aspx?f=255&MSPPError=-2147217396 上的示例。 - RJ Cuthbertson
2
我不明白为什么那很特别。变量名总是相同的,对吧?无论你是否有实例,变量名都不会改变。@sotn - Patrick Hofman
显示剩余7条评论

221

对于ArgumentException及其派生类,它非常有用:

public string DoSomething(string input) 
{
    if(input == null) 
    {
        throw new ArgumentNullException(nameof(input));
    }
    ...

现在,如果有人重构input参数的名称,异常也会随之更新。这对于某些情况非常有用,以前可能需要使用反射来获取属性或参数的名称。在您的例子中,nameof(T)获取类型参数的名称-这也很有用:
throw new ArgumentException(nameof(T), $"Type {typeof(T)} does not support this method.");

另一个使用 nameof 的场景是枚举类型 - 如果你想要获取枚举类型的字符串名称,通常会使用 .ToString()
enum MyEnum { ... FooBar = 7 ... }

Console.WriteLine(MyEnum.FooBar.ToString());

> "FooBar"

实际上,这种方式相对较慢,因为.Net在运行时保留了枚举值(即7),并查找名称。

相反,使用nameof

Console.WriteLine(nameof(MyEnum.FooBar))

> "FooBar"

现在,在编译时,.Net将枚举名称替换为字符串。


另一个用途是像INotifyPropertyChanged和日志记录一样的东西 - 在这两种情况下,您希望调用的成员名称传递给另一个方法:

// Property with notify of change
public int Foo
{
    get { return this.foo; }
    set
    {
        this.foo = value;
        PropertyChanged(this, new PropertyChangedEventArgs(nameof(this.Foo));
    }
}

或者...

// Write a log, audit or trace for the method called
void DoSomething(... params ...)
{
    Log(nameof(DoSomething), "Message....");
}

10
你们添加了另一个很酷的功能:字符串插值! - Patrick Hofman
1
@PatrickHofman 和 typeof(T),这是另一种在类似情况下非常有用的编译时语法糖 :-) - Keith
1
我感觉缺少的是类似于 nameofthismethod 的东西。你可以使用 Log.Error($"Error in {nameof(DoSomething)}..."),但如果你将其复制粘贴到其他方法中,你就不会注意到它仍然在引用 DoSomething。因此,虽然它在使用局部变量或参数时完美运行,但方法名是一个问题。 - Tim Schmelter
3
你知道如果nameOf存在,是否会使用[DisplayName]属性?对于枚举示例,我经常在MVC项目中使用[DisplayName] - Luke T O'Brien
2
@AaronLS 是的,这是相当专业化的东西,不是你经常使用的。然而,throw new是另一种反模式 - 我发现初级开发人员过度使用catch是一个常见问题,因为它感觉像修复问题(但大多数情况下只是隐藏了问题)。 - Keith
显示剩余3条评论

37

另一个使用 C# 6.0 的 nameof 特性变得方便的案例 - 考虑像 Dapper 这样的库,它使得从数据库检索数据变得更容易。尽管这是一个很棒的库,但您需要在查询中硬编码属性/字段名称。这意味着如果您决定重命名属性/字段,则很有可能会忘记更新查询以使用新的字段名称。使用字符串插值和 nameof 特性,代码变得更容易维护且类型安全。

来自链接中给出的示例

没有 nameof

var dog = connection.Query<Dog>(
    "select Age = @Age, Id = @Id",
    new {Age = (int?) null, Id = guid});

使用名称

var dog = connection.Query<Dog>(
    $"select {nameof(Dog.Age)} = @Age, {nameof(Dog.Id)} = @Id",
    new {Age = (int?) null, Id = guid});

11
我喜欢使用Dapper,也很喜欢字符串插值的方式,但我认为这种写法看起来非常丑陋。与如此丑陋的查询相比,通过重命名列而导致查询中断的风险似乎非常小。乍一看,我更喜欢编写EF LINQ查询,或者遵循类似于[表名].[列名]的约定,当需要时可以轻松查找/替换我的查询。 - drizin
@drizin 我使用Dapper FluentMap来防止这样的查询(也为了关注点分离)。 - mamuesstack

30

你的问题已经表达了目的。你必须看到这可能对记录日志或抛出异常有用。

例如:

public void DoStuff(object input)
{
    if (input == null)
    {
        throw new ArgumentNullException(nameof(input));
    }
}

这很好。如果我更改变量的名称,代码将会崩溃,而不是返回带有错误信息的异常


当然,用途不仅限于这种简单情况。您可以在需要编写变量或属性名称的任何位置使用 nameof

在考虑各种绑定和反射情况时,使用方法多种多样。这是一个将运行时错误转化为编译时错误的绝佳方式。


对于记录日志,我可以自己写变量的名称,这样会更短。 - atikot
8
但是,如果您重命名变量,编译器将不会注意到字符串不再匹配。 - O. R. Mapper
1
我实际上使用的是 ReSharper,它会考虑到这一点,但我理解你的观点。 - atikot
4
@atikot,我也希望如此,但是Resharper只产生警告而不是编译器错误。确定性和好的建议之间存在区别。 - Jodrell
1
@atikot,而且,Resharper不会检查日志消息。 - Jodrell
2
@Jodrell:我猜它也没有检查其他用途,比如在代码后台创建的WPF绑定、自定义的OnPropertyChanged方法(直接接受属性名称而不是PropertyChangedEventArgs),或者调用反射来查找特定成员或类型? - O. R. Mapper

14

我能想到的最常见用例是在使用INotifyPropertyChanged接口时。(基本上与WPF和绑定相关的所有内容都使用此接口)

看看这个例子:

public class Model : INotifyPropertyChanged
{
    // From the INotifyPropertyChanged interface
    public event PropertyChangedEventHandler PropertyChanged;

    private string foo;
    public String Foo
    {
        get { return this.foo; }
        set
        {
            this.foo = value;
            // Old code:
            PropertyChanged(this, new PropertyChangedEventArgs("Foo"));

            // New Code:
            PropertyChanged(this, new PropertyChangedEventArgs(nameof(Foo)));           
        }
    }
}

可以看出,在旧方法中,我们需要传递一个字符串来指示哪个属性发生了更改。使用 nameof,我们可以直接使用属性的名称。这可能看起来不像什么大不了的事情。但想象一下当有人更改属性Foo的名称时会发生什么。使用字符串时绑定将停止工作,但编译器不会警告您。而使用 nameof,您会收到编译器错误,指出没有名为Foo的属性/参数。

请注意,某些框架使用反射技巧来获取属性的名称,但现在我们有了 nameof,这已不再必要。


8
虽然这是一种有效的方法,但更方便(也更符合DRY原则)的方法是,在新方法的参数上使用[CallerMemberName]属性来触发此事件。 - Drew Noakes
1
我同意CallerMemberName也很好,但它是一个单独的用例,因为(正如你所说)你只能在方法中使用它。至于DRY,我不确定[CallerMemberName]string x = null是否比nameof(Property)更好。你可以说属性名称被使用了两次,但那基本上就是传递给函数的参数。我认为这并不是DRY的意思 :)。 - Roy T.
实际上,您可以在属性中使用它。它们也是成员。与 nameof 相比的好处是,属性设置器根本不需要指定属性名称,从而消除了复制/粘贴错误的可能性。 - Drew Noakes
7
对于 INotifyPropertyChanged 接口而言,使用 [CallerMemberNameAttribute] 可以使属性 setter 方法干净地触发属性改变通知,而 nameof 语法可以使代码中的其他位置清晰地触发属性改变通知。因此这是一个“共同更好”的情况。 - Andrew Hanlon

12

最常见的用法是在输入验证中,例如:

//Currently
void Foo(string par) {
   if (par == null) throw new ArgumentNullException("par");
}

//C# 6 nameof
void Foo(string par) {
   if (par == null) throw new ArgumentNullException(nameof(par));
}
在第一个情况下,如果您重构该方法并更改 par 参数的名称,则很可能会忘记在 ArgumentNullException 中更改它。使用 nameof ,您无需担心这个问题。
另请参阅:nameof(C#和Visual Basic参考)

为什么我不必担心那个? - Md. Rubel Mia

9
ASP.NET Core MVC项目在AccountController.csManageController.cs中使用nameofRedirectToAction方法一起引用控制器中的操作。

示例:

return RedirectToAction(nameof(HomeController.Index), "Home");

这翻译成:

return RedirectToAction("Index", "Home");

并将用户带到“Home”控制器中的“Index”操作,即/Home/Index


为什么不彻底使用以下代码: return RedirectToAction(nameof(HomeController.Index), nameof(HomeController).Substring(nameof(HomeController),0,nameof(HomeController).Length-"Controller".Length)); - Suncat2000
@Suncat2000 因为其中一件事是在编译时完成的,而另一件事则不是? :) - Dinerdo

9
假设你需要在代码中打印变量的名称。如果你写下以下代码:
int myVar = 10;
print("myVar" + " value is " + myVar.toString());

如果有人重构代码并使用另一个名称来代替myVar,那么他/她将不得不在您的代码中查找字符串值并相应地进行更改。

相反,如果您写:

print(nameof(myVar) + " value is " + myVar.toString());

自动重构会对提高效率很有帮助!


我希望有一种特殊的变量参数语法,可以传递一个元组数组,每个参数都包含源代码表示、类型和值。这将使调用日志记录方法的代码能够消除很多冗余。 - supercat

8
MSDN文章列出了几个与MVC路由有关的例子,其中之一就是我真正理解此概念的例子。下面是该段描述内容(已格式化):

  • 当报告代码错误时、
  • 连接模型视图控制器(MVC)链接时、
  • 触发属性更改事件等时,

您通常需要捕获方法的字符串名称。使用 nameof 可以在重命名定义时保持代码有效。

以前你必须使用字符串字面量来引用定义,但是当重命名代码元素时,这种方式非常脆弱,因为工具无法检查这些字符串字面量。

已经有多个接受/最高评分答案给出了几个极好的具体示例。


6
正如其他人已经指出的那样,nameof运算符确实插入了源代码中元素所给定的名称。
我想补充一下,在重构方面这是一个非常好的想法,因为它使得字符串重构变得安全。以前,我使用了一个静态方法来实现相同的目的,但那会对运行时性能产生影响。而nameof运算符没有运行时性能影响;它在编译时完成工作。如果你查看MSIL代码,你会发现字符串被嵌入其中。请参考以下方法及其反汇编代码。
static void Main(string[] args)
{
    Console.WriteLine(nameof(args));
    Console.WriteLine("regular text");
}

// striped nops from the listing
IL_0001 ldstr args
IL_0006 call System.Void System.Console::WriteLine(System.String)
IL_000C ldstr regular text
IL_0011 call System.Void System.Console::WriteLine(System.String)
IL_0017 ret

然而,如果您计划混淆软件,则可能会有缺点。在混淆后,嵌入的字符串可能不再与元素的名称匹配。依赖于此文本的机制将中断。其中包括但不限于反射、NotifyPropertyChanged等。在运行时确定名称会消耗一些性能,但对于混淆是安全的。如果既不需要也不计划混淆,则建议使用nameof运算符。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接