web-dev-qa-db-ja.com

Lazy <T>または任意のラムダ式を介して非静的メンバーにアクセスする

私はこのコードを持っています:

public class MyClass
{
    public int X { get; set; }
    public int Y { get; set; }

    private Lazy<int> lazyGetSum = new Lazy<int>(new Func<int>(() => X + Y));
    public int Sum{ get { return lazyGetSum.Value; } }

}

私にこのエラーを与えます:

フィールド初期化子は、非静的フィールド、メソッド、またはプロパティを参照できません。

レイジー経由で非静的メンバーにアクセスすることは非常に合理的だと思います。これを行う方法は?

*編集*

受け入れられた回答は問題を完全に解決しますが、問題の詳細な理由を-いつものように Joh Skeetの回答 で読むことができます。

49
Sawan

あなたはそれをコンストラクタに移動することができます:

private Lazy<int> lazyGetSum;
public MyClass()
{
   lazyGetSum = new Lazy<int>(new Func<int>(() => X + Y));
}

問題の理由の詳細については、以下の@JohnSkeetの回答を参照してください。 Lazy <T>または任意のラムダ式を介して非静的メンバーにアクセスする

75
JleruOHeP

問題の簡略版は次のとおりです。

_class Foo
{
    int x = 10;
    int y = this.x;
}
_

そして、少しあまり単純化されていないもの:

_class Foo
{
    int x = 10;
    Func<int> y = () => this.x;
}
_

thisは通常暗黙的ですが、ここでは明確にするために明示的にしました。)

最初のケースでは、thisの使用は非常に明白です。

2番目のケースでは、ラムダ式のために遅延されるため、少しわかりにくくなります。ただし、これはまだ許可されていません...コンパイラは、次のようにthisをターゲットとして使用するデリゲートを構築しようとするためです。

_class Foo
{
    int x = 10;
    Func<int> y = this.GeneratedMethod;

    private int GeneratedMethod()
    {
        return x;
    }
}
_

これは、C#5仕様のセクション10.5.5.2で禁止されています。

インスタンスフィールドの変数初期化子は、作成されるインスタンスを参照できません。

最も単純な修正は、コンストラクター本体に初期化を配置することです。ここで、thisを参照できます。だからあなたのコードで:

_public class MyClass
{
    public int X { get; set; }
    public int Y { get; set; }

    private Lazy<int> lazyGetSum;

    public MyClass()
    {
        lazyGetSum = new Lazy<int>(() => X + Y);
    }

    public int Sum{ get { return lazyGetSum.Value; } }
}
_

ラムダ式も簡略化していることに注意してください。new Func<int>(...)を使用する価値はほとんどありません。

58
Jon Skeet

エラーは何が間違っているかを正確に伝えています。フィールド初期化子のプロパティにはアクセスできません。

クラスが次のようであるとします。

public class MyClass
{
    public int X { get; set; }
    public int Y { get; set; }

    private Lazy<int> lazyGetSum = new Lazy<int>(new Func<int>(() => 2 + 3));
    public int Sum { get { return lazyGetSum.Value; } }

}

その後、問題なくコンパイルされます。コード内では、フィールドの初期化でプロパティXおよびYにアクセスしています。エラーが発生しています。

必要に応じて、コンストラクタでそれらを初期化することもできます。

public class MyClass
{
    public int X { get; set; }
    public int Y { get; set; }

    private Lazy<int> lazyGetSum; 
    public int Sum { get { return lazyGetSum.Value; } }

    public MyClass()
    {
        lazyGetSum = new Lazy<int>(new Func<int>(() => X + Y));
    }

}
3
Habib

このように使用するには、フィールドをstatic定義する必要があると思います。

public  class MyClass
    {
        public static int X { get; set; }
        public static int Y { get; set; }

        private Lazy<int> lazyGetSum = new Lazy<int>(new Func<int>(() => X + Y));
        public int Sum { get { return lazyGetSum.Value; } }
    }

そしてもちろん、フィールドを初期化する必要があります。他の方法ではできません。

[〜#〜] edit [〜#〜]:または、コンストラクターで定義できますwithout任意の定義static

public MyClass()
    {
        lazyGetSum = new Lazy<int>(new Func<int>(() => X + Y));
    }

private Lazy<int> lazyGetSum; 
public int Sum { get { return lazyGetSum.Value; } }
0
Soner Gönül

非静的メソッドを使用する場合。 Lazyのようなコンストラクターではなく、.ValueでFuncパラメーターを取得する Lazyの代替ビルド selfを使用することもできます。

コードは次のようになります。

public class MyClass
{
    public int X { get; set; }
    public int Y { get; set; }

    private readonly LazyValue<int> lazyGetSum = new LazyValue<int>();
    public int Sum { get { return lazyGetSum.GetValue(() => X + Y); } }
}

それはプロパティにプロパティを実装します。あなたがそれを期待するかもしれないところに、

0
Alex Siepman

同様の問題があり、使用するのに適したパターンを見つけたいときにこの質問を見つけました。

他の回答で提案されている「コンストラクターへの初期化の移動」の問題は、初期化関数のラムダがコンストラクターに表示されることです(実際には、以前はコンストラクターを必要としなかったクラスで明示的なコンストラクターが必要になります)。

このおもちゃの例では問題ありませんが、多くのプロパティを持つより複雑なクラスでは、プロパティに関連するすべてのロジックを1か所に置くと便利です。

Alex Siepmanの答えは素晴らしい代替案を提案していますが、LazyValue<>クラスをホストしているサードパーティのサイトは現在「サービスを利用できません」と思われ、いずれにしてもサードパーティのソリューションを探していませんでした。通常のLazy<>クラスで使用します。また、一部のユースケースでは、プロパティにアクセスするたびにデリゲートインスタンスと関連するクロージャーを作成することが重要な場合があることも心配しています。

他の回答で強調表示されている問題のため、コンストラクターに行があることは避けられないように見えますが、プロパティロジックをdiffなどのプロパティから遠く離れているため、コンストラクター自体にプロパティロジックを配置しないようにするのが望ましいようです。

次の2つのパターンは機能するようですが、見逃したさらに良い代替策があるかどうかはわかりません。

パターン1:ラムダを使用しないでください-プロパティの隣で実際の関数を宣言し、暗黙のデリゲートでラップします。

public class MyClass
{
  public MyClass()
  {
    lazyGetSum = new Lazy<int>(GetSum);
  }

  public int X { get; set; }
  public int Y { get; set; }

  private Lazy<int> lazyGetSum;
  public int Sum { get { return lazyGetSum.Value; } }
  private int GetSum() { return X + Y; }
}

パターン2:プロパティのinit関数を宣言し、コンストラクターからそれを呼び出します。

public class MyClass
{
  public MyClass()
  {
    LazyGetSumInit();
  }

  public int X { get; set; }
  public int Y { get; set; }

  private Lazy<int> lazyGetSum;
  public int Sum { get { return lazyGetSum.Value; } }
  private void LazyGetSumInit() { lazyGetSum = new Lazy<int>(new Func<int>(() => X + Y)); }
}

2つ並べて見ると、関数の見た目が不格好な名前を除いて、私は2番目を好むと思います。

[私の実際の実装では、InitSumに似た名前が付いていたため、これは「遅延」プロパティの実装の詳細であり、原則として、遅延なしと遅延なしの実装の間で変更できます。コンストラクタコードを変更する]

0
Steve