Qué es la asignación => en C# en una firma de propiedad


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 un poco familiarizado con las expresiones Lambda. Solo que no lo he visto usarlo de esta manera.

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

public int MaxHealth  = x ? y:z;
 165
Author: DaveInCaz, 2015-08-01

5 answers

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

Cuando el compilador encuentra un miembro de la propiedad con cuerpo de expresión , esencialmente lo convertirá en un get ter, así:

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

(Puede verificar esto por sí mismo introduciendo el código en una herramienta llamada TryRoslyn.)

Los miembros con cuerpo de expresión - como la mayoría de las características de C# 6-son azúcar sintáctico . Esto significa que no proporcionan una funcionalidad que de otra manera no se podría lograr a través de las características 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 hay necesidad de usar una instrucción return porque el compilador puede inferir que desea devolver el resultado de la expresión
  • No hay necesidad de crear una declaración bloquear porque el cuerpo es solo una expresión
  • No hay necesidad de usar la palabra clave get porque está implícita por el uso de la sintaxis de miembros con cuerpo de expresión.

He hecho el punto final audaz 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;

Que-si usted entender las propiedades-debe ser obvio.

Para que quede claro, sin embargo: el primer listado es una propiedad con un getter debajo del capó que se llamará cada vez que acceda a ella. El segundo listado es un campo con un inicializador de campos, cuya expresión solo se evalúa una vez, cuando el tipo es instanciado.

Esta diferencia en la sintaxis es en realidad bastante sutil y puede conducir a un "gotcha" que es descrito por Bill Wagner en un post titulado "A C # 6 gotcha: Inicialización vs. Miembros con cuerpo de Expresión".

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

Voy a también agregue que los miembros con cuerpo de expresión no están limitados a miembros de propiedad. Ellos trabajan en todos estos miembros:

  • Propiedades
  • Indexadores
  • Métodos
  • Operadores

Sin embargo, no trabajan en estos miembros:

  • Constructores
  • Deconstructores
  • Tipos anidados
  • Eventos
  • Campos
 261
Author: Alex Booker,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2016-08-24 11:04:55

Esta es una nueva característica de C# 6 llamada expresión bodied member que le permite definir una propiedad getter only usando una función como lambda.

Si bien se considera azúcar sintáctico para lo siguiente, no pueden producir IL idéntica:

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

Resulta que si compilas ambas versiones de lo anterior y comparas el IL generado para cada una verás que son CASI lo mismo.

Aquí está la 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á la IL para la expresión bodied member version 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

Véase 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.

Ver este post Diferencia entre Propiedad y Campo en C# 3.0+ sobre la diferencia entre un campo y un captador de propiedad en C#.

 30
Author: Tyree Jackson,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-05-23 12:10:41

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

String Property { get; } = "value";

No Es lo mismo que

Property => value

Aquí está la diferencia...

Cuando se utiliza el inicializador automático, la propiedad crea la instancia de valor y utiliza ese valor de forma persistente. En el post 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 tenía mi propiedad auto inicializar un comando en un ViewModel para una Vista. Cambié la propiedad para usar el inicializador de expresión bodied 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 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 CanExecute en mi comando porque siempre le estaba diciendo que actualizara una nueva referencia de ese comando.

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

Dicho esto, si solo está apuntando a un campo de respaldo, entonces funciona bien. Esto solo sucede cuando el cuerpo auto o de expresión crea el valor devuelto.

 19
Author: Michael Puckett II,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2016-08-17 16:35:02

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

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 lo que le permite acortar el repetitivo.

 14
Author: Yuval Itzchakov,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2015-08-01 20:44:32

En otro punto significativo es que '=>' se puede usar en lugar de 'get' y es solo para los métodos 'get only' - no se puede usar con un 'set' .

 3
Author: Chris Halcrow,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-06-15 00:39:05