302

次のコードがあります。

public double CalculateDailyProjectPullForceMax(DateTime date, string start = null, string end = null)
{
    Log("Calculating Daily Pull Force Max...");

    var pullForceList = start == null
                             ? _pullForce.Where((t, i) => _date[i] == date).ToList() // implicitly captured closure: end, start
                             : _pullForce.Where(
                                 (t, i) => _date[i] == date && DateTime.Compare(_time[i], DateTime.Parse(start)) > 0 && 
                                           DateTime.Compare(_time[i], DateTime.Parse(end)) < 0).ToList();

    _pullForceDailyMax = Math.Round(pullForceList.Max(), 2, MidpointRounding.AwayFromZero);

    return _pullForceDailyMax;
}

ここで、 ReSharperが変更を提案している行にコメントを追加しました。それはどういう意味ですか、またはなぜ変更する必要があるのでしょうか?implicitly captured closure: end, start

4

5 に答える 5

392

この警告は、このメソッド内のラムダのいずれかが生き続けるため、変数endと生き続けることを示しています。start

短い例を見てください

protected override void OnLoad(EventArgs e)
{
    base.OnLoad(e);

    int i = 0;
    Random g = new Random();
    this.button1.Click += (sender, args) => this.label1.Text = i++.ToString();
    this.button2.Click += (sender, args) => this.label1.Text = (g.Next() + i).ToString();
}

最初のラムダで「暗黙的にキャプチャされたクロージャー: g」という警告が表示されます。最初のラムダが使用されている限り、ガベージ コレクションg実行できないことがわかります。

コンパイラは両方のラムダ式のクラスを生成し、ラムダ式で使用されるすべての変数をそのクラスに配置します。

したがって、私の例giは、デリゲートの実行のために同じクラスに保持されます。g大量のリソースが残された重いオブジェクトの場合、ガベージ コレクターはそれを回収できませんでした。これは、ラムダ式のいずれかが使用されている限り、このクラスの参照がまだ生きているためです。したがって、これは潜在的なメモリ リークであり、それが R# 警告の理由です。

@splintor C# と同様に、匿名メソッドは常にメソッドごとに 1 つのクラスに格納されます。これを回避するには 2 つの方法があります。

  1. 匿名メソッドの代わりにインスタンス メソッドを使用します。

  2. ラムダ式の作成を 2 つの方法に分割します。

于 2013-04-05T20:49:17.167 に答える
31

警告は有効であり、複数のラムダを持つメソッドで表示され、異なる値を取得します

ラムダを含むメソッドが呼び出されると、コンパイラによって生成されたオブジェクトが次のようにインスタンス化されます。

  • ラムダを表すインスタンス メソッド
  • これらのラムダのいずれかによってキャプチャされたすべての値を表すフィールド

例として:

class DecompileMe
{
    DecompileMe(Action<Action> callable1, Action<Action> callable2)
    {
        var p1 = 1;
        var p2 = "hello";

        callable1(() => p1++);    // WARNING: Implicitly captured closure: p2

        callable2(() => { p2.ToString(); p1++; });
    }
}

このクラス用に生成されたコードを調べます (少し整理されています)。

class DecompileMe
{
    DecompileMe(Action<Action> callable1, Action<Action> callable2)
    {
        var helper = new LambdaHelper();

        helper.p1 = 1;
        helper.p2 = "hello";

        callable1(helper.Lambda1);
        callable2(helper.Lambda2);
    }

    [CompilerGenerated]
    private sealed class LambdaHelper
    {
        public int p1;
        public string p2;

        public void Lambda1() { ++p1; }

        public void Lambda2() { p2.ToString(); ++p1; }
    }
}

とのLambdaHelper両方で作成されたストアのインスタンスに注意してください。p1p2

想像してみろ:

  • callable1その引数への長期間の参照を保持し、helper.Lambda1
  • callable2その引数への参照を保持しません。helper.Lambda2

この場合、 への参照helper.Lambda1は間接的に の文字列も参照しますp2。これは、ガベージ コレクターがその文字列の割り当てを解除できないことを意味します。最悪の場合、メモリ/リソース リークです。または、オブジェクトが必要以上に長く存続する可能性があり、gen0 から gen1 に昇格した場合に GC に影響を与える可能性があります。

于 2015-06-24T10:12:18.647 に答える
3

Linq から Sql へのクエリの場合、この警告が表示されることがあります。メソッドがスコープ外になった後にクエリが実現されることが多いため、ラムダのスコープはメソッドよりも長く存続する可能性があります。状況によっては、L2S ラムダでキャプチャされたメソッドのインスタンス変数で GC を可能にするために、メソッド内で結果を (つまり .ToList() を介して) 実現したい場合があります。

于 2015-05-22T00:39:05.487 に答える