C#: използване на блок: повторно инициализиране на обект

Повторното инициализиране в рамките на блока "използване" е лоша идея, трябва да се избягва по всяко време. Все пак ще попитам това:

Защо извикването "using" разполага с оригиналната стойност, а не с последната препратка или повторно инициализиране (което се случва, ако се използва try finally block)

MyClass b = new MyClass();// implements Idisposable
MyClass c = new MyClass();
MyClass a ; 

 using (a = new MyClass())
 {
                a = b;
                a = c;
 }

В горния код dispose ще бъде извикан на оригиналната препратка, а не на по-новата препратка. Това може лесно да се провери чрез отпечатване на нещо на конзолата в метода dispose.

Въпреки това с try{} finally code се извиква последният референтен метод за изхвърляне.

try
{
   a = new MyClass();
   a = b;
   a = c;
 }
  finally 
   {
   a.Dispose();
  }

MSDN : Инструкцията за използване гарантира, че Dispose се извиква дори ако възникне изключение докато извиквате методи на обекта.

using (Font font1 = new Font("Arial", 10.0f)) 
{
    byte charset = font1.GdiCharSet;
}

По принцип „използване“ се превежда на:

{
  Font font1 = new Font("Arial", 10.0f);
  try
  {
    byte charset = font1.GdiCharSet;
  }
  finally
  {
    if (font1 != null)
      ((IDisposable)font1).Dispose();
  }
}

person PRR    schedule 22.02.2010    source източник


Отговори (6)


Компилаторът генерира този код:

MyClass b = new MyClass();
MyClass a;
MyClass cs$3$000 = a = new MyClass();
try {
  a = b;
}
finally {
  if (cs$3$000 != null) cs$3$000.Dispose();
}

Автоматично генерираната локална променлива cs$3$000 изпълнява договора.

person Hans Passant    schedule 22.02.2010

Има две форми на using изрази, дефинирани в спецификацията на C#:

using-statement:
    using   (    resource-acquisition   )    embedded-statement
resource-acquisition:
    local-variable-declaration
    expression

Ако имате local-variable-declaration, няма да има въпроси. Променливата ще бъде само за четене в използващия блок и изобщо не можете да я промените. Спецификацията казва:

8.13 Инструкцията за използване

[...] И в двете разширения ресурсната променлива е само за четене във вградения оператор.

Тук имаме работа с втората форма: където resource-acquisition е expression, а не local-variable-declaration. В този случай C# спецификацията ясно казва:

Изявление за използване на формуляра

 using (expression) statement

има същите две възможни разширения, но в този случай ResourceType имплицитно е типът на израза по време на компилиране и променливата resource е недостъпна и невидима за вградения израз. [курсивът е мой]

Очевидно не можете да промените невидима, недостъпна променлива. Стойността му се присвоява само в клаузата using resource-acquisition. Следователно ще има старата стойност на променливата, а не новата.

Когато се занимавате с присвояване на вече декларирана променлива, вие използвате тази форма на израза using. Фактът, че присвоявате стойност на променлива като

using ( x = something )

е без значение. Цялото x = something се третира като израз и само стойността на този израз е важна. Важно е да знаете, че "променлива на ресурса" не е "x" тук. Това е невидима променлива. От гледна точка на компилатора, няма голяма разлика между следните конструкции:

using ( something ) 
using ( x = something )
using ( y = x = something )

Във всички случаи изразът ще бъде изпълнен и стойността е това, което ще получи гарантирано изхвърляне, а не променливата. Какво би трябвало да направи компилаторът, ако това не беше дефинираното поведение и бяхте написали третия ред в горния блок? Изхвърлете x? y? и двете? нито едно? Сегашното поведение има смисъл.

person mmx    schedule 22.02.2010
comment
@Downvoter: Искаш ли да обясниш? Изненадан съм, тъй като цитирах препратката тук. Вашето оправдание определено би било интересно да се чуе, тъй като е аргумент срещу спецификацията. - person mmx; 22.02.2010
comment
@silky: Не мисля, че сухотата има нещо общо с коректността. Вярвам, че сухо или не, това е единственото нещо, което има смисъл тук. Спецификацията е единственият достоверен източник за този въпрос и налага такова поведение; всичко останало са спекулации. Ясно е, че езикът може да избере своя оператор using да бъде имплементиран чрез друг метод, тъй като това не е техническо ограничение, а дизайнерско решение. - person mmx; 22.02.2010
comment
Да, разбирам гледната точка. Въпреки това анализът ви остава сух за мен, въпреки че е правилен. Това не може да се оспори, тъй като е мое мнение. Няма смисъл да спорите с мен за това, само вие можете да решите колко означава това за вас. (Не очаквам да означава нещо, просто се опитвах да ви помогна да разберете защо може да сте гласували против). - person Noon Silk; 22.02.2010
comment
@silky: Не споря с теб. Благодаря за коментара. Просто исках да кажа, че спецификациите по своята същност са сухи. - person mmx; 22.02.2010

Използването може да се разглежда като обещание за извикване на disposed на обекта, деклариран с използване. Това е единственото нещо, което, IMHO, има смисъл!

Ако извикате dispose на преназначената стойност, тогава оригиналната стойност няма да бъде изхвърлена.

person AxelEckenberger    schedule 22.02.2010
comment
Това е хубава фраза и правилно обяснява наблюдаваните резултати. - person Noon Silk; 22.02.2010

Изглежда, че "използването" създава собствена променлива за съхраняване на препратката, която се инициализира към "a", което от своя страна се инициализира към първия екземпляр на обекта. По-късно, когато промените "a", вие всъщност не променяте оригиналната препратка, която е била съхранена в израза "using". Изглежда като доста добра функция, тъй като използването е отговорно за изхвърлянето на действителния обект, споменат в израза за използване, а не променливата.

person Mike Mooney    schedule 22.02.2010

Да, интересно е.

Така че погледнах декомпилирания код:

  IL_0000:  nop
  IL_0001:  newobj     instance void ConsoleApplication17.Foo1::.ctor()
  IL_0006:  dup
  IL_0007:  stloc.0
  IL_0008:  stloc.1 // 1. note this
  .try
  {
    IL_0009:  nop
    IL_000a:  newobj     instance void ConsoleApplication17.Foo2::.ctor()
    IL_000f:  stloc.0 // 2. and this
    IL_0010:  nop
    IL_0011:  leave.s    IL_0023
  }  // end .try
  finally
  {
    IL_0013:  ldloc.1 // 3. and this
    IL_0014:  ldnull
    IL_0015:  ceq
    IL_0017:  stloc.2
    IL_0018:  ldloc.2
    IL_0019:  brtrue.s   IL_0022
    IL_001b:  ldloc.1
    IL_001c:  callvirt   instance void [mscorlib]System.IDisposable::Dispose()
    IL_0021:  nop
    IL_0022:  endfinally
  }  // end handler
  IL_0023:  nop

Така че наистина копира препратката и използва нейното копие по-късно, което ви позволява да нулирате променливата, без наистина да постигнете нищо, по отношение на финализатора.

Наистина, би било хубаво, ако можете да използвате само променливи „само за четене“ в оператора using. Малко е объркващо. И със сигурност MSDN е подвеждаща.

person Noon Silk    schedule 22.02.2010

Dispose ще бъде извикан на обекта, посочен в аргумента на използващата клауза. Толкова е просто.

person TheSoftwareJedi    schedule 22.02.2010
comment
използване на преводи, за да се опита най-накрая, така че не трябва ли да извиква последната препратка dispose? - person PRR; 22.02.2010