Pregunta

Considere el siguiente código:

// module level declaration
Socket _client;

void ProcessSocket() {
    _client = GetSocketFromSomewhere();
    using (_client) {
        DoStuff();  // receive and send data

        Close();
    }
}

void Close() {
    _client.Close();
    _client = null;
}

Dado que el código que llama al método Close(), que cierra el socket _client y se pone a null, estando aún dentro del bloque 'usando', lo que sucede exactamente detrás de las escenas? ¿La toma realmente se cierran? ¿Tiene efectos secundarios?

P.S. Se trata de utilizar C # 3.0 en el .NET MicroFramework, pero supongo que el C #, el idioma, debe funcionar de forma idéntica. La razón por la que estoy pidiendo es que de vez en cuando, en muy raras ocasiones, se me acaba de enchufes (que es un recurso muy valioso en un dispositivos .NET MF).

¿Fue útil?

Solución

Todavía se llamará

Desechar. Todo lo que está haciendo está señalando la variable _client a otra cosa en la memoria (en este caso: null). El objeto que _client refirió inicialmente a todavía será dispuesto en el extremo de la instrucción using.

Ejecutar este ejemplo.

class Program
{
    static Foo foo = null;

    static void Main(string[] args)
    {
        foo = new Foo();

        using (foo)
        {
            SomeAction();
        }

        Console.Read();
    }

    static void SomeAction()
    {
        foo = null;
    }
}

class Foo : IDisposable
{
    #region IDisposable Members

    public void Dispose()
    {
        Console.WriteLine("disposing...");
    }

    #endregion
}

Configuración de la variable para nulo no está destruyendo el objeto o evitando que sea dispuesto por el uso. Todo lo que está haciendo es cambiar la referencia de la variable, sin cambiar el objeto referenciado originalmente.

Late edición:

En cuanto a la discusión de los comentarios sobre el uso de la referencia de MSDN http: // MSDN .microsoft.com / es-es / library / yh598w02.aspx y el código en el PO y en mi ejemplo, he creado una versión más simple del código como este.

Foo foo = new Foo();
using (foo)
{
    foo = null;
}

(Y, sí, el objeto todavía se pone dispuesta.)

Se podría inferir desde el enlace anterior que el código está siendo reescrito como esto:

Foo foo = new Foo();
{
    try
    {
        foo = null;
    }
    finally
    {
        if (foo != null)
            ((IDisposable)foo).Dispose();
    }
}

Lo que no sería disponer el objeto, y que no coincide con el comportamiento del fragmento de código. Así que tomé un vistazo a él a través ildasm, y lo mejor que he entendido es que la referencia original se copia en una nueva dirección en la memoria. El foo = null; afirmación se aplica a la variable original, pero la llamada a .Dispose() está sucediendo en la dirección de copiado. Así que aquí está una mirada a cómo creo que el código es en realidad ser reescrito.

Foo foo = new Foo();
{
    Foo copyOfFoo = foo;
    try
    {
        foo = null;
    }
    finally
    {
        if (copyOfFoo != null)
            ((IDisposable)copyOfFoo).Dispose();
    }
}

Para referencia, esto es lo que las miradas IL como a través de ildasm.

.method private hidebysig static void  Main() cil managed
{
  .entrypoint
  // Code size       29 (0x1d)
  .maxstack  1
  .locals init ([0] class Foo foo,
           [1] class Foo CS$3$0000)
  IL_0000:  newobj     instance void Foo::.ctor()
  IL_0005:  stloc.0
  IL_0006:  ldloc.0
  IL_0007:  stloc.1
  .try
  {
    IL_0008:  ldnull
    IL_0009:  stloc.0
    IL_000a:  leave.s    IL_0016
  }  // end .try
  finally
  {
    IL_000c:  ldloc.1
    IL_000d:  brfalse.s  IL_0015
    IL_000f:  ldloc.1
    IL_0010:  callvirt   instance void [mscorlib]System.IDisposable::Dispose()
    IL_0015:  endfinally
  }  // end handler
  IL_0016:  call       int32 [mscorlib]System.Console::Read()
  IL_001b:  pop
  IL_001c:  ret
} // end of method Program::Main

Yo no se ganan la vida mirando a ildasm, por lo que mi análisis puede ser clasificado como Cuidado con este hotel. Sin embargo, el comportamiento es lo que es.

Otros consejos

supongo que se podría resolver esto mirando el desmontaje, pero es mucho más fácil a la sección acaba de leer 8.13 de la especificación, en donde se describen claramente todas estas reglas.

La lectura de estas normas deja claro que el código

_client = GetSocketFromSomewhere(); 
using (_client) 
{ 
    DoStuff();
    Close(); 
} 

se transforma por el compilador en

_client = GetSocketFromSomewhere();
{
    Socket temp = _client;
    try 
    { 
        DoStuff();
        Close(); 
    }
    finally
    {
        if (temp != null) ((IDispose)temp).Dispose();
    }
}

Así que eso es lo que sucede. La tubuladura se dispuestos dos veces en la ruta de código no excepcional. Esto me parece probablemente no es fatal, pero sin duda un mal olor. Me gustaría escribir esto como:

_client = GetSocketFromSomewhere();
try 
{ 
    DoStuff();
}
finally
{
    Close();
}

Está claro perfectamente de esa manera y nada se interpone doble cerrado.

Como se señaló Anthony Dispose() se llamará incluso si la referencia se anula durante la ejecución del bloque de usar. Si se echa un vistazo a la IL generado, verá que ProcessSocket() incluso dura usa un miembro de instancia para almacenar el campo, una referencia local todavía se crea en la pila. Es por medio de esta referencia local que se llama Dispose().

La IL para las miradas ProcessSocket() como este

.method public hidebysig instance void ProcessSocket() cil managed
{
   .maxstack 2
   .locals init (
      [0] class TestBench.Socket CS$3$0000)
   L_0000: ldarg.0 
   L_0001: ldarg.0 
   L_0002: call instance class TestBench.Socket     TestBench.SocketThingy::GetSocketFromSomewhere()
   L_0007: stfld class TestBench.Socket TestBench.SocketThingy::_client
   L_000c: ldarg.0 
   L_000d: ldfld class TestBench.Socket TestBench.SocketThingy::_client
   L_0012: stloc.0 
   L_0013: ldarg.0 
   L_0014: call instance void TestBench.SocketThingy::DoStuff()
   L_0019: ldarg.0 
   L_001a: call instance void TestBench.SocketThingy::Close()
   L_001f: leave.s L_002b
   L_0021: ldloc.0 
   L_0022: brfalse.s L_002a
   L_0024: ldloc.0 
   L_0025: callvirt instance void [mscorlib]System.IDisposable::Dispose()
   L_002a: endfinally 
   L_002b: ret 
   .try L_0013 to L_0021 finally handler L_0021 to L_002b
}

Aviso del local y observe como se establece en el punto al miembro de líneas L_000d-L_0012. Lo local se carga de nuevo en L_0024 y usado para Dispose() llamada en L_0025.

utilizando sólo se traduce en un simple try / finally donde en el _client.Dispose() bloque finally se llama si _client no es nulo.

por lo que desde que se cierre _client y la ajuste en nulo, el uso de realidad no hacer nada cuando se cierra.

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top