IDisposable: нужно ли проверять на null в finally {}? - PullRequest
11 голосов
/ 08 апреля 2010

В большинстве примеров, которые вы найдете в Интернете, когда явно не использует «использование», шаблон выглядит примерно так:

  SqlConnection c = new SqlConnection(@"...");
  try {
    c.Open();
  ...
  } finally {
    if (c != null) //<== check for null
      c.Dispose();
  }

Если вы используете «использование» и смотритев сгенерированном коде IL вы можете видеть, что он генерирует проверку на null

L_0024: ldloc.1 
L_0025: ldnull 
L_0026: ceq 
L_0028: stloc.s CS$4$0000
L_002a: ldloc.s CS$4$0000
L_002c: brtrue.s L_0035
L_002e: ldloc.1 
L_002f: callvirt instance void [mscorlib]System.IDisposable::Dispose()
L_0034: nop 
L_0035: endfinally 

Я понимаю, почему IL переводится для проверки на null (не знает, что вы делали внутри блока using),но если вы используете try..finally и у вас есть полный контроль над тем, как объект IDisposable используется внутри блока try..finally, действительно ли вам нужно проверять наличие нуля?если так, то почему?

Ответы [ 4 ]

12 голосов
/ 08 апреля 2010

операторы using могут инициализировать переменные вызовами, отличными от конструкторов. Например:

using (Foo f = GetFoo())
{
    ...
}

Здесь f может легко быть нулевым, тогда как вызов конструктора никогда не может 1 вернуть ноль. Вот почему оператор using проверяет на ничтожность. Это не связано с тем, что находится внутри самого блока, потому что оператор using сохраняет исходное начальное значение. Если вы напишите:

Stream s;
using (s = File.OpenRead("foo.txt"))
{
    s = null;
}

тогда поток все равно будет ликвидирован. (Если переменная объявлена ​​ в части инициализатора оператора using, она все равно доступна только для чтения.)

В вашем случае, поскольку вы знаете, что c не равен нулю, прежде чем войти в блок try, вам не нужно проверять наличие нуля в блоке finally, если вы не переназначаете его значение ( что я искренне надеюсь, что вы не!) в блоке.

Теперь с вашим текущим кодом равен небольшой риск того, что асинхронное исключение может возникнуть между присваиванием c и входом в блок try - но такого рода гонки трудно избежать условие полностью, так как после завершения конструктора может возникнуть асинхронное исключение, но до того, как значение будет присвоено c вообще. Я бы предположил, что большинству разработчиков не нужно беспокоиться о подобных вещах - асинхронные исключения имеют тенденцию быть достаточно «сложными», чтобы они все равно остановили процесс.

Есть ли причина, по которой вы все равно не хотите просто использовать оператор using? Если честно, я очень редко пишу свои finally блоки в эти дни ...


1 См. Ответ Марка и плач. Хотя обычно не актуально.

5 голосов
/ 08 апреля 2010

В вашем примере проверка на нулевое значение не требуется, так как c не может быть нулевым после явного построения.

В следующем примере (аналогично тому, который генерируется оператором using), конечно, необходима проверка на нулевое значение:

SqlConnection c = null; 
  try { 
    c = new SqlConnection(@"..."); 
    c.Open(); 
  ... 
  } finally { 
    if (c != null) //<== check for null 
      c.Dispose(); 
  } 

Кроме того, проверка на нулевое значение потребуется в следующем случае, поскольку вы не можете быть уверены, что CreateConnection не вернет нулевое значение:

SqlConnection c = CreateConnection(...); 
  try { 
    c.Open(); 
  ... 
  } finally { 
    if (c != null) //<== check for null 
      c.Dispose(); 
  } 
2 голосов
/ 08 апреля 2010

Джон уже рассмотрел основную точку здесь ... но просто случайный кусок пустяков; ваш конструктор может вернуть null. Не совсем обычный случай и, конечно, не реальная причина, по которой using делает это, и вы действительно не должны пытать свой код для этого - но это может произойти MyFunnyProxy).

0 голосов
/ 08 апреля 2010

Интересно .. Я использую VS2010, и я обнаружил, что с помощью моих пользовательских фрагментов кода (R :) - использование блоков качается.

  • В блоке использования нельзя назначить ноль (или что-нибудь в этом отношении) к переменной блока.Это приводит к ошибке компилятора CS1656
  • Далее присваивается блок var с помощью фабричного метода, который возвращает ноль.В этом случае пустой блок использования интеллектуально не вызывает Dispose.(Очевидно, вы получите NullReferenceException, если попытаетесь использовать блок var)
  • Далее в блоке try нет правил, вы можете присвоить переменной значение null.Следовательно, нулевая проверка перед Dispose обязательна в блоке finally.
...