I came across some code that said
public int MaxHealth =>
Memory[Address].IsValid ?
Memory[Address].Read(Offs.Life.MaxHp)
One other significant point if you're using C# 6:
'=>' can be used instead of 'get' and is only for 'get only' methods - it can't be used with a 'set'.
For C# 7, see the comment from @avenmore below - it can now be used in more places. Here's a good reference - https://csharp.christiannagel.com/2017/01/25/expressionbodiedmembers/
Ok... I made a comment that they were different but couldn't explain exactly how but now I know.
String Property { get; } = "value";
is not the same as
String Property => "value";
Here's the difference...
When you use the auto initializer the property creates the instance of value and uses that value persistently. In the above post there is a broken link to Bill Wagner, that explains this well, and I searched the correct link to understand it myself.
In my situation I had my property auto initialize a command in a ViewModel for a View. I changed the property to use expression bodied initializer and the command CanExecute stopped working.
Here's what it looked like and here's what was happening.
Command MyCommand { get; } = new Command(); //works
here's what I changed it to.
Command MyCommand => new Command(); //doesn't work properly
The difference here is when I use { get; } =
I create and reference the SAME command in that property. When I use =>
I actually create a new command and return it every time the property is called. Therefore, I could never update the CanExecute
on my command because I was always telling it to update a new reference of that command.
{ get; } = // same reference
=> // new reference
All that said, if you are just pointing to a backing field then it works fine. This only happens when the auto or expression body creates the return value.
You can even write this:
private string foo = "foo";
private string bar
{
get => $"{foo}bar";
set
{
foo = value;
}
}
For the following statement shared by Alex Booker in their answer
When the compiler encounters an expression-bodied property member, it essentially converts it to a getter like this:
Please see the following screenshot, it shows how this statement (using SharpLab link)
public string APIBasePath => Configuration.ToolsAPIBasePath;
converts to
public string APIBasePath
{
get
{
return Configuration.ToolsAPIBasePath;
}
}
Screenshot:
It is called Expression Bodied Member and it was introduced in C# 6. It is merely syntactic sugar over a get
only property.
It is equivalent to:
public int MaxHealth { get { return Memory[Address].IsValid ?
Memory[Address].Read<int>(Offs.Life.MaxHp) : 0; }
An equivalent of a method declaration is avaliable:
public string HelloWorld() => "Hello World";
Mainly allowing you shortening of boilerplate.
This is a new feature of C# 6 called an expression bodied member that allows you to define a getter only property using a lambda like function.
While it is considered syntactic sugar for the following, they may not produce identical IL:
public int MaxHealth
{
get
{
return Memory[Address].IsValid
? Memory[Address].Read<int>(Offs.Life.MaxHp)
: 0;
}
}
It turns out that if you compile both versions of the above and compare the IL generated for each you'll see that they are NEARLY the same.
Here is the IL for the classic version in this answer when defined in a class named 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
And here is the IL for the expression bodied member version when defined in a class named 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
See https://msdn.microsoft.com/en-us/magazine/dn802602.aspx for more information on this and other new features in C# 6.
See this post Difference between Property and Field in C# 3.0+ on the difference between a field and a property getter in C#.
Update:
Note that expression-bodied members were expanded to include properties, constructors, finalizers and indexers in C# 7.0.