Какова цель имени?


264

В версии 6.0 появилась новая функция nameof, но я не могу понять ее назначение, поскольку она просто берет имя переменной и заменяет ее на строку при компиляции.

Я думал, что это может иметь какую-то цель при использовании, <T>но когда я пытаюсь, nameof(T)он просто печатает меня Tвместо используемого типа.

Любая идея о цели?



28
Не было способа получить это Tраньше. Был способ получить использованный тип раньше.
Джон Ханна

15
Определенно полезно при рефакторинге / переименовании имени внутри nameof. Также помогает предотвратить опечатки.
bvj

nameof пригодится с помощью nunit 3 TestCaseData . Документы показывают имя метода в виде строки [Test, TestCaseSource(typeof(MyDataClass), "TestCases")], которую можно заменить на [Test, TestCaseSource(typeof(MyDataClass), nameof(MyDataClass.TestCases))]. Гораздо приятнее.
Джереми

4
Официальная документация nameof перенесена сюда: docs.microsoft.com/en-us/dotnet/csharp/language-reference/… - здесь также перечислены ключевые варианты использования, которые служат довольно хорошим ответом на вопрос.
Маркус с

Ответы:


324

Как насчет случаев, когда вы хотите повторно использовать имя свойства, например, при создании исключения на основе имени свойства или при обработке PropertyChangedсобытия. Есть множество случаев, когда вы хотели бы иметь имя собственности.

Возьмите этот пример:

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

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

В первом случае переименование SomePropertyтакже изменит имя свойства или прервет компиляцию. В последнем случае нет.

Это очень полезный способ сохранить ваш код компиляцией и без ошибок (вроде как).

( Очень хорошая статья от Эрика Липперта, почему infoofне удалось, а nameofсделал)


2
Я понимаю суть, просто добавляя, что resharper меняет строки при рефакторинге имен, не уверенный, если VS обладает подобной функциональностью.
Эш Бурлаченко

8
Оно имеет. Но и Resharper, и VS не работают, например, над проектами. Это делает. На самом деле, это лучшее решение.
Патрик Хофман

50
Другим распространенным вариантом использования является маршрутизация в MVC с использованием nameofимени действия вместо жестко заданной строки.
RJ Катбертсон

2
@ sotn Я не уверен, что понимаю, о чем ты спрашиваешь. Ничто не мешает вам использовать это как public class MyController { public ActionResult Index() { return View(nameof(Index)); } }- и вы можете использовать nameofна нестатических членах (например, вы можете вызывать, nameof(MyController.Index)используя класс выше, и он выдаст «Index»). Ознакомьтесь с примерами по адресу msdn.microsoft.com/en-us/library/…
RJ Cuthbertson,

2
Я не понимаю, почему это особенное. Имена переменных всегда одинаковы, верно? Независимо от того, есть у вас экземпляр или нет, имена переменных не изменятся @sotn
Патрик Хофман

177

Это действительно полезно для 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), который является еще одним кусочком сахара во время компиляции, который полезен в подобных обстоятельствах :-)
Кейт

1
Одна вещь, по которой я скучаю, это что-то вроде nameofthismethod. Вы можете использовать, Log.Error($"Error in {nameof(DoSomething)}...")но если вы скопируете это в другие методы, вы не заметите, что оно все еще ссылается на DoSomething. Поэтому, хотя он отлично работает с локальными переменными или параметрами, имя метода является проблемой.
Тим Шмельтер

3
Знаете ли вы, если nameOfбудете использовать [DisplayName]атрибут, если он присутствует? Для enumпримера, который я [DisplayName]часто использую с проектами MVC
Люк Т О'Брайен

2
@AaronLS Да, это довольно специализированный, а не то, что вы бы часто использовали. Однако throw newэто совершенно другой анти-паттерн - я считаю, что чрезмерное использование catchявляется распространенной проблемой для младших разработчиков, потому что это похоже на исправление проблемы (когда большую часть времени она просто скрывает это).
Кит

26

Еще один вариант использования, в котором nameofфункция C # 6.0 становится удобной - рассмотрим такую ​​библиотеку, как 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 });

3
Я люблю Dapper и мне очень нравятся струнные взаимосвязи, но IMO это выглядит так ужасно. Риск разбить запрос путем переименования столбца кажется таким маленьким по сравнению с такими уродливыми запросами. На первый взгляд, я бы сказал, что предпочитаю писать запросы EF LINQ или следовать соглашению типа [TableName]. [ColumnName], где я могу легко находить / заменять свои запросы, когда мне это нужно.
Дризин

@drizin Я использую Dapper FluentMap для предотвращения таких запросов (а также для разделения проблем)
mamuesstack

21

Ваш вопрос уже выражает цель. Вы должны увидеть, что это может быть полезно для регистрации или создания исключений.

например.

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

это хорошо, если я изменю имя переменной, вместо этого код сломается или выдаст исключение с неверным сообщением .


Конечно, использование не ограничено этой простой ситуацией. Вы можете использовать nameofвсякий раз, когда было бы полезно кодировать имя переменной или свойства.

Использование разнообразно, когда вы рассматриваете различные ситуации связывания и отражения. Это отличный способ перенести ошибки компиляции во время выполнения.


6
@atikot: Но тогда, если вы переименуете переменную, компилятор не заметит, что строка больше не соответствует.
ИЛИ Mapper

1
Я на самом деле использую Resharper, который принимает это к сведению, но я понимаю вашу точку зрения.
Атикот

4
@atikot, я тоже, но Resharper генерирует только предупреждение, а не ошибку компилятора. Есть разница между уверенностью и хорошим советом.
Джодрелл

1
@atikot, и, Решарпер не проверяет сообщения регистрации
Джодрелл

2
@Jodrell: И, я подозреваю, он также не проверяет различные другие применения - как насчет привязок WPF, созданных в выделенном коде, пользовательских OnPropertyChangedметодов (которые непосредственно принимают имена свойств, а не PropertyChangedEventArgs), или обращений к отражению для поиска конкретного член или тип?
ИЛИ 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 вы получаете ошибку компилятора, что нет имени / аргумента с именем Foo.

Обратите внимание, что некоторые фреймворки используют магию отражения, чтобы получить имя свойства, но теперь у нас есть nameof, которое больше не нужно .


5
Хотя это правильный подход, более удобный (и СУХОЙ) подход заключается в использовании [CallerMemberName]атрибута в параметре нового метода для вызова этого события.
Дрю Ноакс

1
Я согласен, что CallerMemberName также хорош, но это отдельный вариант использования, поскольку (как вы сказали) вы можете использовать его только в методах. Что касается СУХОГО, я не уверен, что [CallerMemberName]string x = nullлучше, чем nameof(Property). Можно сказать, что имя свойства используется дважды, но это в основном аргумент, передаваемый функции. Не совсем то, что подразумевается под DRY, я думаю :).
Рой Т.

На самом деле вы можете использовать его в свойствах. Они тоже члены. Преимущество nameofзаключается в том, что установщику свойств вообще не нужно указывать имя свойства, что исключает возможность копирования / вставки ошибок.
Дрю Ноакс

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 (C # и Visual Basic Reference)


7

Проект ASP.NET Core MVC использует nameofв AccountController.csи ManageController.csсRedirectToAction методом ссылку на действие в контроллере.

Пример:

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

Это переводится как:

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

и берет берет пользователя к действию «индекс» в контроллере «Home», то есть /Home/Index.


Почему бы не пойти целым и использовать return RedirectToAction(nameof(HomeController.Index), nameof(HomeController).Substring(nameof(HomeController),0,nameof(HomeController).Length-"Controller".Length));?
Suncat2000

@ Suncat2000, потому что одна из этих вещей выполняется в процессе компиляции, а другая - нет? :)
Dinerdo

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ключевым словом, можно использовать опцию Refactor.

Например, если у вас есть 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-помощник для создания некоторого элемента управления, он использует имена свойств в атрибуте name ввода 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 },
    }
}
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.