Dinámica del uso de la palabra clave
-
22-09-2019 - |
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).
Solución
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.