What is the => assignment in C# in a property signature

I came across some code that said

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

Now I am somewhat familiar with Lambda expressions. I just have not seen it used it this way.

What would be the difference between the above statement and

public int MaxHealth  = x ? y:z;

What you're looking at is an expression-bodied member, not a lambda expression.

When the compiler encounters an expression-bodied property member, it will essentially convert it into a get ter, like this:

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

(You can verify this for yourself by pumping the code into a tool called TryRoslyn.)

Expression-bodied members - like most C# 6 features - are just syntactic sugar. This means that they don't provide functionality that couldn't otherwise be achieved through existing features. Instead, these new features allow a more expressive and succinct syntax to be used

As you can see, expression-bodied members have a handful of shortcuts that make property members more compact:

  • There is no need to use a return statement because the compiler can infer that you want to return the result of the expression
  • There is no need to create a statement block because the body is only one expression
  • There is no need to use the get keyword because it is implied by the use of the expression-bodied member syntax.
  • I have made the final point bold because it is relevant to your actual question, which I will answer now.

    The difference between...

    // expression-bodied member property
    public int MaxHealth => x ? y:z;
    

    And...

    // field with field initializer
    public int MaxHealth = x ? y:z;
    

    Is the same as the difference between...

    public int MaxHealth
    {
        get
        {
            return x ? y:z;
        }
    }
    

    And...

    public int MaxHealth = x ? y:z;
    

    Which - if you understand properties - should be obvious.

    Just to be clear, though: the first listing is a property with a getter under the hood that will be called each time you access it. The second listing is is a field with a field initializer, whose expression is only evaluated once, when the type is instantiated.

    This difference in syntax is actually quite subtle and can lead to a "gotcha" which is described by Bill Wagner in a post entitled "AC# 6 gotcha: Initialization vs. Expression Bodied Members".

    While expression-bodied members are lambda expression-like, they are not lambda expressions. The fundamental difference is that a lambda expression results in either a delegate instance or an expression tree. Expression-bodied members are just a directive to the compiler to generate a property behind the scenes. The similarity (more or less) starts and end with the arrow ( => ).

    I'll also add that expression-bodied members are not limited to property members. They work on all these members:

  • Properties
  • Indexers
  • Methods
  • Operators
  • However, they do not work on these members:

  • Constructors
  • Deconstructors
  • Nested Types
  • Events
  • Fields

  • 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#.


    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

    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; } = { 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.

    链接地址: http://www.djcxy.com/p/75172.html

    上一篇: 在C#6.0中体现函数成员的效率和性能

    下一篇: 在属性签名中,C#中的赋值是什么?