Qual é a atribuição => em C # em uma assinatura de propriedade

229

Me deparei com um código que dizia

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

Agora estou familiarizado com as expressões Lambda. Eu simplesmente não o vi dessa maneira.

Qual seria a diferença entre a afirmação acima e

public int MaxHealth  = x ? y:z;
Mike
fonte
4
primeiro bloco é propriedade segunda é variável
M.kazem Akhgary
14
@ M.kazemAkhgary * um campo, não uma variável.
Mafii 26/08/16

Respostas:

376

O que você está vendo é um membro com expressão, não uma expressão lambda.

Quando o compilador encontra um membro da propriedade do corpo da expressão , ele basicamente o converte em um getter como este:

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

(Você pode verificar isso por si mesmo bombeando o código para uma ferramenta chamada TryRoslyn .)

Membros com corpo de expressão - como a maioria dos recursos do C # 6 - são apenas açúcar sintático . Isso significa que eles não fornecem funcionalidades que não poderiam ser alcançadas por meio de recursos existentes. Em vez disso, esses novos recursos permitem que uma sintaxe mais expressiva e sucinta seja usada

Como você pode ver, os membros com expressão têm vários atalhos que tornam os membros da propriedade mais compactos:

  • Não há necessidade de usar uma returninstrução porque o compilador pode inferir que você deseja retornar o resultado da expressão
  • Não há necessidade de criar um bloco de instruções porque o corpo é apenas uma expressão
  • Não há necessidade de usar a getpalavra-chave, porque está implícita no uso da sintaxe do membro com corpo de expressão.

Tornei o ponto final em negrito, porque é relevante para a sua pergunta real, que responderei agora.

A diferença entre...

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

E...

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

É o mesmo que a diferença entre ...

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

E...

public int MaxHealth = x ? y:z;

O que - se você entende as propriedades - deve ser óbvio.

Apenas para ficar claro: a primeira listagem é uma propriedade com um getter sob o capô que será chamada sempre que você acessá-la. A segunda listagem é um campo com um inicializador de campos, cuja expressão é avaliada apenas uma vez, quando o tipo é instanciado.

Essa diferença de sintaxe é realmente bastante sutil e pode levar a uma "pegadinha", descrita por Bill Wagner em um post intitulado "Pegada da AC # 6: inicialização versus membros corporados de expressão" .

Enquanto membros com expressão corporal são semelhantes a expressões lambda , eles não são expressões lambda. A diferença fundamental é que uma expressão lambda resulta em uma instância delegada ou em uma árvore de expressão. Membros com corpo de expressão são apenas uma diretiva para o compilador para gerar uma propriedade nos bastidores. A semelhança (mais ou menos) começa e termina com a seta ( =>).

Também acrescentarei que membros com expressão corporal não se limitam a membros de propriedade. Eles trabalham em todos esses membros:

  • Propriedades
  • Indexadores
  • Métodos
  • Operadores

Adicionado no C # 7.0

No entanto, eles não funcionam com esses membros:

  • Tipos aninhados
  • Eventos
  • Campos
Alex Booker
fonte
6
A partir do C # 7, construtores e finalizadores também são suportados. docs.microsoft.com/pt-br/dotnet/csharp/programming-guide/…
bzier 05/04
8
@bzier É uma conspiração nos tornar programadores funcionais. SE ENTÃO MAIS PARA SEMPRE !!
Sentinel
Resposta super impressionante!
Jaime Arroyo Garcia
2
O link para a postagem de Bill Wagner está quebrado no momento. Acho que encontrei o novo URL: codeproject.com/Articles/1064964/…
Fry Simpson
36

Ok ... Fiz um comentário de que eram diferentes, mas não conseguiam explicar exatamente como, mas agora eu sei.

String Property { get; } = "value";

não é o mesmo que

String Property => "value";

Aqui está a diferença ...

Quando você usa o inicializador automático, a propriedade cria a instância do valor e usa esse valor persistentemente. No post acima, há um link quebrado para Bill Wagner, que explica isso bem, e eu procurei no link correto para entender eu mesmo.

Na minha situação, minha propriedade inicializou automaticamente um comando em um ViewModel para um View. Alterei a propriedade para usar o inicializador corporativo de expressão e o comando CanExecute parou de funcionar.

Aqui está o que parecia e aqui está o que estava acontecendo.

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

aqui está o que eu mudei para.

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

A diferença aqui é que quando eu uso { get; } =, crio e faço referência ao mesmo comando nessa propriedade. Quando uso =>, na verdade, crio um novo comando e o retorno sempre que a propriedade é chamada. Portanto, nunca pude atualizar o CanExecutecomando, pois estava sempre dizendo para atualizar uma nova referência desse comando.

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

Tudo isso dito, se você está apenas apontando para um campo de apoio, ele funciona bem. Isso só acontece quando o corpo automático ou de expressão cria o valor de retorno.

Michael Puckett II
fonte
8
A sintaxe => é igual ao get {return new Command (); } sintaxe.
Mafii
35

Esse é um novo recurso do C # 6 chamado membro incorporado da expressão que permite definir uma propriedade getter only usando uma função semelhante a lambda.

Embora seja considerado açúcar sintático para o seguinte, eles podem não produzir IL idêntica:

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

Acontece que, se você compilar as duas versões acima e comparar o IL gerado para cada uma delas, verá que elas são quase as mesmas.

Aqui está o IL da versão clássica nesta resposta quando definida em uma classe denominada 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

E aqui está o IL da versão do membro corporativo da expressão quando definido em uma classe denominada 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 obter mais informações sobre esse e outros novos recursos do C # 6.

Veja este post Diferença entre propriedade e campo em C # 3.0+ sobre a diferença entre um campo e um getter de propriedade em c #.

Atualizar:

Observe que os membros com expressão foram expandidos para incluir propriedades, construtores, finalizadores e indexadores no C # 7.0.

Tyree Jackson
fonte
16

É chamado de Membro Corporado da Expressão e foi introduzido no C # 6. É apenas açúcar sintático sobre uma getúnica propriedade.

É equivalente a:

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

Um equivalente de uma declaração de método está disponível:

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

Principalmente permitindo o encurtamento do clichê.

Yuval Itzchakov
fonte
7

Outro ponto significativo se você estiver usando o C # 6:

'=>' pode ser usado em vez de 'get' e é apenas para os métodos 'get only' - não pode ser usado com um 'set'.

Para o C # 7, veja o comentário de @avenmore abaixo - agora ele pode ser usado em mais lugares. Aqui está uma boa referência - https://csharp.christiannagel.com/2017/01/25/expressionbodiedmembers/

Chris Halcrow
fonte
8
Não é mais verdadeiro se você estiver usando o C # 7. "O C # 7.0 continua com aprimoramentos de produtividade. Os membros com expressão estavam disponíveis no C # 6 para métodos e propriedades, agora eles podem ser usados ​​com construtores, destruidores, acessadores de propriedades e acessadores de eventos. também." ( Source )
avenmore
1

Pela seguinte declaração compartilhada por Alex Booker em sua resposta

Quando o compilador encontra um membro da propriedade do corpo da expressão, ele basicamente o converte em um getter como este:

Por favor, veja a captura de tela a seguir , mostra como esta declaração (usando o link SharpLab )

public string APIBasePath => Configuration.ToolsAPIBasePath;

converte para

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

Captura de tela: insira a descrição da imagem aqui

shakeel
fonte