5

次のコードを検討してください。

// module level declaration
Socket _client;

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

        Close();
    }
}

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

コードがメソッドを呼び出し、ソケットClose()を閉じてに設定すると、「using」ブロック内にあるとすると、舞台裏で正確に何が起こるでしょうか。ソケットは本当に閉じられますか?副作用はありますか?_clientnull

PSこれは.NETMicroFrameworkでC#3.0を使用していますが、言語であるc#は同じように機能するはずです。私が尋ねている理由は、まれに、ソケットが不足することです(これは、.NET MFデバイスの非常に貴重なリソースです)。

4

4 に答える 4

5

Disposeは引き続き呼び出されます。実行しているのは、変数_clientをメモリ内の他の何か(この場合はnull)にポイントすることだけです。_clientが最初に参照したオブジェクトは、usingステートメントの最後に引き続き破棄されます。

この例を実行します。

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
}

変数をnullに設定しても、オブジェクトが破壊されたり、を使用してオブジェクトが破棄されたりすることはありません。あなたがしているのは、最初に参照されたオブジェクトを変更するのではなく、変数の参照を変更することだけです。

後期編集:

リファレンスhttp://msdn.microsoft.com/en-us/library/yh598w02.aspxとOPのコードを使用した、MSDNに関するコメントからの議論に関して、私の例では、このようなコードのより単純なバージョンを作成しました。 。

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

(そして、はい、オブジェクトはまだ破棄されます。)

上記のリンクから、コードが次のように書き直されていることが推測できます。

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

これはオブジェクトを破棄せず、コードスニペットの動作と一致しません。そこで、ildasmで調べました。収集できる最善の方法は、元の参照がメモリ内の新しいアドレスにコピーされていることです。ステートメントfoo = null;は元の変数に適用されますが、への呼び出し.Dispose()はコピーされたアドレスで発生しています。それで、コードが実際に書き直されていると私がどのように信じているかを見てみましょう。

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

参考までに、これはildasmを通してILがどのように見えるかです。

.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

私はildasmを見つめて生計を立てていないので、私の分析は警告の空虚として分類することができます。ただし、動作はそれが何であるかです。

于 2010-03-25T04:25:57.747 に答える
5

分解を見れば理解できると思いますが、これらすべてのルールが明確に記述されている仕様のセクション8.13を読む方がはるかに簡単です。

これらのルールを読むと、コードが明確になります

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

コンパイラによってに変換されます

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

だからそれが起こるのです。ソケットは、例外ではないコードパスで2回破棄されます。これはおそらく致命的ではないが、間違いなく悪臭だと私は思う。私はこれを次のように書きます:

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

その方法は完全に明らかであり、二重に閉じられるものはありません。

于 2010-03-25T06:31:51.813 に答える
2

Anthonyが指摘したDispose()ように、usingブロックの実行中に参照がnullになった場合でも呼び出されます。生成されたILを見るとProcessSocket()、インスタンスメンバーを使用してフィールドを格納するのが難しい場合でも、スタック上にローカル参照が作成されていることがわかります。と呼ばれるのは、このローカル参照を介してDispose()です。

のILはProcessSocket()次のようになります

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

ローカルに注意し、これが行上のメンバーを指すように設定されていることに注意してくださいL_000d- L_0012。ローカルが再度ロードされ、のL_0024呼び出しDispose()に使用されますL_0025

于 2010-03-25T05:53:05.123 に答える
0

_client.Dispose()使用するだけで、_clientがnullでない場合にfinallyブロックが呼び出される単純なtry/finallyに変換されます。

したがって、_clientを閉じてnullに設定すると、usingは閉じても実際には何もしません。

于 2010-03-25T04:22:57.180 に答える