使用关键字的动态

时间:2010-03-25 04:20:20

标签: c# programming-languages using .net-micro-framework

请考虑以下代码:

// 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,同时仍在“using”块内,究竟发生了什么场景?套接字真的关闭了吗?有副作用吗?

P.S。这是在.NET MicroFramework上使用C#3.0,但我认为c#(语言)应该具有相同的功能。我问的原因是偶尔,很少,我用完套接字(这是.NET MF设备上非常宝贵的资源)。

4 个答案:

答案 0 :(得分: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谋生,所以我的分析可归类为警告。但是,行为就是它。

答案 1 :(得分: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();
}

非常清楚的是,没有任何东西可以双重关闭。

答案 2 :(得分:2)

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

ProcessSocket()的IL看起来像这样

.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

答案 3 :(得分:0)

使用只是转换为一个简单的try / finally,如果_client不为null,则调用finally块_client.Dispose()中的位置。

所以既然你关闭了_client并将它设置为null,那么当它关闭时,使用它并没有真正做任何事情。