web-dev-qa-db-fra.com

Resharper: fermeture implicitement capturée: ce

Je reçois cet avertissement ("Fermeture capturée par implicité: ceci") de Resharper: cela signifie-t-il que ce code capture en quelque sorte l'objet englobant entier?

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

Je ne sais pas comment ni pourquoi il le fait - la seule variable qu'il devrait capturer est le TaskCompletionSource, qui est intentionnel. Est-ce réellement un problème et comment pourrais-je le résoudre s'il l'est?

EDIT: l'avertissement est sur le premier lambda (l'événement Timeout).

65
Aaron Maslen

Il semble que le problème ne soit pas la ligne que je pense.

Le problème est que j'ai deux lambdas référençant des champs dans l'objet parent: Le compilateur génère une classe avec deux méthodes et une référence à la classe parent (this).

Je pense que ce serait un problème car la référence à this pourrait potentiellement rester dans l'objet TaskCompletionSource, l'empêchant d'être GCed. C'est du moins ce que j'ai trouvé sur cette question.

La classe générée ressemblerait à ceci (les noms seront évidemment différents et imprononçables):

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

La raison pour laquelle le compilateur fait cela est probablement l'efficacité, je suppose, car le TaskCompletionSource est utilisé par les deux lambdas; mais maintenant tant qu'une référence à l'un de ces lambdas est toujours référencée, la référence à l'objet Request est également conservée.

Cependant, je ne suis pas encore prêt à trouver un moyen d'éviter ce problème.

EDIT: Je n'ai évidemment pas réfléchi à cela quand je l'ai écrit. J'ai résolu le problème en changeant la méthode comme ceci:

    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

Ça ressemble à _response est un champ de votre classe.

Référencement _response du lambda capturera this dans la fermeture et lira this._response lorsque le lambda s'exécute.

Pour éviter cela, vous pouvez copier _response à une variable locale et utilisez-le à la place. Notez que cela lui fera utiliser la valeur actuelle de _response plutôt que sa valeur éventuelle.

12
SLaks