web-dev-qa-db-ja.com

プロパティシグネチャにおけるC#の=>代入とは何ですか

私は言ったいくつかのコードに遭遇しました

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

今、私はLambda式に多少慣れています。私はそれがこのようにそれを使ったのを見たことがありません。

上記のステートメントとの違いは何でしょう

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

あなたが見ているのはラムダ式ではなく 式本体 です。

コンパイラーが式本体プロパティーメンバーを検出すると、基本的にそれを次のようなゲッターに変換します。

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

(コードを TryRoslyn というツールに送り込むことでこれを確認できます。)

ほとんどのC#6機能と同様に、式を構成するメンバーは ちょうど構文糖 です。つまり、既存の機能では実現できなかった機能を提供していないということです。代わりに、これらの新機能により、より表現力豊かで簡潔な構文を使用できます。

ご覧のとおり、式ボディメンバーには、プロパティメンバーをよりコンパクトにするためのショートカットがいくつかあります。

  • returnステートメントを使用する必要はありません。なぜなら、コンパイラーは、式の結果を返したいと推論できるからです。
  • 本体は1つの式だけなので、ステートメントブロックを作成する必要はありません。
  • getキーワードは、式本体メンバー構文の使用によって暗黙的に指定されているため、使用する必要はありません。

私が今答えるあなたの実際の質問に関連しているので、私は最終的なポイントを大胆にしました。

違いは...

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

そして...

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

...との違いは同じです.

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

そして...

public int MaxHealth = x ? y:z;

それが - あなたが特性を理解している場合 - 明白であるべきです。

ただし、明確にするために、最初のリストは、アクセスするたびに呼び出される、フードの下にあるゲッターを持つプロパティです。 2番目のリストは、フィールド初期化子を持つフィールドです。フィールド初期化子は、型がインスタンス化されるときに1回だけ評価されます。

この構文の違いは実際には非常に微妙であり、Bill Wagnerによって "C#6 gotcha:初期化と式本体のメンバー" という題名の記事で説明されている。

式ボディメンバーはラムダ式 like ですが、 ではない ラムダ式です。基本的な違いは、ラムダ式はデリゲートインスタンスか式ツリーのどちらかになるということです。式本体メンバーは、舞台裏でプロパティを生成するためのコンパイラへの単なる指示です。類似度(多かれ少なかれ)は矢印(=>)で始まり、終わります。

また、表現体メンバーはプロパティメンバーに限定されないことを付け加えます。彼らはこれらすべてのメンバーに働きかけます:

  • プロパティ
  • インデクサー
  • 方法
  • オペレータ

C#7. に追加

しかし、彼らはこれらのメンバーには働きません:

  • 入れ子になった型
  • イベント
  • フィールド
324
Alex Booker

これはC#6の新機能で、式ボディメンバーと呼ばれ、ラムダ関数を使ってゲッター専用プロパティを定義できます。

それは以下のために 構文糖 と見なされますが、それらはではないかもしれませんが同一のILを生成します:

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

上記の両方のバージョンをコンパイルし、それぞれに対して生成されたILを比較すると、それらはほぼ同じであることがわかります。

これがTestClassという名前のクラスで定義された場合の、この回答のクラシック版のILです。

.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

そして、これがTestClassという名前のクラスで定義された場合の式ボディメンバーバージョンのILです。

.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

これとC#6の他の新機能の詳細については、 https://msdn.Microsoft.com/ja-jp/magazine/dn802602.aspx を参照してください。 。

C#でのフィールドとプロパティゲッターの違いについては、この投稿を参照してください C#3.0でのプロパティとフィールドの違い+

更新:

C#7.0では、式本体のメンバがプロパティ、コンストラクタ、ファイナライザ、インデクサを含むように拡張されたことに注意してください。

32
Tyree Jackson

わかりました...私はそれらが異なっていたが正確にどのように説明することができなかったかというコメントをしました、しかし今私は知っています。

String Property { get; } = "value";

と同じではありません

String Property => "value";

違いはここにあります...

自動初期化子を使用すると、プロパティはvalueのインスタンスを作成し、その値を永続的に使用します。上記の記事ではBill Wagnerへのリンクが壊れています。

私の状況では、ViewのViewModelで自分のプロパティにコマンドを自動初期化させました。プロパティを式本体イニシャライザを使用するように変更し、CanExecuteコマンドが機能しなくなりました。

これはそれがどのように見えたかであり、ここで起こっていたことです。

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

これは私がそれを変えたものです。

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

ここでの違いは、{ get; } =を使用したときに、そのプロパティでSAMEコマンドを作成して参照することです。 =>を使用するとき、私は実際に新しいコマンドを作成し、プロパティが呼び出されるたびにそれを返します。そのため、常にそのコマンドの新しい参照を更新するように指示していたため、コマンドのCanExecuteを更新することはできませんでした。

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

あなたがただバッキングフィールドを指しているだけなら、それはうまくいきます。これは、autoボディまたはexpressionボディが戻り値を作成したときにのみ発生します。

24

これは Expression Bodied Member と呼ばれ、C#6で導入されました。これはget onlyプロパティに対する単なる構文上の糖です。

これは以下と同等です。

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

メソッド宣言と同等のものが利用可能です。

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

主に定型文の短縮を可能にします。

16
Yuval Itzchakov

もう1つの重要な点は、 '=>'を 'get'の代わりに使用でき、 '= only'メソッドではonlyであることです - 'set'と一緒に使用することはできません。

4
Chris Halcrow