¿Cuál es la asignación => en C # en una firma de propiedad

229

Me encontré con un código que decía

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

Ahora estoy algo familiarizado con las expresiones Lambda. Simplemente no he visto que lo use de esta manera.

¿Cuál sería la diferencia entre la declaración anterior y

public int MaxHealth  = x ? y:z;
Miguel
fuente
44
el primer bloque es propiedad, el segundo es variable
M.kazem Akhgary
14
@ M.kazemAkhgary * un campo, no una variable.
Mafii

Respuestas:

376

Lo que estás viendo es un miembro con cuerpo de expresión, no una expresión lambda.

Cuando el compilador encuentra un miembro de propiedad con cuerpo de expresión , esencialmente lo convierte en un captador como este:

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

(Puede verificar esto usted mismo bombeando el código a una herramienta llamada TryRoslyn ).

Los miembros con cuerpo de expresión, como la mayoría de las características de C # 6, son solo azúcar sintáctica . Esto significa que no proporcionan una funcionalidad que de otro modo no se podría lograr a través de las funciones existentes. En cambio, estas nuevas características permiten utilizar una sintaxis más expresiva y sucinta

Como puede ver, los miembros con cuerpo de expresión tienen un puñado de atajos que hacen que los miembros de propiedad sean más compactos:

  • No es necesario usar una returndeclaración porque el compilador puede inferir que desea devolver el resultado de la expresión
  • No es necesario crear un bloque de instrucción porque el cuerpo es solo una expresión
  • No es necesario usar la getpalabra clave porque está implícita en el uso de la sintaxis de miembro con cuerpo de expresión.

He puesto el último punto en negrita porque es relevante para su pregunta real, que responderé ahora.

La diferencia entre...

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

Y...

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

Es lo mismo que la diferencia entre ...

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

Y...

public int MaxHealth = x ? y:z;

Lo cual, si comprende las propiedades, debería ser obvio.

Sin embargo, para ser claros: la primera lista es una propiedad con un captador debajo del capó que se llamará cada vez que acceda a ella. La segunda lista es un campo con un inicializador de campo, cuya expresión solo se evalúa una vez, cuando se instancia el tipo.

Esta diferencia en la sintaxis es en realidad bastante sutil y puede conducir a un "problema" que describe Bill Wagner en una publicación titulada "AC # 6 gotcha: Initialization vs. Expression Bodied Members" .

Mientras que los miembros de expresión lambda cuerpo son expresión- como , que son no las expresiones lambda. La diferencia fundamental es que una expresión lambda da como resultado una instancia delegada o un árbol de expresión. Los miembros con cuerpo de expresión son solo una directiva para que el compilador genere una propiedad detrás de escena. La similitud (más o menos) comienza y termina con la flecha ( =>).

También agregaré que los miembros con cuerpo de expresión no se limitan a los miembros de la propiedad. Trabajan en todos estos miembros:

  • Propiedades
  • Indexadores
  • Métodos
  • Operadores

Agregado en C # 7.0

Sin embargo, no trabajan en estos miembros:

  • Tipos anidados
  • Eventos
  • Campos
Alex Booker
fuente
66
A partir de C # 7, también se admiten constructores y finalizadores. docs.microsoft.com/en-us/dotnet/csharp/programming-guide/…
bzier
8
@bzier Es una conspiración para hacernos programadores funcionales. SI ENTONCES MÁS por siempre !!
Sentinel
Súper respuesta impresionante!
Jaime Arroyo García
2
El enlace a la publicación de Bill Wagner está roto actualmente. Creo que encontré la nueva URL: codeproject.com/Articles/1064964/…
Fry Simpson
36

Ok ... Hice un comentario de que eran diferentes pero no podía explicar exactamente cómo, pero ahora lo sé.

String Property { get; } = "value";

no es lo mismo que

String Property => "value";

Aquí está la diferencia ...

Cuando usa el inicializador automático, la propiedad crea la instancia de valor y usa ese valor de forma persistente. En la publicación anterior hay un enlace roto a Bill Wagner, que explica esto bien, y busqué el enlace correcto para entenderlo yo mismo.

En mi situación, mi propiedad inicializó automáticamente un comando en un ViewModel para una Vista. Cambié la propiedad para usar el inicializador con cuerpo de expresión y el comando CanExecute dejó de funcionar.

Esto es lo que parecía y esto es lo que estaba sucediendo.

Command MyCommand { get; } = new Command();  //works

esto es a lo que lo cambié.

Command MyCommand => new Command();  //doesn't work properly

La diferencia aquí es que cuando uso { get; } =creo y hago referencia al mismo comando en esa propiedad. Cuando uso =>realmente creo un nuevo comando y lo devuelvo cada vez que se llama a la propiedad. Por lo tanto, nunca pude actualizar el CanExecutecomando porque siempre le decía que actualizara una nueva referencia de ese comando.

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

Dicho todo esto, si solo está apuntando a un campo de respaldo, entonces funciona bien. Esto solo ocurre cuando el cuerpo automático o de expresión crea el valor de retorno.

Michael Puckett II
fuente
8
La sintaxis => es igual a get {return new Command (); } sintaxis.
Mafii
35

Esta es una nueva característica de C # 6 llamada miembro con cuerpo de expresión que le permite definir una propiedad getter usando una función similar a lambda.

Si bien se considera azúcar sintáctico para lo siguiente, es posible que no produzcan IL idéntica:

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

Resulta que si compila ambas versiones de lo anterior y compara la IL generada para cada una, verá que son casi iguales.

Aquí está el IL para la versión clásica en esta respuesta cuando se define en una clase llamada 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

Y aquí está el IL para la versión del miembro con cuerpo de expresión cuando se define en una clase llamada 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

Consulte https://msdn.microsoft.com/en-us/magazine/dn802602.aspx para obtener más información sobre esta y otras características nuevas en C # 6.

Vea esta publicación Diferencia entre propiedad y campo en C # 3.0+ sobre la diferencia entre un campo y un captador de propiedad en C #.

Actualizar:

Tenga en cuenta que los miembros con cuerpo de expresión se expandieron para incluir propiedades, constructores, finalizadores e indexadores en C # 7.0.

Tyree Jackson
fuente
16

Se llama Expression Bodied Member y se introdujo en C # 6. Es simplemente azúcar sintáctico sobre una getúnica propiedad.

Es equivalente a:

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

Está disponible un equivalente de una declaración de método:

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

Principalmente permitiéndole acortar de repetitivo.

Yuval Itzchakov
fuente
7

Otro punto importante si está usando C # 6:

'=>' se puede usar en lugar de 'get' y es solo para métodos 'get only' ; no se puede usar con un 'set'.

Para C # 7, vea el comentario de @avenmore a continuación: ahora se puede usar en más lugares. Aquí hay una buena referencia: https://csharp.christiannagel.com/2017/01/25/expressionbodiedmembers/

Chris Halcrow
fuente
8
Ya no es cierto si está usando C # 7 ". C # 7.0 continúa con mejoras de productividad. Los miembros con cuerpo de expresión han estado disponibles con C # 6 para métodos y propiedades, ahora se pueden usar con constructores, destructores, accesores de propiedades y accesores de eventos también." ( Fuente )
avenmore
1

Para la siguiente declaración compartida por Alex Booker en su respuesta

Cuando el compilador encuentra un miembro de propiedad con cuerpo de expresión, esencialmente lo convierte en un captador como este:

Consulte la siguiente captura de pantalla , muestra cómo funciona esta declaración (usando el enlace SharpLab )

public string APIBasePath => Configuration.ToolsAPIBasePath;

se convierte a

public string APIBasePath
{
    get
    {
        return Configuration.ToolsAPIBasePath;
    }
}

Captura de pantalla: ingrese la descripción de la imagen aquí

Shakeel
fuente