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.