Что такое присвоение => в С# в сигнатуре свойства

Я наткнулся на код, который сказал

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

Теперь я немного знаком с лямбда-выражениями. Я просто не видел, чтобы он использовал это таким образом.

Какая разница между приведенным выше утверждением и

public int MaxHealth  = x ? y:z;

Ответ 1

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

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

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

(Вы можете убедиться в этом сами, накачав код в инструмент под названием TryRoslyn.)

Члены с выраженным выражением - как и большинство функций С# 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;

Который - если вы понимаете свойства - должен быть очевидным.

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

Это различие в синтаксисе на самом деле довольно тонкое и может привести к "погрешности", которая описана Биллом Вагнером в посте, озаглавленном "AС# 6 gotcha: Инициализация против членов тела с выражением".

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

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

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

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

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

  • Вложенные типы
  • События
  • поля

Ответ 2

Это новая функция С# 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 выражения, когда она определена в классе с именем 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 для получения дополнительной информации об этой и других новых функциях в С# 6.

См. Этот пост Разница между свойством и полем в С# 3. 0+ о разнице между полем и средством получения свойства в С#.

Обновить:

Обратите внимание, что члены с выражением тела были расширены для включения свойств, конструкторов, финализаторов и индексаторов в С# 7.0.

Ответ 3

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

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; } = { get; } = Я создаю и ссылаюсь на ту же команду в этом свойстве. Когда я использую => я фактически создаю новую команду и возвращаю ее каждый раз, когда вызывается свойство. Поэтому я никогда не мог обновить CanExecute в моей команде, потому что я всегда говорил ему обновить новую ссылку на эту команду.

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

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

Ответ 4

Он называется Expression Bodied Member, и он был введен в С# 6. Это просто синтаксический сахар поверх свойства get.

Это эквивалентно:

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

Доступен эквивалент объявления метода:

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

В основном разрешается сокращение шаблона.

Ответ 5

Еще один важный момент, если вы используете С# 6:

'=>' может использоваться вместо 'get' и используется только для методов 'get only' - его нельзя использовать с 'set'.

Для С# 7 см. Комментарий от @avenmore ниже - теперь его можно использовать в других местах. Здесь хорошая ссылка - https://csharp.christiannagel.com/2017/01/25/expressionbodiedmembers/