Динамика использования ключевого слова - PullRequest
5 голосов
/ 25 марта 2010

Рассмотрим следующий код:

// 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. Это использует C # 3.0 на .NET MicroFramework, но я предполагаю, что язык c # должен работать одинаково. Причина, по которой я спрашиваю, состоит в том, что иногда очень редко у меня заканчиваются сокеты (что является очень ценным ресурсом на устройствах .NET MF).

Ответы [ 4 ]

5 голосов
/ 25 марта 2010

Полагаю, вы могли бы понять это, взглянув на разборку, но намного проще просто прочитать раздел 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();
}

Это совершенно ясно, и ничто не закрывается дважды.

5 голосов
/ 25 марта 2010

Утилизация все равно будет вызываться. Все, что вы делаете, указываете переменную _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, поэтому мой анализ можно отнести к категории предостерегающего emptor. Тем не менее, поведение такое, какое оно есть.

2 голосов
/ 25 марта 2010

Как указал Энтони, Dispose() будет вызываться, даже если ссылка обнуляется во время выполнения блока using. Если вы посмотрите на сгенерированный 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.

0 голосов
/ 25 марта 2010

с использованием just переводит в простой try / finally, где в блоке finally вызывается _client.Dispose(), если _client не равен нулю.

, так как вы закрываете _client и устанавливаете его в null, использование на самом деле ничего не делает, когда закрывается.

...