プロパティシグネチャのC#での=>割り当てとは


229

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

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

今、私はラムダ式にある程度精通しています。私はそれがこのようにそれを使用するのを見たことがありません。

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

public int MaxHealth  = x ? y:z;

4
最初のブロックはプロパティ、2番目のブロックは変数
M.kazem Akhgary、2015

14
@ M.kazemAkhgary *変数ではなくフィールド。
2016

回答:


376

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

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

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が「AC#6 gotcha:Initialization vs. Expression Bodied Members」というタイトルの投稿で説明していますます。

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

また、式本体のメンバーはプロパティメンバーに限定されないことも付け加えておきます。彼らはこれらすべてのメンバーに取り組みます:

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

C#7.0で追加

ただし、これらのメンバーには機能しません。

  • ネストされた型
  • イベント
  • 田畑

6
C#7以降では、コンストラクターとファイナライザーもサポートされています。docs.microsoft.com/en-us/dotnet/csharp/programming-guide/...
bzier

8
@bzier機能的なプログラマーにすることは陰謀です。もしそれ以外の場合は永遠に!!
Sentinel

超すごい答え!
ハイメアロヨガルシア

2
Bill Wagnerの投稿へのリンクは現在壊れています。:私は、私は新しいURL見つけたと思うcodeproject.com/Articles/1064964/...
フライシンプソン

36

わかりました…違うとコメントしましたが、正確には説明できませんでしたが、わかりました。

String Property { get; } = "value";

と同じではありません

String Property => "value";

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

自動初期化子を使用すると、プロパティは値のインスタンスを作成し、その値を永続的に使用します。上記の投稿には、Bill Wagnerへのリンクが壊れており、これがよく説明されています。正しいリンクを検索して、自分で理解しました。

私の状況では、ビューの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または式の本体が戻り値を作成するときにのみ発生します。


8
=>構文は、get {return new Command();と同じです。}構文。
2017年

35

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

以下の構文糖と見なされますが、同一のILを生成しない場合があります。

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

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

次の名前のクラスで定義されている場合、この回答のクラシックバージョンのILは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

そして、これは、次の名前のクラスで定義されたときの式のボディメンバーバージョンのILです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

C#6のこの新機能およびその他の新機能の詳細については、https://msdn.microsoft.com/en-us/magazine/dn802602.aspxを参照してください

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

更新:

C#7.0では、式を含むメンバーが拡張され、プロパティ、コンストラクター、ファイナライザー、およびインデクサーが含まれるようになりました。


16

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

これは次と同等です。

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

メソッド宣言に相当するものが利用可能です:

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

主に定型文の短縮が可能です。


7

C#6を使用している場合のもう1つの重要な点:

'=>'は 'get'の代わりに使用でき、 'get only'メソッド専用です -それは、「SET」で使用することはできません。

C#7については、以下の@avenmoreからのコメントを参照してください。これで、より多くの場所で使用できるようになりました。ここに良いリファレンスがあります-https://csharp.christiannagel.com/2017/01/25/expressionbodiedmembers/


8
C#7を使用している場合はもう当てはまりません。「C#7.0では生産性が向上していますが、C#6ではメソッドとプロパティに式本体のメンバーを使用できるようになりました。コンストラクター、デストラクター、プロパティアクセサー、イベントアクセサーで使用できるようになりました。同じように。" (出典
アベンモア2017

1

Alex Booker が回答で共有した次のステートメントについて

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

次のスクリーンショットを参照してください。このステートメントがどのように表示されるかを示します(SharpLabリンクを使用)。

public string APIBasePath => Configuration.ToolsAPIBasePath;

に変換する

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

スクリーンショット: ここに画像の説明を入力してください

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.