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()方法,该方法关闭_client套接字并将其设置为null,而仍在“使用”块内,幕后究竟发生了什么?套接字真的关闭了吗?有副作用吗?

PS 这是在 .NET MicroFramework 上使用 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 不会破坏对象或阻止它被使用。您所做的只是更改变量的引用,而不是更改最初引用的对象。

后期编辑:

关于关于 MSDN 使用参考http://msdn.microsoft.com/en-us/library/yh598w02.aspx和 OP 中的代码以及在我的示例中的代码的评论中的讨论,我创建了一个更简单的代码版本,如下所示.

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();
    }
}

作为参考,这是 IL 通过 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

我不是靠盯着 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();
    }
}

所以这就是发生的事情。套接字在非异常代码路径中被释放两次。这让我觉得可能不是致命的,但绝对是难闻的气味。我会这样写:

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

这样一来就很清楚了,没有任何东西会被双重关闭。

于 2010-03-25T06:31:51.813 回答
2

正如 Anthony 指出的那样,Dispose()即使在执行 using 块期间引用为空,也会被调用。如果查看生成的 IL,您会发现即使很难ProcessSocket()使用实例成员来存储字段,仍然会在堆栈上创建本地引用。正是通过这个本地引用Dispose()被调用。

IL forProcessSocket()看起来像这样

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

注意 local 并注意 this 如何设置为指向行上的成员L_000d- L_0012。本地再次加载L_0024并用于调用Dispose()L_0025

于 2010-03-25T05:53:05.123 回答
0

using just 转换为一个简单的 try/finally,_client.Dispose()如果 _client 不为空,则在 finally 块中调用。

因此,由于您关闭 _client 并将其设置为 null,因此 using 在关闭时实际上并没有做任何事情。

于 2010-03-25T04:22:57.180 回答