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

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

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

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

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

public int MaxHealth  = x ? y:z;
4b9b3361

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

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

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

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

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

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

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

  • Конструкторы
  • Deconstructors
  • Вложенные типы
  • События
  • поля
287
ответ дан 01 авг. '15 в 21:05
источник

Это новая функция С# 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+ о разнице между полем и средством получения свойства в С#.

32
ответ дан 01 авг. '15 в 20:54
источник

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

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

20
ответ дан 17 авг. '16 в 17:51
источник

Он называется 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";

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

16
ответ дан 01 авг. '15 в 20:53
источник

Еще одним важным моментом является то, что "=>" может использоваться вместо "get" и используется только для методов "get only" - его нельзя использовать с "set".

4
ответ дан 15 июня '17 в 3:39
источник