2009-08-14 9 views
5

Digamos que tengo el siguiente código:métodos anónimos, el alcance y la serialización

public class Foo 
{ 
    private int x; 
    private int y; 

    public Bar CreateBar() 
    { 
     return new Bar(x,() => y); 
    } 
} 

[Serializable] 
public class Bar 
{ 
    private int a; 
    private Func<int> b; 

    public Bar(int a, Func<int> b) 
    { 
     this.a = a; 
     this.b = b; 
    } 
} 

¿Qué ocurre con el alcance de los objetos y valores en este escenario? Como x es un tipo de valor, se pasa a Bar por valor y, por lo tanto, no es necesario que su alcance suceda. Pero, ¿qué le sucede a y? El valor de y necesita mantenerse para volver cuando realmente se evalúa b. ¿Se mantiene todo Foo para evaluar y en un momento posterior? Solo puedo suponer que Foo no tiene GC.

Ahora digamos que serializamos Bar en disco, luego lo deserializamos más tarde. ¿Qué se ha serializado realmente? ¿Se serializó Foo también? ¿Qué magia ha sucedido para que b se pueda evaluar después de que Bar se haya deserializado? ¿Puedes explicar lo que está sucediendo en IL?

Respuesta

5

Actualización: para ver lo que está sucediendo realmente sin tener que recurrir a IL: Using reflector to understand anonymous methods and captured variables


Cuando se utiliza:

public Bar CreateBar() 
{ 
    return new Bar(x,() => y); 
} 

Usted está lo que significa implícitamente this.y; por lo tanto, en términos del delegado, se incluye la referencia a Foo. Como tal, la instancia de Bar (a través del delegado) mantiene la totalidad de Foo activa (no recogida de basura) hasta que el Bar esté disponible para la recopilación.

En particular, no es necesario (en este caso) que el compilador genere una clase adicional para manejar las variables capturadas; lo único que se requiere es la instancia Foo, por lo que se puede generar un método en Foo. Esto sería más complejo si el delegado involucrara variables locales (que no sean this).

En términos de serialización ... bueno, lo primero que diría es que la serialización de delegados es una muy mala idea. Sin embargo, BinaryFormatterse delegados a pie, y puede (en teoría) terminar con un número de serie Bar, un serializado Foo, y un delegado serializado para vincularlos - pero sólosi marca Foo como [Serializable].

Pero hago hincapié en que esta es una mala idea . Raramente utilizo BinaryFormatter (por una variedad de razones), pero una pregunta común que veo por las personas que la usan es "¿por qué está tratando de serializar (algún tipo aleatorio)". Por lo general, la respuesta es "usted está publicando un evento y está intentando serializar al suscriptor", en cuyo caso la solución más común sería marcar el campo del evento como [NonSerialized].


En lugar de mirar IL; Otra forma de investigar esto es usar el reflector en el modo .NET 1.0 (es decir, sin que se intercambie en métodos anónimos); entonces usted puede ver:

public Bar CreateBar() 
{ 
    return new Bar(this.x, new Func<int>(this.<CreateBar>b__0)); 
} 
[CompilerGenerated] 
private int <CreateBar>b__0() 
{ 
    return this.y; 
} 

Como puede ver; lo que pasó a Bar es un delegado a un método oculto (llamado <CreateBar>b__0()) en la instancia actual (this). Por lo tanto, es la instancia del actual Foo que se pasa al Bar.

+0

En la sección 6.5.3 del Lenguaje de programación de C# (3ª edición), hay un ejemplo muy similar a este caso y se maneja tal como lo explicó Marc mediante un método de instancia generado por el compilador en Foo. –

+0

¡Respuesta fantástica Marc! ¡Gracias! –

0

Cree un proyecto de prueba rápido para mostrar los valores y luego verlos. Debería responder a las preguntas, y probablemente haga que aprendas algo extra en el proceso. (Esto es lo que han hecho la mayoría de las personas que responderán a su pregunta.)

+0

También tengo curiosidad acerca de la IL y la teoría de la misma. He escrito pruebas para responder algunas de mis preguntas, pero me gustaría entender más lo que está sucediendo realmente. Pensé que algunas personas súper inteligentes en SO podrían arrojar algo más de luz. –

+0

Puede usar Reflector para ver el IL producido por su proyecto de prueba, pero por supuesto los comentarios de un experto serían más comprensibles de inmediato. –

1

Recibí un error al tratar de serializar cuando estaba reflejando el objeto para serializar.

mi ejemplo:

[Serializable] 
    public class SerializeTest 
    { 
     //public SerializeTest(int a, Func<int> b) 
     //{ 
     // this.a = a; 
     // this.b = b; 
     //} 

     public SerializeTest() 
     { 

     } 

     public int A 
     { 
      get 
      { 
       return a; 
      } 

      set 
      { 
       a = value; 
      } 
     } 
     public Func<int> B 
     { 
      get 
      { 
       return b; 
      } 
      set 
      { 
       b = value; 
      } 
     } 


     #region properties 

     private int a; 
     private Func<int> b; 



     #endregion 

     //serialize itself 
     public string Serialize() 
     { 
      MemoryStream memoryStream = new MemoryStream(); 

      XmlSerializer xs = new XmlSerializer(typeof(SerializeTest)); 
      using (StreamWriter xmlTextWriter = new StreamWriter(memoryStream)) 
      { 
       xs.Serialize(xmlTextWriter, this); 
       xmlTextWriter.Flush(); 
       //xmlTextWriter.Close(); 
       memoryStream = (MemoryStream)xmlTextWriter.BaseStream; 
       memoryStream.Seek(0, SeekOrigin.Begin); 
       StreamReader reader = new StreamReader(memoryStream); 

       return reader.ReadToEnd(); 
      } 
     } 

     //deserialize into itself 
     public void Deserialize(string xmlString) 
     { 
      String XmlizedString = null; 

      using (MemoryStream memoryStream = new MemoryStream()) 
      { 
       using (StreamWriter w = new StreamWriter(memoryStream)) 
       { 
        w.Write(xmlString); 
        w.Flush(); 

        XmlSerializer xs = new XmlSerializer(typeof(SerializeTest)); 
        memoryStream.Seek(0, SeekOrigin.Begin); 
        XmlReader reader = XmlReader.Create(memoryStream); 

        SerializeTest currentConfig = (SerializeTest)xs.Deserialize(reader); 

        this.a = currentConfig.a; 
        this.b = currentConfig.b; 

        w.Close(); 
       } 
      } 
     } 

    } 

class Program 
    { 
     static void Main(string[] args) 
     { 

      SerializeTest test = new SerializeTest() { A = 5, B =()=>67}; 
      string serializedString = test.Serialize(); 


} 
} 

Aquí hay un enlace a hacerlo ... poco más complejo: Serializing Anon Delegates

+0

Tiene razón, en mi ejemplo, Foo también debe marcarse como Serializable. Entonces esto significa que está serializando todo Foo. –

+0

@Stefan: no, ninguna de sus clases necesita el atributo Serializable (al menos no para la serialización de XML). Este atributo es para la serialización con formateadores (BinaryFormatter, SoapFormatter ...) –

+0

@Thomas Correcto, estoy usando BinaryFormatter. –

0

creo que x e y en el objeto de Foo serán capturados como los tipos de valor. Por lo tanto, el cierre creado para esa expresión lambda no debe mantener una referencia al objeto Foo. Por lo que el compilador puede crear una clase para la que el cierre como:

internal class CompilerGeneratedClassName 
{ 
    private int x; 
    private int y; 
    public CompilerGeneratedClassName(int x, int y) 
    { 
    this.x = x; 
    this.y = y; 
    } 

    public int CompilerGeneratedMethodName() 
    { 
    return this.y; 
    }  
} 

y

return new Bar(x,() => y); 

puede ser reemplazado por

return new Bar(x,new CompilerGeneratedClassName(x,y).CompilerGeneratedMethodName); 

así que no creo que eso se hará referencia al objeto Foo como resultado de este cierre. Así que el objeto Foo podría ser GCed. Podría estar equivocado. Una cosa que puede hacer es escribir un programa pequeño, compilarlo e inspeccionar el IL generado en la herramienta ILDASM.

+0

He inspeccionado el IL, pero no estoy muy familiarizado con IL y no puedo encontrar nada especial que el compilador haya hecho. –

+1

El compilador no necesita una clase de cierre para esto; es un campo, no una variable de método local. Es el "esto" que se pasa. Ver mi respuesta para más. –

Cuestiones relacionadas