2010-03-25 2 views
4

Рассмотрим следующий код:Динамика, используя ключевое слово

// 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).

ответ

5

Dispose все равно будет вызываться. Все, что вы делаете, указывает на переменную _client на что-то другое в памяти (в данном случае: null). Объект, который ссылается на _client, все еще будет удален в конце инструкции using.

Запустить этот пример.

Установка переменной в нуль не разрушает объект или препятствует его удалению путем использования. Все, что вы делаете, это изменение ссылки на переменную, а не изменение исходного объекта.

Late редактировать:

Что касается обсуждения от комментариев по поводу 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(); 
    } 
} 

Для справки, это то, что ИЛ выглядит как 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, так что мой анализ может быть классифицирован как РИСКОВАННАЯ ПОКУПКА. Однако поведение - это то, что есть.

+0

Извините, но нисходящее голосование неверно. Но, хорошо, перепишите его. Моя точка зрения все еще стоит. Я отправлю код, соответствующий его примеру, если это поможет. –

+0

@Ben, спасибо за повторное рассмотрение. ;) –

+0

Ну, ваш первый пример был явно неправильным. Но так был и мой второй комментарий (который я удалил). :-) В моей защите ссылка немного вводит в заблуждение - http://msdn.microsoft.com/en-us/library/yh598w02.aspx; см. часть 'if (var! = null)', но я ее протестировал, и вы правы. –

1

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

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

+0

Вот ответ на SOF, который похож на то, что описывает: http://stackoverflow.com/questions/278902/using-statement-vs-try-finally/278924#278924 – Joel

+1

Этот ответ вводит в заблуждение. То, что задано клиентом, находится в момент, когда dispose происходит, не имеет значения; первоначальное значение - то, что расположено. –

2

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

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

+0

Рад быть не единственным, кто смотрит на демонстратор ИЛ! –

4

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

Совершенно ясно, что путь и ничто не закрывается двойным образом.

+0

Это намного легче читать, чем мой свалка IL. Благодарю. Почему я просто не посмотрел в спецификации. –