Что такое присвоение => в 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

То, на что вы смотрите, это член с выражением тела, а не лямбда-выражение.

Когда компилятор встречает элемент свойства с выражением-выражением , он по сути преобразует его в метод получения, подобный этому:

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: Инициализация против членов тела с выражением» .

Хотя члены с выражением тела похожи на лямбда-выражения , они не являются лямбда-выражениями. Принципиальное отличие состоит в том, что лямбда-выражение приводит либо к экземпляру делегата, либо к дереву выражений. Элементы с выражением в выражении - это просто директива для компилятора для создания свойства за сценой. Сходство (более или менее) начинается и заканчивается стрелкой ( =>).

Я также добавлю, что члены с выраженным выражением не ограничены членами собственности. Они работают на всех этих членов:

  • свойства
  • индексаторы
  • методы
  • операторы

Добавлено в C # 7.0

Тем не менее, они не работают над этими членами:

  • Вложенные типы
  • События
  • поля
Алекс Букер
источник
6
Начиная с C # 7, конструкторы и финализаторы также поддерживаются. docs.microsoft.com/en-us/dotnet/csharp/programming-guide/...
bzier
8
@bzier Это заговор, чтобы сделать нас функциональными программистами. ЕСЛИ ЭТО ДРУГОЕ навсегда !!
Сентин
Супер классный ответ!
Хайме Арройо Гарсия
2
Ссылка на пост Билла Вагнера в настоящее время не работает. Я думаю, что нашел новый URL: codeproject.com/Articles/1064964/…
Фрай Симпсон,
36

Хорошо ... Я сделал комментарий, что они разные, но не могу точно объяснить, как, но теперь я знаю.

String Property { get; } = "value";

это не то же самое, что

String Property => "value";

Вот разница ...

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

В моей ситуации у меня было свойство автоматически инициализировать команду в ViewModel для View. Я изменил свойство, чтобы использовать инициализатор с выражением bodied, и команда CanExecute перестала работать.

Вот как это выглядело, и вот что происходило.

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

вот что я изменил.

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

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

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

Все это говорит, что если вы просто указываете на вспомогательное поле, то это работает нормально. Это происходит только тогда, когда тело auto или expression создает возвращаемое значение.

Майкл Пакетт II
источник
8
Синтаксис => равен get {return new Command (); } синтаксис.
Мафия
35

Это новая функция C # 6, называемая членом с выражением, которая позволяет вам определять свойство только для получения, используя лямбда-подобную функцию.

Хотя это считается синтаксическим сахаром для следующего, они могут не производить идентичный IL:

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

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

Вот IL для классической версии в этом ответе, когда она определена в классе с именем 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 для версии члена в теле выражения, когда он определен в классе с именем 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 Member и был введен в 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 для методов и свойств, теперь их можно использовать с конструкторами, деструкторами, средствами доступа к свойствам и средствами доступа к событиям также." ( Источник )
Авенмор
1

За следующее утверждение поделился Алекс Букер в своем ответе

Когда компилятор встречает элемент свойства с выражением-выражением, он по сути преобразует его в метод получения, подобный этому:

Пожалуйста, смотрите следующий скриншот , он показывает, как это утверждение (используя ссылку SharpLab )

public string APIBasePath => Configuration.ToolsAPIBasePath;

превращается в

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

Скриншот: введите описание изображения здесь

Шакил
источник