Resharper: 隐式捕获闭包: this

65

我从Resharper这里收到了警告("隐式捕获闭包:this"),这是否意味着这段代码以某种方式捕获了整个封闭对象?

    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,这是有意的。如果这真的是一个问题,我该如何解决?

编辑:警告在第一个lambda表达式上(Timeout事件)。


2
可能是[为什么Resharper告诉我“隐式捕获闭包:end,start”?]的重复问题(https://dev59.com/JGYr5IYBdhLWcg3wfaSc) - Soren
2个回答

27

看起来问题不在我认为的那一行。

问题在于我有两个lambda引用了父对象中的字段:编译器生成一个类,其中包括两个方法和对父类(this)的引用。

我认为这可能是个问题,因为对this的引用可能会在TaskCompletionSource对象中保持存在,导致它无法被垃圾回收。至少这是我在这个问题上发现的。

生成的类会长成这样(显然名称将不同而且难以发音):

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被两个lambda表达式都使用;但只要仍然引用其中一个lambda表达式,对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;
        }
    }

将if语句或Timeout事件行移动到单独的方法中可以消除警告,但我觉得这不是一个理想的解决方案。 - Aaron Maslen

13

看起来_response是你的类中的一个字段。

从lambda引用_response将会捕获闭包中的this,并且在lambda执行时读取this._response

为了防止这种情况发生,你可以将_response复制到一个本地变量中,并使用该变量。请注意,这将导致它使用当前_response的值而不是其最终的值。


我应该提到,警告在第一个lambda(Timeout事件)上而不是第二个。另外,你所描述的是对_response的捕获,而不是"this"引用。 - Aaron Maslen
@user1447072:在答案中,心理替换 _responseTimeout :) - Jon
它不应该捕获Timeout变量,对吧?(因为它在lambda中没有被引用)。 - Aaron Maslen
@AaronMaslen#1: 不是的。Lambda表达式捕获变量或参数,例如this_response实际上是this._response,它捕获了this - SLaks
据我所记,我认为它不会捕获this - 意思是编译器生成的类只包含一个类型为Response的字段,即引用this._response。不过,需要检查反汇编以确认。无论如何,这是一个无关紧要的问题,因为这行代码并不是导致警告的行。 - Aaron Maslen
看我的回答,我觉得现在我对问题有了更好的理解。 - Aaron Maslen

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接