Quelle est l'affectation => en C # dans une signature de propriété

229

Je suis tombé sur un code qui disait

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

Maintenant, je connais un peu les expressions Lambda. Je ne l'ai tout simplement pas vu utilisé de cette façon.

Quelle serait la différence entre la déclaration ci-dessus et

public int MaxHealth  = x ? y:z;
Mike
la source
4
le premier bloc est la propriété le second est variable
M.kazem Akhgary
14
@ M.kazemAkhgary * un champ, pas une variable.
Mafii

Réponses:

376

Ce que vous regardez est un membre d'expression corporelle et non une expression lambda.

Lorsque le compilateur rencontre un membre de propriété doté d'un corps d'expression , il le convertit essentiellement en un getter comme celui-ci:

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

(Vous pouvez le vérifier par vous-même en injectant le code dans un outil appelé TryRoslyn .)

Les membres dotés d'une expression - comme la plupart des fonctionnalités C # 6 - ne sont que du sucre syntaxique . Cela signifie qu'ils ne fournissent pas de fonctionnalités qui n'auraient pas pu être obtenues autrement grâce aux fonctionnalités existantes. Au lieu de cela, ces nouvelles fonctionnalités permettent d'utiliser une syntaxe plus expressive et succincte

Comme vous pouvez le voir, les membres dotés d'une expression ont une poignée de raccourcis qui rendent les membres de propriété plus compacts:

  • Il n'est pas nécessaire d'utiliser une returninstruction car le compilateur peut déduire que vous souhaitez renvoyer le résultat de l'expression
  • Il n'est pas nécessaire de créer un bloc d'instructions car le corps n'est qu'une expression
  • Il n'est pas nécessaire d'utiliser le getmot - clé car il est impliqué par l'utilisation de la syntaxe des membres avec corps d'expression.

J'ai mis le dernier point en gras, car il est pertinent pour votre question actuelle, à laquelle je vais répondre maintenant.

La différence entre...

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

Et...

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

Est la même que la différence entre ...

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

Et...

public int MaxHealth = x ? y:z;

Ce qui - si vous comprenez les propriétés - devrait être évident.

Pour être clair, cependant: la première annonce est une propriété avec un getter sous le capot qui sera appelée chaque fois que vous y accéderez. La deuxième liste est un champ avec un initialiseur de champ, dont l'expression n'est évaluée qu'une seule fois, lorsque le type est instancié.

Cette différence de syntaxe est en fait assez subtile et peut conduire à un "gotcha" qui est décrit par Bill Wagner dans un article intitulé "AC # 6 gotcha: Initialization vs. Expression Bodied Members" .

Bien que les membres dotés d'une expression ressemblent à une expression lambda , ils ne sont pas des expressions lambda. La différence fondamentale est qu'une expression lambda aboutit à une instance déléguée ou à un arbre d'expression. Les membres dotés d'expressions ne sont qu'une directive adressée au compilateur pour générer une propriété en arrière-plan. La similitude (plus ou moins) commence et se termine par la flèche ( =>).

J'ajouterai également que les membres d'expression ne sont pas limités aux membres de propriété. Ils travaillent sur tous ces membres:

  • Propriétés
  • Indexeurs
  • Les méthodes
  • Les opérateurs

Ajouté dans C # 7.0

Cependant, ils ne fonctionnent pas sur ces membres:

  • Types imbriqués
  • Événements
  • Des champs
Alex Booker
la source
6
Depuis C # 7, les constructeurs et les finaliseurs sont également pris en charge. docs.microsoft.com/en-us/dotnet/csharp/programming-guide/…
bzier
8
@bzier C'est un complot pour faire de nous des programmeurs fonctionnels. SI ALORS AILLEURS pour toujours !!
Sentinel
Réponse super géniale!
Jaime Arroyo Garcia
2
Le lien vers le billet de Bill Wagner est actuellement rompu. Je pense avoir trouvé la nouvelle URL: codeproject.com/Articles/1064964/…
Fry Simpson
36

Ok ... J'ai fait un commentaire qu'ils étaient différents mais ne pouvaient pas expliquer exactement comment mais maintenant je sais.

String Property { get; } = "value";

n'est pas la même chose que

String Property => "value";

Voici la différence ...

Lorsque vous utilisez l'initialiseur automatique, la propriété crée l'instance de valeur et utilise cette valeur de manière persistante. Dans le post ci-dessus, il y a un lien brisé vers Bill Wagner, cela explique bien cela, et j'ai cherché le bon lien pour le comprendre moi-même.

Dans ma situation, j'avais ma propriété initialiser automatiquement une commande dans un ViewModel pour une vue. J'ai modifié la propriété pour utiliser l'initialiseur d'expression corporel et la commande CanExecute a cessé de fonctionner.

Voici à quoi cela ressemblait et voici ce qui se passait.

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

voici ce que je l'ai changé.

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

La différence ici est lorsque j'utilise, { get; } =je crée et référence la même commande dans cette propriété. Lorsque j'utilise, =>je crée en fait une nouvelle commande et la renvoie à chaque appel de la propriété. Par conséquent, je n'ai jamais pu mettre à jour le CanExecutesur ma commande car je lui disais toujours de mettre à jour une nouvelle référence de cette commande.

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

Cela dit, si vous pointez simplement vers un champ de support, cela fonctionne très bien. Cela se produit uniquement lorsque le corps auto ou expression crée la valeur de retour.

Michael Puckett II
la source
8
La syntaxe => est égale à get {return new Command (); } syntaxe.
Mafii
35

Il s'agit d'une nouvelle fonctionnalité de C # 6 appelée membre bodied d'expression qui vous permet de définir une propriété getter uniquement à l'aide d'une fonction de type lambda.

Bien qu'il soit considéré comme du sucre syntaxique pour les éléments suivants, ils peuvent ne pas produire d'IL identique:

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

Il s'avère que si vous compilez les deux versions de ce qui précède et comparez l'IL généré pour chacune, vous verrez qu'elles sont presque les mêmes.

Voici l'IL de la version classique dans cette réponse lorsqu'elle est définie dans une classe nommée 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

Et voici l'IL de la version de membre de l'expression corporelle lorsqu'elle est définie dans une classe nommée 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

Voir https://msdn.microsoft.com/en-us/magazine/dn802602.aspx pour plus d'informations à ce sujet et d'autres nouvelles fonctionnalités de C # 6.

Voir cet article Différence entre propriété et champ en C # 3.0+ sur la différence entre un champ et un getter de propriété en C #.

Mettre à jour:

Notez que les membres dotés d'une expression ont été développés pour inclure des propriétés, des constructeurs, des finaliseurs et des indexeurs dans C # 7.0.

Tyree Jackson
la source
16

Il s'appelle Expression Bodied Member et a été introduit en C # 6. Il s'agit simplement de sucre syntaxique sur une getseule propriété.

Il équivaut à:

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

Un équivalent d'une déclaration de méthode est disponible:

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

Vous permettant principalement de raccourcir le passe-partout.

Yuval Itzchakov
la source
7

Un autre point important si vous utilisez C # 6:

'=>' peut être utilisé au lieu de 'get' et est uniquement pour les méthodes 'get only' - il ne peut pas être utilisé avec un 'set'.

Pour C # 7, voir le commentaire de @avenmore ci-dessous - il peut maintenant être utilisé dans plus d'endroits. Voici une bonne référence - https://csharp.christiannagel.com/2017/01/25/expressionbodiedmembers/

Chris Halcrow
la source
8
Ce n'est plus vrai si vous utilisez C # 7. "C # 7.0 continue avec des améliorations de productivité. Les membres dotés d'expressions étaient disponibles avec C # 6 pour les méthodes et les propriétés, maintenant ils peuvent être utilisés avec des constructeurs, des destructeurs, des accesseurs de propriété et des accesseurs d'événement ainsi que." ( Source )
avenmore
1

Pour la déclaration suivante partagée par Alex Booker dans leur réponse

Lorsque le compilateur rencontre un membre de propriété doté d'une expression, il le convertit essentiellement en un getter comme celui-ci:

Veuillez voir la capture d'écran suivante , elle montre comment cette déclaration (en utilisant le lien SharpLab )

public string APIBasePath => Configuration.ToolsAPIBasePath;

convertit en

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

Capture d'écran: entrez la description de l'image ici

shakeel
la source