Вопрос

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

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

Это было полезно?

Решение

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

Другие советы

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

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

Как указал Энтони Dispose() будет вызван, даже если ссылка обнулена во время выполнения блока using.Если вы взглянете на сгенерированный IL, вы увидите, что даже жесткие ProcessSocket() использует элемент экземпляра для хранения поля, локальная ссылка по-прежнему создается в стеке.Именно через эту локальную ссылку Dispose() называется.

ИЛ для 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
}

Обратите внимание на local и обратите внимание, как он настроен так, чтобы указывать на участника в строках L_000d-L_0012.Локальный файл снова загружается в L_0024 и привык называть Dispose() в L_0025.

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

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

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top