C #: использование блока: повторная инициализация объекта - PullRequest
5 голосов
/ 22 февраля 2010

Повторная инициализация внутри блока «using» - плохая идея, которую следует избегать всегда. Тем не менее я собираюсь спросить это:

Почему вызов "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 {}, последний метод удаления ссылки вызывается.

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();
  }
}

Ответы [ 6 ]

8 голосов
/ 22 февраля 2010

Существует две формы using операторов, определенных в спецификации C #:

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

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

8.13 Оператор использования

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

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

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

 using (expression) statement

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

Очевидно, что вы не можете изменить невидимую, недоступную переменную. Его значение присваивается только в условии использования resource-acquisition. Следовательно, оно будет иметь старое значение переменной, а не новое.

Когда вы имеете дело с присвоением уже объявленной переменной , вы используете эту форму оператора using. Тот факт, что вы присваиваете значение переменной , как

using ( x = something )

не имеет значения. Целое x = something рассматривается как выражение, и значение имеет только значение этого выражения. Важно знать, что «переменная ресурса» здесь не «х». Это невидимая переменная. С точки зрения компилятора, нет большой разницы между следующими конструкциями:

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

Во всех случаях выражение будет выполнено, и значение - это то, что получит гарантированное удаление, а не переменная. Что должен делать компилятор, если это не определенное поведение и вы написали третью строку в приведенном выше блоке? Утилизировать x? y? и то и другое? ни? Текущее поведение имеет смысл.

3 голосов
/ 22 февраля 2010

Компилятор генерирует этот код:

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 реализует контракт.

3 голосов
/ 22 февраля 2010

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

Если вы вызовете dispose для переназначенного значения, то исходное значение не будет уничтожено.

1 голос
/ 22 февраля 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 вводит в заблуждение.

1 голос
/ 22 февраля 2010

Кажется, что "using" создает свою собственную переменную для хранения ссылки, которая инициализируется как "a", которая, в свою очередь, инициализируется для первого экземпляра объекта. Позже, когда вы изменяете «a», вы на самом деле не изменяете исходную ссылку, которая была сохранена в выражении «using». Это кажется довольно хорошей функцией, так как использование отвечает за удаление фактического объекта, упомянутого в операторе использования, а не переменной.

0 голосов
/ 22 февраля 2010

Dispose будет вызываться для объекта, на который есть ссылка в аргументе выражения using. Это так просто.

...