Что такое присвоение => в С# в сигнатуре свойства
Я наткнулся на код, который сказал
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/