web-dev-qa-db-fra.com

Quel est le => affectation en C # dans une signature de propriété

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 l'utiliser de cette façon.

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

public int MaxHealth  = x ? y:z;
206
Mike

Ce que vous regardez est un membre avec expression pas une expression lambda.

Lorsque le compilateur rencontre un membre property avec une expression, il le convertit essentiellement en getter comme ceci:

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

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

Les membres à corps d'expression - comme la plupart des fonctionnalités de C # 6 - sont just sucre syntaxique . Cela signifie qu’ils ne fournissent pas de fonctionnalités qui ne pourraient être obtenues autrement avec les fonctionnalités existantes. Au lieu de cela, ces nouvelles fonctionnalités permettent d’utiliser une syntaxe plus expressive et succincte.

Comme vous pouvez le constater, les membres avec 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 instruction return car le compilateur peut en 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 mot clé get car il est impliqué par l'utilisation de la syntaxe du membre comportant une expression.

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

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-ce la même chose 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.

Soyons clairs: la première liste est une propriété avec un getter sous le capot qui sera appelée à chaque fois que vous y accédez. La deuxième liste est un champ avec un initialiseur de champ, dont l’expression n’est évaluée qu’une fois, lorsque le type est instancié.

Cette différence de syntaxe est en réalité 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 ayant le corps d'une expression soient lambda expression - comme , ils sont et non lambda. La différence fondamentale est qu'une expression lambda donne lieu à une instance de délégué ou à un arbre d'expression. Les membres d'expression-body sont simplement une directive au compilateur pour générer une propriété dans les coulisses. La similitude (plus ou moins) commence et se termine par la flèche (=>).

J'ajouterai également que les membres avec corps 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.

Cependant, ils ne travaillent pas sur ces membres:

  • Types imbriqués
  • Événements
  • Des champs
338
Alex Booker

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

Bien qu'il soit considéré sucre syntaxique pour les suivants, ils ne peuvent produire un 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 d'elles, vous verrez qu'elles sont PRESQUE identiques.

Voici l'IL de la version classique de cette réponse 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 pour la version de membre avec corps d'expression 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 sur cette fonctionnalité et sur les nouvelles fonctionnalités de C # 6.

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

Mise à jour:

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

32
Tyree Jackson

Ok ... J'ai fait un commentaire sur le fait 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 cela très bien, et j'ai cherché le bon lien pour le comprendre moi-même.

Dans ma situation, ma propriété initialisait automatiquement une commande dans un ViewModel pour une vue. J'ai changé la propriété pour utiliser un initialiseur d'expression 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 j'ai changé pour.

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

La différence est que lorsque j'utilise { get; } =, je crée et référence la commande SAME dans cette propriété. Lorsque j'utilise =>, je crée une nouvelle commande et la retourne chaque fois que la propriété est appelée. Par conséquent, je ne pouvais jamais mettre à jour le CanExecute de ma commande car je lui disais toujours de mettre à jour une nouvelle référence de cette commande.

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

Cela étant dit, si vous pointez simplement sur un champ de support, cela fonctionne bien. Cela se produit uniquement lorsque le corps auto ou d'expression crée la valeur de retour.

26
Michael Puckett II

Il s'appelle Expression Bodied Member et il a été introduit en C # 6. Il s'agit simplement d'un sucre syntaxique recouvrant une propriété get only.

Cela é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";

Cela vous permet principalement de raccourcir le passe-partout.

16
Yuval Itzchakov

Un autre point important si vous utilisez le C # 6:

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

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

6
Chris Halcrow