¿Por qué ReSharper me dice "cierre capturado implícitamente"?

Resuelto PiousVenom asked hace 11 años • 5 respuestas

Tengo el siguiente código:

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

Ahora, agregué un comentario en la línea que indica que ReSharper sugiere un cambio. ¿Qué significa o por qué sería necesario cambiarlo?implicitly captured closure: end, start

PiousVenom avatar Nov 30 '12 02:11 PiousVenom
Aceptado

La advertencia le indica que las variables endy startpermanecen activas ya que cualquiera de las lambdas dentro de este método permanece activa.

Eche un vistazo al breve ejemplo.

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

Recibo una advertencia de "Cierre capturado implícitamente: g" en la primera lambda. Me dice que gno se puede recolectar basura mientras la primera lambda esté en uso.

El compilador genera una clase para ambas expresiones lambda y coloca en esa clase todas las variables que se utilizan en las expresiones lambda.

Entonces, en mi ejemplo g, se illevan a cabo en la misma clase para la ejecución de mis delegados. Si gse trata de un objeto pesado al que le quedan muchos recursos, el recolector de basura no podrá recuperarlo, porque la referencia en esta clase sigue viva mientras cualquiera de las expresiones lambda esté en uso. Entonces, esta es una posible pérdida de memoria, y ese es el motivo de la advertencia de R#.

@splintor Como en C# los métodos anónimos siempre se almacenan en una clase por método, hay dos formas de evitar esto:

  1. Utilice un método de instancia en lugar de uno anónimo.

  2. Divida la creación de expresiones lambda en dos métodos.

quadroid avatar Apr 05 '2013 20:04 quadroid

De acuerdo con Peter Mortensen.

El compilador de C# genera solo un tipo que encapsula todas las variables para todas las expresiones lambda en un método.

Por ejemplo, dado el código fuente:

public class ValueStore
{
    public Object GetValue()
    {
        return 1;
    }

    public void SetValue(Object obj)
    {
    }
}

public class ImplicitCaptureClosure
{
    public void Captured()
    {
        var x = new object();

        ValueStore store = new ValueStore();
        Action action = () => store.SetValue(x);
        Func<Object> f = () => store.GetValue();    //Implicitly capture closure: x
    }
}

El compilador genera un tipo parecido a:

[CompilerGenerated]
private sealed class c__DisplayClass2
{
  public object x;
  public ValueStore store;

  public c__DisplayClass2()
  {
    base.ctor();
  }

  //Represents the first lambda expression: () => store.SetValue(x)
  public void Capturedb__0()
  {
    this.store.SetValue(this.x);
  }

  //Represents the second lambda expression: () => store.GetValue()
  public object Capturedb__1()
  {
    return this.store.GetValue();
  }
}

Y el Capturemétodo se compila como:

public void Captured()
{
  ImplicitCaptureClosure.c__DisplayClass2 cDisplayClass2 = new ImplicitCaptureClosure.c__DisplayClass2();
  cDisplayClass2.x = new object();
  cDisplayClass2.store = new ValueStore();
  Action action = new Action((object) cDisplayClass2, __methodptr(Capturedb__0));
  Func<object> func = new Func<object>((object) cDisplayClass2, __methodptr(Capturedb__1));
}

Aunque la segunda lambda no usa x, no se puede recolectar basura ya que xse compila como una propiedad de la clase generada utilizada en la lambda.

Smartkid avatar Oct 31 '2014 10:10 Smartkid

La advertencia es válida y se muestra en métodos que tienen más de una lambda y capturan valores diferentes .

Cuando se invoca un método que contiene lambdas, se crea una instancia de un objeto generado por el compilador con:

  • métodos de instancia que representan las lambdas
  • campos que representan todos los valores capturados por cualquiera de esas lambdas

Como ejemplo:

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

Examine el código generado para esta clase (ordenado un poco):

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

Tenga en cuenta la instancia de LambdaHelperlas tiendas creadas tanto p1como p2.

Imagina eso:

  • callable1mantiene una referencia duradera a su argumento,helper.Lambda1
  • callable2no guarda referencia a su argumento,helper.Lambda2

En esta situación, la referencia a helper.Lambda1también hace referencia indirectamente a la cadena en p2, y esto significa que el recolector de basura no podrá desasignarla. En el peor de los casos, es una pérdida de memoria/recursos. Alternativamente, puede mantener vivos los objetos por más tiempo del necesario, lo que puede tener un impacto en GC si se promueven de gen0 a gen1.

Drew Noakes avatar Jun 24 '2015 10:06 Drew Noakes

Para consultas de Linq a Sql, es posible que reciba esta advertencia. El alcance de la lambda puede sobrevivir al método debido al hecho de que la consulta a menudo se actualiza después de que el método está fuera del alcance. Dependiendo de su situación, es posible que desee actualizar los resultados (es decir, mediante .ToList()) dentro del método para permitir GC en las variables de instancia del método capturadas en la lambda L2S.

Jason Dufair avatar May 22 '2015 00:05 Jason Dufair