Динамика ключевого слова использования

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

// 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, в то время как все еще в блоке 'использования', что точно происходит негласно? Сокет действительно становится закрытым? Есть ли побочные эффекты?

P.S. Это использует C# 3.0 на MicroFramework.NET, но я предполагаю, что c#, язык, должен функционировать тождественно. Причина, которую я спрашиваю, состоит в том, что иногда, очень редко, у меня заканчиваются сокеты (который является очень драгоценным ресурсом на MF.NET устройства).

5
задан AngryHacker 25 March 2010 в 04:20
поделиться

4 ответа

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

Поздняя правка:

В связи с дискуссией в комментариях об использовании ссылки в 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, поэтому мой анализ можно классифицировать как осторожность. Однако, поведение таково, каково оно есть.

5
ответ дан 13 December 2019 в 05:33
поделиться

Я полагаю, вы могли бы понять это, посмотрев на разборку, но намного проще просто прочитать раздел 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
ответ дан 13 December 2019 в 05:33
поделиться

As Anthony указанное 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 .

2
ответ дан 13 December 2019 в 05:33
поделиться

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

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

0
ответ дан 13 December 2019 в 05:33
поделиться
Другие вопросы по тегам:

Похожие вопросы: