1. ホーム
  2. c#

[解決済み】ReSharperが「暗黙のうちに捕捉されたクロージャ」と言うのはなぜ?

2022-03-29 20:51:10

質問

次のようなコードがあります。

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;
}

今度は、その行にコメントをつけて リシャーパー が変更を示唆しています。その意味や、なぜ変更する必要があるのでしょうか? implicitly captured closure: end, start

どのように解決するのですか?

警告では、変数 endstart は、このメソッド内のラムダが生きているのと同じように生きています。

短い例を見てみましょう。

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();
}

最初のラムダで、"Implicitly captured closure: g"という警告が表示されます。これは、次のように言っています。 g はありえない ガベージコレクション は、最初のラムダが使用中である限りは

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

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

スプリンター C#では、匿名メソッドは常にメソッドごとに1つのクラスに格納されるため、これを回避する方法が2つあります。

  1. 無名メソッドではなく、インスタンスメソッドを使用する。

  2. ラムダ式の作成を2つのメソッドに分割する。