nameof 的目的是啥?

Posted

技术标签:

【中文标题】nameof 的目的是啥?【英文标题】:What is the purpose of nameof?nameof 的目的是什么? 【发布时间】:2015-10-20 03:58:18 【问题描述】:

6.0 版增加了nameof 的新功能,但我不明白它的用途,因为它只是获取变量名并在编译时将其更改为字符串。

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

对目的有什么想法吗?

【问题讨论】:

另见msdn.microsoft.com/library/dn986596.aspx 以前没有办法获得T。以前有一种方法可以获取使用的类型。 在重构/重命名 nameof 中的名称时绝对有用。还有助于防止拼写错误。 nameof 在 nunit 3 TestCaseData 中派上用场。文档通过字符串[Test, TestCaseSource(typeof(MyDataClass), "TestCases")] 显示方法名称,可以用[Test, TestCaseSource(typeof(MyDataClass), nameof(MyDataClass.TestCases))] 替换。好多了。 nameof 的官方文档已移至此处:docs.microsoft.com/en-us/dotnet/csharp/language-reference/… - 它还列出了可以很好地回答问题的关键用例。 【参考方案1】:

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

【讨论】:

您还添加了另一个很酷的功能:字符串插值! @PatrickHofman 和 typeof(T),这是另一个在类似情况下很有用的编译时糖 :-) 我缺少的一件事是nameofthismethod。您可以使用Log.Error($"Error in nameof(DoSomething)..."),但如果您将其复制粘贴到其他方法,您将不会注意到它仍然指的是DoSomething。因此,虽然它与局部变量或参数完美配合,但方法名称是个问题。 您知道nameOf 是否会使用[DisplayName] 属性(如果存在)?对于 enum 示例,我经常在 MVC 项目中使用 [DisplayName] @AaronLS 是的,它相当专业,不是您经常使用的东西。然而,throw new 是一个完全不同的反模式——我发现过度使用catch 是初级开发人员的常见问题,因为它感觉就像解决问题(大多数时候它只是隐藏它)。跨度> 【参考方案2】:

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

例如:

public void DoStuff(object input)

    if (input == null)
    
        throw new ArgumentNullException(nameof(input));
    

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


当然,用途不仅限于这种简单的情况。只要对变量或属性的名称进行编码有用,您就可以使用nameof

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

【讨论】:

@atikot:但是,如果你重命名变量,编译器不会注意到字符串不再匹配。 我实际上使用 resharper 来考虑它,但我明白你的意思。 @atikot,我也是,但 Resharper 只生成警告,而不是编译器错误。确定性和好的建议是有区别的。 @atikot,而且,Resharper 不检查日志消息 @Jodrell:而且,我怀疑,它也不会检查其他各种用途 - 在代码隐藏、自定义 OnPropertyChanged 方法中创建的 WPF 绑定怎么样(直接接受属性名称而不是PropertyChangedEventArgs),还是调用反射来查找特定成员或类型?【参考方案3】:

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

举个例子:

switch (e.PropertyName)

    case nameof(SomeProperty):
     break; 

    // opposed to
    case "SomeOtherProperty":
     break; 

在第一种情况下,如果您不更改属性定义和nameof(SomeProperty) 表达式,重命名SomeProperty 将导致编译错误。在第二种情况下,重命名 SomeOtherProperty 或更改 "SomeOtherProperty" 字符串将导致运行时行为静默中断,在构建时不会出现错误或警告。

这是一种非常有用的方法,可以让您的代码保持编译和无错误(某种程度)。

(very nice article from Eric Lippert 为什么infoof 没有成功,而nameof 成功了)

【讨论】:

我明白这一点,只是添加 resharper 在重构名称时会更改字符串,不确定 VS 是否具有类似的功能。 它有。但是,例如,Resharper 和 VS 都不适用于项目。这样做。事实上,这是更好的解决方案。 另一个常见用例是在 MVC 中使用 nameof 和操作名称而不是硬编码字符串进行路由。 @sotn 我不确定我是否理解您的要求。没有什么能阻止你像public class MyController public ActionResult Index() return View(nameof(Index)); 那样使用它——你可以在非静态成员上使用nameof(例如,你可以使用上面的类调用nameof(MyController.Index),它会发出“索引”)。查看msdn.microsoft.com/en-us/library/…的示例 我不明白这有什么特别之处。变量名总是一样的,对吧?不管你有没有实例,变量名都不会改变@sotn【参考方案4】:

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

//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# and Visual Basic Reference)

【讨论】:

为什么我不用担心这个?【参考方案5】:

假设您需要在代码中打印变量的名称。如果你写:

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

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

相反,如果你写:

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

这将有助于自动重构!

【讨论】:

我希望有一种特殊的变量参数语法可以传递一个元组数组,每个参数一个,包含源代码表示、Type 和值。这将使调用日志记录方法的代码能够消除大量冗余。【参考方案6】:

我能想到的最常见的用例是使用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,这不再是必需的

【讨论】:

虽然这是一种有效的方法,但更方便(且 DRY)的方法是在新方法的参数上使用 [CallerMemberName] 属性来引发此事件。 我同意 CallerMemberName 也不错,但它是一个单独的用例,因为(如您所说)您只能在方法中使用它。至于 DRY,我不确定 [CallerMemberName]string x = null 是否优于 nameof(Property)。您可以说属性名称被使用了两次,但这基本上是传递给函数的参数。我认为这并不是 DRY 的真正含义:)。 其实可以在属性中使用。他们也是会员。 nameof 的好处是属性设置器根本不需要指定属性名称,消除了复制/粘贴错误的可能性。 对于INotifyPropertyChanged 来说,这是一个“更好地在一起”的情况,使用[CallerMemberNameAttribute] 允许从属性设置器中干净地引发更改通知,而nameof 语法允许更改通知从代码中的不同位置干净地提出来。【参考方案7】:

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))]

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

【讨论】:

【参考方案8】:

正如其他人已经指出的那样,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 运算符。

【讨论】:

【参考方案9】:

The MSDN article 列出了 MVC 路由(对我来说真正符合这个概念的示例)等等。 (格式化的)描述段落如下:

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

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

在你不得不使用字符串字面量之前 引用定义,在重命名代码元素时很脆弱 因为工具不知道检查这些字符串文字。

接受/评价最高的答案已经给出了几个很好的具体例子。

【讨论】:

【参考方案10】:

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

例子:

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 因为其中一件事是在编译中完成的,而另一件事不是? :)【参考方案11】:

nameof 关键字的一种用法是在 wpf 中以编程方式设置Binding

要设置Binding,您必须使用字符串设置Path,并使用nameof 关键字,可以使用Refactor 选项。

例如,如果您的 UserControl 中有 IsEnable 依赖属性,并且您希望将其绑定到 IsEnableCheckBox 中的 CheckBox,则可以使用以下两个代码:

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);

很明显第一个代码不能重构,但是第二个代码......

【讨论】:

【参考方案12】:

以前我们使用类似的东西:

// 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()。

【讨论】:

【参考方案13】:

另一个使用 C# 6.0 的 nameof 功能变得方便的用例 - 考虑像 Dapper 这样的库,它使数据库检索更加容易。尽管这是一个很棒的库,但您需要在查询中硬编码属性/字段名称。这意味着如果您决定重命名属性/字段,您很可能会忘记更新查询以使用新的字段名称。通过字符串插值和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);

【讨论】:

我喜欢 Dapper,我真的很喜欢字符串插值,但是 IMO 这看起来很丑。与如此丑陋的查询相比,通过重命名列来破坏查询的风险似乎很小。乍一看,我会说我更喜欢编写 EF LINQ 查询,或者遵循 [TableName].[ColumnName] 之类的约定,我可以在需要时轻松找到/替换我的查询。 @drizin 我使用Dapper FluentMap 来防止此类查询(也用于关注点分离)【参考方案14】:

当您使用 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)" )
  

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

【讨论】:

【参考方案15】:

nameof 的另一个用例是检查标签页,而不是检查索引,您可以检查标签页的Name 属性,如下所示:

if(tabControl.SelectedTab.Name == nameof(tabSettings))

    // Do something

不那么凌乱:)

【讨论】:

【参考方案16】:

我发现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 ,
    

【讨论】:

【参考方案17】:

nameof 的目的是重构。例如,当您在代码中的其他位置更改通过 nameof 引用的类的名称时,您将收到编译错误这就是您想要的。如果您没有使用 nameof 并且只有一个纯字符串作为参考,则必须全文搜索类的名称才能更改它。这是一个底部的痛苦。使用 nameof,您可以轻松自如地构建,并在 IDE 中自动获取所有需要更改的案例。

【讨论】:

以上是关于nameof 的目的是啥?的主要内容,如果未能解决你的问题,请参考以下文章

AsQueryable() 的目的是啥?

@ConditionalOnProperty 注释的目的是啥?

toBeFalsy() 的目的是啥?

移位的目的是啥? [关闭]

docker build 输出的目的是啥?

partitioningBy的目的是啥