Яка мета найменування?


263

Версія 6.0 отримала нову функцію nameof, але я не можу зрозуміти її призначення, оскільки вона просто бере ім'я змінної та змінює її на рядок при компіляції.

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

Будь-яка ідея про мету?


2
Дивіться також msdn.microsoft.com/library/dn986596.aspx
Corak

28
Раніше цього не було T. Раніше існував спосіб отримати використаний тип.
Джон Ханна

Спочатку це здавалося надмірним, і я все ще не бачу вагомих причин використовувати його. Може, приклад MVC?
Корі Алікс

15
Безумовно, корисно при поновленні / перейменуванні імені в межах nameof. Також допомагає запобігти друкарським помилкам.
bvj

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

Відповіді:


322

Що стосується випадків, коли ви хочете повторно використовувати ім’я ресурсу, наприклад, коли викидаєте виняток на основі імені властивості або обробляєте PropertyChanged подію. Є численні випадки, коли ви хочете мати назву об’єкта нерухомості.

Візьмемо цей приклад:

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

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

У першому випадку перейменування SomeProperty також змінить назву властивості, або воно порушить компіляцію. Останній випадок ні.

Це дуже корисний спосіб зберегти ваш код і збирання помилок (сортування).

( Дуже приємна стаття Еріка Ліпперта, чому infoofїї не зробили, поки nameofзробили)


1
Я розумію суть, лише додавши, що resharper змінює рядки під час рефакторингу імен, не впевнений, чи має VS подібний функціонал.
Еш Бурлаценко

7
Це має. Але і Resharper, і VS не працюють над проектами, наприклад. Це робить. Насправді це краще рішення.
Патрік Хофман

49
Іншим поширеним випадком використання є маршрутизація в MVC з використанням nameofі назви дії замість жорстко закодованого рядка.
RJ Cuthbertson

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- для перерахунків - зазвичай, якщо ви хочете, щоб ім'я рядка enum ви використовували .ToString():

enum MyEnum { ... FooBar = 7 ... }

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

> "FooBar"

Це насправді відносно повільно, оскільки .Net утримує значення enum (тобто 7) і знаходить ім'я під час виконання.

Замість цього використовуйте nameof:

Console.WriteLine(nameof(MyEnum.FooBar))

> "FooBar"

Тепер .Net замінює ім'я enum рядком під час компіляції.


Ще одне використання стосується таких речей, як 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
Luke T O'Brien

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

з 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 Я використовую Dapper FluentMap для запобігання подібних запитів (а також для вирішення проблем)
mamuesstack

21

Ваше запитання вже виражає мету. Ви повинні побачити, що це може бути корисним для реєстрації або викидання винятків.

наприклад.

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

це добре, якщо я зміню ім'я змінної, код порушить замість цього або поверне виняток із невірним повідомленням .


Звичайно, використання не обмежується цією простою ситуацією. Ви можете використовувати, nameofколи було б корисно кодувати ім'я змінної чи властивості.

Використання є різноманітним при розгляді різних ситуацій зв’язку та відображення. Це відмінний спосіб принести те, що були помилками часу запуску, щоб скласти час.


6
@atikot: Але тоді, якщо перейменувати змінну, компілятор не помітить, що рядок більше не відповідає.
АБО Mapper

1
Я фактично використовую resharper, який враховує це, але я бачу вашу думку.
atikot

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

1
@atikot і, Resharper не перевіряє реєстрацію повідомлень
Jodrell

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.

Зауважте, що деякі рамки використовують деяку магію відображення, щоб отримати назву властивості, але тепер у нас назва цього не потрібна .


5
Незважаючи на те, що це правильний підхід, більш зручним (і сухим) підходом є використання [CallerMemberName]атрибуту в параметрі нового методу для піднесення цієї події.
Дрю Ноакс

1
Я погоджуюся, CallerMemberName також приємний, але це окремий випадок використання, оскільки (як ви вже сказали) ви можете використовувати його лише в методах. Щодо DRY, я не впевнений, чи [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 MVC Ядро використовує nameofв AccountController.csі ManageController.csз RedirectToActionметодом , щоб посилатися на дію в контролері.

Приклад:

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

@ Suncat2000, тому що одна з цих речей робиться у компіляції, а інша - ні? :)
Дінердо

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та значення. Це дало б можливість методам реєстрації коду, що викликають код, щоб усунути багато надмірностей.
supercat

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в МОФ програмно .

щоб встановити, 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 для побудови деякого елемента контролю, він використовує імена властивостей у атрибуті імен введення 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.