nameof的目的是什么?


263

6.0版具有的新功能nameof,但我无法理解它的用途,因为它只接受变量名,并在编译时将其更改为字符串。

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

有什么目的吗?



28
以前没有办法做到这一点T。以前有一种获取二手类型的方法。
乔恩·汉娜

最初,这似乎有点过分,但我仍然没有看到有说服力的理由来使用它。也许是MVC的例子?
科里·阿利克斯

15
在中重构/重命名名称时绝对有用nameof。还有助于防止错别字。
bvj

4
nameof的官方文档已移至此处:docs.microsoft.com/zh-cn/dotnet/csharp/language-reference / ...-它还列出了关键用例,可以很好地解决该问题。
markus s

Answers:


322

如果要重用属性名称,例如在基于属性名称引发异常或处理PropertyChanged事件时,该怎么办?在很多情况下,您都希望使用属性名称。

举个例子:

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

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

在第一种情况下,重命名SomeProperty也将更改属性的名称,否则将中断编译。最后一种情况没有。

这是保持代码编译和消除错误(排序)的非常有用的方法。

埃里克·利珀特Eric Lippert)的一篇非常好的文章,为什么infoof没有做到,而同时nameof却做到了)


1
我理解这一点,只是补充说,在重构名称时,resharper会更改字符串,不确定VS是否具有类似的功能。
Ash Burlaczenko

7
它有。但是,例如,Resharper和VS都不在项目上工作。确实如此。实际上,这是更好的解决方案。
帕特里克·霍夫曼

49
另一个常见的用例是在MVC中使用nameof和动作名称而不是硬编码字符串进行路由。
RJ卡斯伯森2015年

2
@sotn我不确定我是否理解您的要求。没有什么可以阻止您使用它的public class MyController { public ActionResult Index() { return View(nameof(Index)); } },您可以nameof在非静态成员上使用(例如,可以nameof(MyController.Index)使用上面的类进行调用,它将发出“索引”)。在msdn.microsoft.com/en-us/library/…上
RJ Cuthbertson

2
我不明白为什么这很特别。变量名总是一样的,对吗?无论您是否具有实例,变量名称都不会更改@sotn
Patrick Hofman

176

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

9
您还添加了另一个很酷的功能:字符串插值!
帕特里克·霍夫曼

1
@PatrickHofman和typeof(T),这是另一段在类似情况下有用的编译时糖:-)
Keith

1
我想念的一件事是nameofthismethod。您可以使用,Log.Error($"Error in {nameof(DoSomething)}...")但是如果将其复制粘贴到其他方法中,您不会注意到它仍在引用DoSomething。因此,当它与局部变量或参数完美配合时,方法名是一个问题。
蒂姆·施密特

3
您知道是否nameOf会使用该[DisplayName]属性(如果存在)?例如,enum[DisplayName]经常在MVC项目中使用
Luke T O'Brien

2
@AaronLS是的,它相当专业,不是您经常使用的东西。但这完全throw new是另一种反模式-我发现过度使用catch是初级开发人员的常见问题,因为感觉就像是在解决问题(大多数时候只是将其隐藏)。
基思

26

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

从链接中给出的示例

没有名字

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

与nameof

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

3
我爱Dapper,我真的很喜欢字符串插入,但是IMO看起来很丑。与这种丑陋的查询相比,通过重命名列破坏查询的风险似乎很小。乍一看,我会说我更喜欢编写EF LINQ查询,或者遵循[TableName]。[ColumnName]这样的约定,可以在需要时轻松查找/替换查询。
drizin

@drizin我使用Dapper FluentMap来防止此类查询(以及用于分离关注点)
mamuesstack

21

您的问题已经表达了目的。您必须看到这对于记录或引发异常可能很有用。

例如。

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

这很好,如果我更改变量的名称,代码将改为中断或返回带有错误消息的异常


当然,用途不限于这种简单情况。您可以nameof在需要为变量或属性的名称编码时使用。

当您考虑各种绑定和反射情况时,这些用法是多种多样的。这是将运行时错误带入编译时间的绝佳方法。


6
@atikot:但是,如果您重命名变量,编译器将不会注意到该字符串不再匹配。
或Mapper

1
我实际上使用了reshaper来解决这个问题,但是我明白你的意思。
atikot

4
@atikot,我也是,但是Resharper仅生成警告,而不是编译器错误。确定性和好的建议之间是有区别的。
Jodrell

1
@atikot,并且Resharper不会检查日志消息
Jodrell

2
@Jodrell:而且,我怀疑,它不会检查其他各种用途-在代码隐藏,自定义OnPropertyChanged方法(直接接受属性名而不是PropertyChangedEventArgs)中创建的WPF绑定或调用反射来查找特定对象如何?成员或类型?
或Mapper

13

我能想到的最常见的用例是使用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时,会出现一个编译器错误,即name没有属性/参数Foo

注意,某些框架使用一些反射魔术来获取属性的名称,但是现在我们有了nameof,它不再是必需的


5
虽然这是一种有效的方法,但更方便的(和DRY)方法是[CallerMemberName]在新方法的参数上使用该属性来引发此事件。
Drew Noakes

1
我同意CallerMemberName也很好,但是它是一个单独的用例,因为(如您所说)您只能在方法中使用它。至于DRY,我不确定是否[CallerMemberName]string x = null比更好nameof(Property)。您可以说属性名使用了两次,但这基本上就是传递给函数的参数。我认为DRY实际上不是什么意思:)。
罗伊T.15年

实际上,您可以在属性中使用它。他们也是成员。这样做的好处nameof是,属性设置程序根本不需要指定属性名称,从而消除了复制/粘贴错误的可能性。
Drew Noakes

4
对于INotifyPropertyChanged,这是一种“更好的在一起”的情况,使用[CallerMemberNameAttribute]允许从属性设置程序中清晰地发出更改通知,而nameof语法允许从代码中的不同位置清晰地发出更改通知。
安德鲁·汉隆

9

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

//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参考)


7

在ASP.NET MVC的核心项目使用nameofAccountController.cs,并ManageController.csRedirectToAction方法在控制器中引用的动作。

例:

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

转换为:

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

并将用户带到“主页”控制器(即)中的“索引”操作/Home/Index


为什么不全程使用return RedirectToAction(nameof(HomeController.Index), nameof(HomeController).Substring(nameof(HomeController),0,nameof(HomeController).Length-"Controller".Length));呢?
Suncat2000 '17

@ Suncat2000,因为其中一项是在编译中完成的,而另一项不是吗?:)
Dinerdo '18

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

但是,如果您计划对软件进行混淆处理,则可能是一个缺点。混淆后,嵌入的字符串可能不再与元素名称匹配。依赖于本文的机制将会中断。包括但不限于以下示例:Reflection,NotifyPropertyChanged ...

在运行时确定名称会花费一些性能,但可以避免混淆。如果既不需要也不计划混淆,我建议您使用nameof运算符。


5

考虑到您在代码中使用了变量,并且需要获取变量的名称并可以说将其打印出来,因此您必须使用

int myVar = 10;
print("myVar" + " value is " + myVar.toString());

然后如果有人重构代码并为“ myVar”使用另一个名称,则他/她将必须注意代码中的字符串值并相应地对其进行处理。

相反,如果您有

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

这将有助于自动重构!


我希望有一种特殊的变量参数语法,该语法将传递一个元组数组,每个元组一个,其中包含源代码表示形式Type,和值。这将使代码调用日志记录方法消除大量冗余成为可能。
超级猫

5

MSDN文章列出了MVC路由(该示例对我来说确实是一个概念)。(格式化的)描述段落为:

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

您通常想 捕获方法的字符串名称重命名定义时,使用nameof有助于使代码有效。

在您不得不使用字符串文字 来引用定义之前,在重命名代码元素时 这很容易,因为工具不知道要检查这些字符串文字。

公认的/评分最高的答案已经给出了几个很好的具体例子。


3

nameof操作员的目的是提供工件的源名称。

通常,源名称与元数据名称相同:

public void M(string p)
{
    if (p == null)
    {
        throw new ArgumentNullException(nameof(p));
    }
    ...
}

public int P
{
    get
    {
        return p;
    }
    set
    {
        p = value;
        NotifyPropertyChanged(nameof(P));
    }
}

但这并非总是如此:

using i = System.Int32;
...
Console.WriteLine(nameof(i)); // prints "i"

要么:

public static string Extension<T>(this T t)
{
    return nameof(T); returns "T"
}

我一直给它的一种用途是命名资源:

[Display(
    ResourceType = typeof(Resources),
    Name = nameof(Resources.Title_Name),
    ShortName = nameof(Resources.Title_ShortName),
    Description = nameof(Resources.Title_Description),
    Prompt = nameof(Resources.Title_Prompt))]

事实是,在这种情况下,我什至不需要生成的属性来访问资源,但是现在我有了编译时检查资源是否存在。


0

nameof关键字的用法之一是用于Binding编程方式在wpf中进行设置。

要设置Binding您必须Path使用字符串和nameof关键字进行设置,可以使用“重构”选项。

举例来说,如果你有IsEnable在你所依赖的属性UserControl,并且希望将其绑定到IsEnable的一些CheckBox在你的UserControl,你可以使用这两个代码:

CheckBox chk = new CheckBox();
Binding bnd = new Binding ("IsEnable") { Source = this };
chk.SetBinding(IsEnabledProperty, bnd);

CheckBox chk = new CheckBox();
Binding bnd = new Binding (nameof (IsEnable)) { Source = this };
chk.SetBinding(IsEnabledProperty, bnd);

很明显,第一个代码无法重构,而第二个代码可以重构。


0

以前我们使用的是这样的:

// Some form.
SetFieldReadOnly( () => Entity.UserName );
...
// Base form.
private void SetFieldReadOnly(Expression<Func<object>> property)
{
    var propName = GetPropNameFromExpr(property);
    SetFieldsReadOnly(propName);
}

private void SetFieldReadOnly(string propertyName)
{
    ...
}

原因-编译时间安全。没有人可以默默地重命名属性并破坏代码逻辑。现在我们可以使用nameof()了。


0

使用ASP.Net MVC时,它具有优势。当您使用HTML帮助器在视图中构建某些控件时,它将在html输入的名称属性中使用属性名称:

@Html.TextBoxFor(m => m.CanBeRenamed)

它使像这样:

<input type="text" name="CanBeRenamed" />

因此,现在,如果您需要使用Validate方法验证属性,则可以执行以下操作:

public IEnumerable<ValidationResult> Validate(ValidationContext validationContext) {
  if (IsNotValid(CanBeRenamed)) {
    yield return new ValidationResult(
      $"Property {nameof(CanBeRenamed)} is not valid",
      new [] { $"{nameof(CanBeRenamed)}" })
  }
}

如果您使用重构工具重命名属性,则不会破坏您的验证。


0

的另一个用例nameof是检查选项卡页,而不是检查索引,可以Name按如下方式检查选项卡页的属性:

if(tabControl.SelectedTab.Name == nameof(tabSettings))
{
    // Do something
}

少乱:)


0

我发现这nameof增加了我的应用程序中非常长且复杂的SQL语句的可读性。它使变量脱颖而出,并省去了弄清楚SQL语句中变量在何处使用的工作。

public bool IsFooAFoo(string foo, string bar)
{
    var aVeryLongAndComplexQuery = $@"SELECT yada, yada
    -- long query in here
    WHERE fooColumn = @{nameof(foo)}
    AND barColumn = @{nameof(bar)}
    -- long query here";


    SqlParameter[] parameters = {
        new SqlParameter(nameof(foo), SqlDBType.VarChar, 10){ Value = foo },
        new SqlParameter(nameof(bar), SqlDBType.VarChar, 10){ Value = bar },
    }
}
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.