Что такое присвоение => в 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 создает возвращаемое значение.


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

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

Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.