Question

Considérez le code suivant:

// module level declaration
Socket _client;

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

        Close();
    }
}

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

Étant donné que que le code appelle la méthode Close(), qui ferme la prise _client et il met à null, tout en continuant à l'intérieur du bloc `l'aide », ce qui se passe exactement dans les coulisses? La prise vraiment se ferme? Y at-il des effets secondaires?

P.S. Ceci est en C # 3.0 sur le MicroFramework .NET, mais je suppose que c #, le langage, doit fonctionner de manière identique. La raison pour laquelle je demande est que de temps en temps, très rarement, je manque de prises (ce qui est une ressource très précieuse sur un des appareils .NET MF).

Était-ce utile?

La solution

Dispose sera toujours appelé. Tout ce que vous faites est pointer la _client variable quelque chose d'autre dans la mémoire (dans ce cas: null). L'objet qui _client intially appelé sera toujours disposé à la fin de l'instruction à l'aide.

Exécuter cet exemple.

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
}

Réglage de la variable nulle est pas détruire l'objet ou l'empêchant d'être disposé par l'utilisant. Tout ce que vous faites est de changer la référence de la variable, ne change pas l'objet référencé à l'origine.

modifier la fin:

En ce qui concerne une discussion des commentaires sur l'utilisation de référence MSDN http: // msdn .microsoft.com / fr-fr / bibliothèque / yh598w02.aspx et le code dans l'OP et dans mon exemple, je créé une version plus simple du code comme celui-ci.

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

(Et, oui, l'objet obtient toujours disposé à.)

Vous pouvez déduire à partir du lien ci-dessus que le code est en cours de réécriture comme ceci:

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

Ce qui ne réglerait pas l'objet, et qui ne correspond pas au comportement de l'extrait de code. J'ai donc pris un coup d'oeil à travers ildasm, et le meilleur que je peux comprendre est que la référence originale est copiée dans une nouvelle adresse en mémoire. La déclaration foo = null; applique à la variable d'origine, mais l'appel à .Dispose() se passe à l'adresse copiée. Voici donc un aperçu de la façon dont je crois que le code est en fait en cours de réécriture.

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

Pour référence, c'est ce que l'IL ressemble par 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

Je ne fais pas une vie à regarder ildasm, donc mon analyse peut être classée comme emptor mise en garde. Cependant, le comportement est ce qu'il est.

Autres conseils

Je suppose que vous pouvez vérifier cela en regardant le démontage, mais il est beaucoup plus facile à lire que l'article 8.13 de la spécification, où toutes ces règles sont clairement décrites.

La lecture de ces règles, il est clair que le code

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

est transformé par le compilateur en

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

Voilà ce qui se passe. La prise se fait deux fois disposé dans le trajet de code non exceptionnel. Cela me paraît probablement pas fatale, mais certainement une mauvaise odeur. Je vous écris ceci comme:

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

Il est parfaitement clair de cette façon et rien ne se double fermée.

Comme Anthony a souligné Dispose() sera appelée même si la référence est nulled lors de l'exécution du bloc à l'aide. Si vous jetez un oeil à l'IL généré, vous verrez que même ProcessSocket() difficile utilise un membre d'instance pour stocker le champ, une référence locale est toujours créée sur la pile. Il est par cette référence locale Dispose() est appelée.

L'IL pour ProcessSocket() ressemble à ceci

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

Notez local et notez comment cela est mis au point à l'élément sur les lignes L_000d-L_0012. Le nouveau est chargé local L_0024 et utilisé pour appeler Dispose() dans L_0025.

à l'aide se traduit simplement à un simple try / finally où dans le bloc finally _client.Dispose() est appelée si _client est non nulle.

depuis que vous fermez _client et le mettre à null, l'utilisation de ne pas vraiment faire quoi que ce soit quand il se ferme.

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top