IDisposable: нужно ли проверять значение null на finally {}?

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

  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 (не знает, что вы делали внутри блока использования), но если вы используете try..finally, и у вас есть полный контроль над тем, как используется IDisposable-объект внутри блока try..finally вам действительно нужно проверить значение null? если да, то почему?

Ответ 1

Операторы

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

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

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

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

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

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

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

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


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

Ответ 2

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

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

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

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

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

Ответ 3

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

Ответ 4

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

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