Яке призначення => в C # в підписі властивості


229

Я натрапив на якийсь код, який сказав

public int MaxHealth => 
         Memory[Address].IsValid ? 
         Memory[Address].Read<int>(Offs.Life.MaxHp) : 
         0;

Тепер я дещо знайомий з виразами лямбда. Я просто не бачив, щоб він використовував це таким чином.

Яка була б різниця між наведеним твердженням та

public int MaxHealth  = x ? y:z;

4
Перший блок є властивістю, другий - мінливий
М.казем Ахгарі

14
@ M.kazemAkhgary * поле, а не змінна.
Мафія

Відповіді:


376

Те, на що ти дивишся, - це тілесний вираз, а не лямбда-вираз.

Коли компілятор стикається з елементом властивості , вираженим вираженням , він по суті перетворює його в getter, як це:

public int MaxHealth
{
    get
    {
        return Memory[Address].IsValid ? Memory[Address].Read<int>(Offs.Life.MaxHp) : 0;
    }
}

(Ви можете перевірити це для себе, завантаживши код у інструмент під назвою TryRoslyn .)

Члени з вираженими виразами - як і більшість функцій C # 6 - є лише синтаксичним цукром . Це означає, що вони не забезпечують функціоналу, якого інакше не вдалося досягти за допомогою існуючих функцій. Натомість ці нові функції дозволяють використовувати більш виразний і стислий синтаксис

Як бачите, члени з вираженими виразами мають кілька ярликів, які роблять членів власності більш компактними:

  • Не потрібно використовувати returnоператор, оскільки компілятор може зробити висновок, що ви хочете повернути результат виразу
  • Немає необхідності створювати блок тверджень, оскільки тіло - це лише один вираз
  • Не потрібно використовувати getключове слово, оскільки воно має на увазі використання синтаксису елементарного члена виразу.

Я зробив підсумковий пункт сміливим, оскільки він відповідає вашому актуальному питанню, на яке я зараз відповім.

Різниця між ...

// expression-bodied member property
public int MaxHealth => x ? y:z;

І ...

// field with field initializer
public int MaxHealth = x ? y:z;

Це те саме, що різниця між ...

public int MaxHealth
{
    get
    {
        return x ? y:z;
    }
}

І ...

public int MaxHealth = x ? y:z;

Яке - якщо ви розумієте властивості - має бути очевидним.

Однак для того, щоб було зрозуміло: перший перелік - це властивість із геттером під кришкою, який буде називатися щоразу, коли ви отримуєте доступ до нього. Другий лістинг - це поле з ініціалізатором поля, вираження якого оцінюється лише один раз, коли тип інстанціюється.

Ця різниця в синтаксисі насправді є досить тонкою і може призвести до "готчі", яку описав Білл Вагнер у публікації "AC # 6 gotcha: Ініціалізація проти членів висловлювань" .

У той час як вираз працездатних члени лямбда expression- як , вони НЕ лямбда - вираження. Принципова відмінність полягає в тому, що лямбда-вираз призводить до або делегатного екземпляра, або до дерева виразів. Члени з вираженими виразами - це лише директива для компілятора генерувати властивість за кадром. Подібність (більш-менш) починається і закінчується стрілкою ( =>).

Я також додам, що члени-виразники не обмежуються членами власності. Вони працюють над усіма цими членами:

  • Властивості
  • Індексатори
  • Методи
  • Оператори

Додано в C # 7.0

Однак вони не працюють над цими членами:

  • Вкладені типи
  • Події
  • Поля

6
Станом на C # 7 також підтримуються конструктори та фіналізатори. docs.microsoft.com/en-us/dotnet/csharp/programming-guide/…
bzier

8
@bzier Це змова зробити нас функціональними програмістами. ЯКЩО ТАКЕ ВІН ВІН !!
Sentinel

Супер чудова відповідь!
Хайме Арройо Гарсія

2
Посилання на посаду Білла Вагнера наразі порушено. Я думаю, що я знайшов новий URL: codeproject.com/Articles/1064964/…
Fry Simpson

36

Гаразд ... Я прокоментував, що вони різні, але не міг точно пояснити як, але тепер я знаю.

String Property { get; } = "value";

не те саме, що

String Property => "value";

Ось різниця ...

Коли ви використовуєте автоматичний ініціалізатор, властивість створює екземпляр значення та використовує це значення постійно. У вищезгаданому пості є розірване посилання на Білла Вагнера, що це добре пояснює, і я шукав правильну посилання, щоб зрозуміти це сам.

У моїй ситуації у мене автоматеріал автоматично ініціалізував команду в ViewModel для перегляду. Я змінив властивість використовувати ініціалізатор, що працює на вираз, і команда CanExecute перестала працювати.

Ось як це виглядало і ось що відбувалося.

Command MyCommand { get; } = new Command();  //works

ось що я змінив.

Command MyCommand => new Command();  //doesn't work properly

Різниця тут полягає в тому, що коли я використовую, { get; } =я створюю та посилаюсь на команду SAME у цій властивості. Під час використання =>я фактично створюю нову команду і повертаю її щоразу, коли властивість викликається. Тому я ніколи не міг оновити CanExecuteсвою команду, тому що я завжди казав їй оновити нову посилання на цю команду.

{ get; } = // same reference
=>         // new reference

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


8
Синтаксис => дорівнює get {return new Command (); } синтаксис.
Мафія

35

Це нова особливість C # 6, яка називається членом, що працює на вираз, що дозволяє визначити властивість getter лише за допомогою функції, подібної до лямбда.

Хоча це вважається синтаксичним цукром для наступного, вони можуть не виробляти однакових ІЛ:

public int MaxHealth
{
    get
    {
        return Memory[Address].IsValid
               ?   Memory[Address].Read<int>(Offs.Life.MaxHp)
               :   0;
    }
}

Виявляється, що якщо скласти обидві версії вищезазначених та порівняти ІЛ, згенерований для кожної, ви побачите, що вони НІКОЛИ однакові.

Ось ІЛ для класичної версії в цій відповіді, коли визначено в класі з назвою TestClass:

.property instance int32 MaxHealth()
{
    .get instance int32 TestClass::get_MaxHealth()
}

.method public hidebysig specialname 
    instance int32 get_MaxHealth () cil managed 
{
    // Method begins at RVA 0x2458
    // Code size 71 (0x47)
    .maxstack 2
    .locals init (
        [0] int32
    )

    IL_0000: nop
    IL_0001: ldarg.0
    IL_0002: ldfld class [mscorlib]System.Collections.Generic.Dictionary`2<int64, class MemoryAddress> TestClass::Memory
    IL_0007: ldarg.0
    IL_0008: ldfld int64 TestClass::Address
    IL_000d: callvirt instance !1 class [mscorlib]System.Collections.Generic.Dictionary`2<int64, class MemoryAddress>::get_Item(!0)
    IL_0012: ldfld bool MemoryAddress::IsValid
    IL_0017: brtrue.s IL_001c

    IL_0019: ldc.i4.0
    IL_001a: br.s IL_0042

    IL_001c: ldarg.0
    IL_001d: ldfld class [mscorlib]System.Collections.Generic.Dictionary`2<int64, class MemoryAddress> TestClass::Memory
    IL_0022: ldarg.0
    IL_0023: ldfld int64 TestClass::Address
    IL_0028: callvirt instance !1 class [mscorlib]System.Collections.Generic.Dictionary`2<int64, class MemoryAddress>::get_Item(!0)
    IL_002d: ldarg.0
    IL_002e: ldfld class Offs TestClass::Offs
    IL_0033: ldfld class Life Offs::Life
    IL_0038: ldfld int64 Life::MaxHp
    IL_003d: callvirt instance !!0 MemoryAddress::Read<int32>(int64)

    IL_0042: stloc.0
    IL_0043: br.s IL_0045

    IL_0045: ldloc.0
    IL_0046: ret
} // end of method TestClass::get_MaxHealth

І ось IL для версії bodhed члена, коли визначено в класі з назвою TestClass:

.property instance int32 MaxHealth()
{
    .get instance int32 TestClass::get_MaxHealth()
}

.method public hidebysig specialname 
    instance int32 get_MaxHealth () cil managed 
{
    // Method begins at RVA 0x2458
    // Code size 66 (0x42)
    .maxstack 2

    IL_0000: ldarg.0
    IL_0001: ldfld class [mscorlib]System.Collections.Generic.Dictionary`2<int64, class MemoryAddress> TestClass::Memory
    IL_0006: ldarg.0
    IL_0007: ldfld int64 TestClass::Address
    IL_000c: callvirt instance !1 class [mscorlib]System.Collections.Generic.Dictionary`2<int64, class MemoryAddress>::get_Item(!0)
    IL_0011: ldfld bool MemoryAddress::IsValid
    IL_0016: brtrue.s IL_001b

    IL_0018: ldc.i4.0
    IL_0019: br.s IL_0041

    IL_001b: ldarg.0
    IL_001c: ldfld class [mscorlib]System.Collections.Generic.Dictionary`2<int64, class MemoryAddress> TestClass::Memory
    IL_0021: ldarg.0
    IL_0022: ldfld int64 TestClass::Address
    IL_0027: callvirt instance !1 class [mscorlib]System.Collections.Generic.Dictionary`2<int64, class MemoryAddress>::get_Item(!0)
    IL_002c: ldarg.0
    IL_002d: ldfld class Offs TestClass::Offs
    IL_0032: ldfld class Life Offs::Life
    IL_0037: ldfld int64 Life::MaxHp
    IL_003c: callvirt instance !!0 MemoryAddress::Read<int32>(int64)

    IL_0041: ret
} // end of method TestClass::get_MaxHealth

Див. Https://msdn.microsoft.com/en-us/magazine/dn802602.aspx для отримання додаткової інформації про цю та інші нові функції в C # 6.

Дивіться цю публікацію Різниця між властивістю та полем у C # 3.0+ про різницю між полем та користувачем у C #.

Оновлення:

Зауважте, що члени з виразом розширення включали властивості, конструктори, фіналізатори та індексатори в C # 7.0.


16

Він називається членом Expression Bodied, і він був введений в C # 6. Це просто синтаксичний цукор над getєдиною властивістю.

Він еквівалентний:

public int MaxHealth { get { return Memory[Address].IsValid ?
                             Memory[Address].Read<int>(Offs.Life.MaxHp) : 0; }

Еквівалент декларації методу доступний:

public string HelloWorld() => "Hello World";

В основному дозволяє скоротити котельну плиту.


7

Ще один важливий момент, якщо ви використовуєте C # 6:

'=>' може використовуватися замість 'get' і призначений лише для методів 'get only' - він не може бути використаний із 'set'.

Щодо C # 7, дивіться коментар від @avenmore нижче - його тепер можна використовувати в інших місцях. Ось хороша довідка - https://csharp.christiannagel.com/2017/01/25/expressionbodiedmembers/


8
Більше не відповідає дійсності, якщо ви використовуєте C # 7. "C # 7.0 продовжує підвищення продуктивності. Експресивні члени були доступні за допомогою C # 6 для методів та властивостей. Тепер вони можуть використовуватися з конструкторами, деструкторами, доступними властивостями та аксесуарами подій. також." ( Джерело )
avenmore

1

Для наступного твердження поділився у своїй відповіді Алекс Букер

Коли компілятор стикається з елементом властивості, вираженим вираженням, він по суті перетворює його в getter, як це:

Дивіться наступний знімок екрана , він показує, як це твердження (за допомогою посилання SharpLab )

public string APIBasePath => Configuration.ToolsAPIBasePath;

перетворюється в

public string APIBasePath
{
    get
    {
        return Configuration.ToolsAPIBasePath;
    }
}

Знімок екрана: введіть тут опис зображення

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.