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

Повторная инициализация в блоке «using» — плохая идея, которой следует избегать всегда. И все же я спрошу это:

Почему «использующий» вызов распоряжается исходным значением, а не последней ссылкой или повторной инициализацией (что происходит, если используется блок try finally)

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

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

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

Однако с кодом try {} finally вызывается метод удаления последней ссылки.

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

MSDN: оператор using гарантирует, что 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

В спецификации C# определены две формы операторов using:

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

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

8.13 Оператор using

[...] В любом расширении переменная ресурса доступна только для чтения во встроенном операторе.

Здесь мы имеем дело со второй формой: где resource-acquisition — это expression, а не local-variable-declaration. В этом случае спецификация С# ясно говорит:

Оператор использования формы

 using (expression) statement

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

Очевидно, что вы не можете изменить невидимую, недоступную переменную. Его значение присваивается только в предложении 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

Использование можно рассматривать как обещание вызвать объект, объявленный с помощью использования. Это единственное, что, ИМХО, имеет смысл!

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

person TheSoftwareJedi    schedule 22.02.2010
comment
используя переводы, чтобы попробовать, наконец, поэтому не следует ли вызывать последнюю ссылку? - person PRR; 22.02.2010