命名的目的是什么?

本文关键字:是什么 | 更新日期: 2023-09-27 18:05:30

6.0版本获得了nameof的新特性,但我不能理解它的目的,因为它只是获取变量名并在编译时将其更改为字符串。

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

知道目的吗?

命名的目的是什么?

如果您希望重用属性的名称,例如在基于属性名称抛出异常或处理PropertyChanged事件时,该怎么办?在许多情况下,您都希望拥有属性的名称。

举个例子:

switch (e.PropertyName)
{
    case nameof(SomeProperty):
    { break; }
    // opposed to
    case "SomeOtherProperty":
    { break; }
}

在第一种情况下,如果不同时更改属性定义和nameof(SomeProperty)表达式,重命名SomeProperty将导致编译错误。在第二种情况下,重命名SomeOtherProperty或更改"SomeOtherProperty"字符串将导致无声地破坏运行时行为,在构建时没有错误或警告。

这是一个非常有用的方法来保持你的代码编译和无bug(某种程度上)。

(Eric Lippert的一篇很好的文章,为什么infoof没有成功,而nameof却成功了)

这对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和logging——在这两种情况下,你都希望你调用的成员的名字传递给另一个方法:

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

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

从link

给出的例子没有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});

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

例如:

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

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


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

当考虑到各种绑定和反射情况时,

的用途是多种多样的。这是将运行时错误引入编译时的好方法。

我能想到的最常见的用例是在使用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的属性/参数。

请注意,一些框架使用一些反射魔法来获取属性的名称,但现在我们有了这个名称,不再需要

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

//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中更改它。如果的name为,您就不必担心这个问题了。

参见:nameof (c# and Visual Basic Reference)

. NET Core MVC项目使用AccountController.cs中的nameofManageController.cs中的RedirectToAction方法来引用控制器中的动作。

的例子:

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

转换成:

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

and takes将用户带到'Home'控制器中的'Index'动作,即/Home/Index

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

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

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

相反,如果你写:

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

这将有助于自动重构!

MSDN文章列出了MVC路由(这个例子对我来说真正点击了这个概念)和其他几个。(格式化的)描述段落如下:

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

你经常想捕获方法的字符串名称。使用nameof可以帮助保存代码

以前必须使用字符串字面值要引用定义,在重命名代码元素时是脆弱的因为工具不知道检查这些字符串字面值

被接受的/评分最高的答案已经给出了几个优秀的具体例子。

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

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

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

nameof的目的是重构。例如,当您更改通过nameof在代码中的其他地方引用的类的名称时,您将得到一个编译错误,这就是您想要的。如果您没有使用nameof,并且只有一个普通字符串作为引用,那么您必须全文搜索类的名称才能更改它。那是最痛苦的事。使用nameof,您可以轻松地在IDE中自动构建和获取所有更改用例。

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

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

不那么乱:)

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

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

设置Binding,你必须设置Path与字符串,并与nameof关键字,可以使用重构选项。

例如,如果你在UserControl中有IsEnable的依赖属性,你想把它绑定到UserControlCheckBoxIsEnable,你可以使用这两个代码:
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);

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

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

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

当您使用ASP时,它具有优势。净MVC。当你使用HTML helper在视图中构建一些控件时,它使用HTML input的name属性中的属性名:

@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)}" })
  }
}

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