web-dev-qa-db-ja.com

Resharper:暗黙的にキャプチャーされたクロージャー:これ

Resharperからこの警告(「Impplicityキャプチャされたクロージャ:これ」)を受け取っています。

    internal Timer Timeout = new Timer
                            {
                                Enabled = false,
                                AutoReset = false
                            };
    public Task<Response> ResponseTask
    {
        get
        {
            var tcs = new TaskCompletionSource<Response>();

            Timeout.Elapsed += (e, a) => tcs.SetException(new TimeoutException("Timeout at " + a.SignalTime));

            if (_response != null) tcs.SetResult(_response);
            else ResponseHandler += r => tcs.SetResult(_response);
            return tcs.Task;
        }
    }

どのようにまたはなぜそうするのかわかりません-キャプチャする必要がある唯一の変数はTaskCompletionSourceであり、これは意図的なものです。これは実際に問題なのでしょうか?それが解決した場合、どうすれば解決できますか?

編集:警告は最初のラムダ(タイムアウトイベント)にあります。

65
Aaron Maslen

問題は、私がそう思うと思う線ではないようです。

問題は、親オブジェクトのフィールドを参照する2つのラムダがあることです。コンパイラは、2つのメソッドと親クラスへの参照(this)を持つクラスを生成します。

thisへの参照は潜在的にTaskCompletionSourceオブジェクト内に留まり、GCされないようにする可能性があるため、これは問題になると思います。少なくとも、この問題で私が発見したことは示唆しています。

生成されたクラスは次のようになります(明らかに名前は異なり、発音できません):

class GeneratedClass {
    Request _this;
    TaskCompletionSource tcs;

    public lambda1 (Object e, ElapsedEventArgs a) {
        tcs.SetException(new TimeoutException("Timeout at " + a.SignalTime));
    }

    public lambda2 () {
        tcs.SetResult(_this._response);
    }
}

コンパイラがこれを行う理由はおそらく、おそらく効率です。なぜなら、TaskCompletionSourceは両方のラムダで使用されるからです。しかし、これらのラムダの1つへの参照がまだ参照されている限り、Requestオブジェクトへの参照も維持されます。

ただし、この問題を回避する方法を見つけることにはまだ近づいていません。

編集:私はそれを書いていたときに明らかにこれを考えていませんでした。次のようにメソッドを変更することで問題を解決しました。

    public Task<Response> TaskResponse
    {
        get
        {
            var tcs = new TaskCompletionSource<Response>();

            Timeout.Elapsed += (e, a) => tcs.SetException(new TimeoutException("Timeout at " + a.SignalTime));

            if (_response != null) tcs.SetResult(_response);
            else ResponseHandler += tcs.SetResult; //The event passes an object of type Response (derp) which is then assigned to the _response field.
            return tcs.Task;
        }
    }
27
Aaron Maslen

_responseはクラスのフィールドのようです。

ラムダから_responseを参照すると、クロージャーでthisがキャプチャされ、ラムダの実行時にthis._responseが読み取られます。

これを防ぐには、_responseをローカル変数にコピーして、代わりに使用します。その結果、最終的な値ではなく_responseの-​​current値が使用されることに注意してください。

12
SLaks