В большинстве примеров, которые Вы находите в сети, явно не используя "использование", шаблон смотрит что-то как:
SqlConnection c = new SqlConnection(@"...");
try {
c.Open();
...
} finally {
if (c != null) //<== check for null
c.Dispose();
}
Если Вы действительно используете "использование" и смотрите на сгенерированный код IL, Вы видите, что это генерирует чек для пустого указателя
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 переводится для проверки на пустой указатель (не знает то, что Вы сделали в блоке использования), но если Вы используете попытку.. наконец и Вы имеете полный контроль над тем, как объект IDisposable привыкает в попытке.. наконец блок, необходимо ли действительно проверить на пустой указатель? если так, почему?
операторы using могут инициализировать переменные вызовами, отличными от конструкторов. Например:
using (Foo f = GetFoo())
{
...
}
Здесь f
легко может быть нулевым, тогда как вызов конструктора никогда не может 1 вернуть null. Вот почему оператор using
проверяет значение null. Это не имеет отношения к тому, что находится внутри самого блока, потому что оператор using
сохраняет исходное начальное значение. Если вы напишете:
Stream s;
using (s = File.OpenRead("foo.txt"))
{
s = null;
}
, поток все равно будет удален. (Если переменная объявлена в части инициализатора оператора using
, она все равно доступна только для чтения.)
В вашем случае, как вы знаете, c
не является нулевым до того, как вы войдете в блок try
, вам не нужно проверять нулевое значение в блоке finally
, если вы не переназначаете его значение (что я искренне надеюсь вы не!) внутри блока.
Теперь с вашим текущим кодом есть небольшой риск того, что может возникнуть асинхронное исключение между назначением c
и входом в блок try
- но трудно полностью избежать такого рода состояния гонки, поскольку асинхронное исключение может также возникнуть после завершения работы конструктора, но до того, как значение будет вообще присвоено c
. Я бы посоветовал большинству разработчиков не беспокоиться о подобных вещах - асинхронные исключения имеют тенденцию быть достаточно «сложными», чтобы они в любом случае остановили процесс.
Есть ли причина, по которой вы все равно не хотите просто использовать оператор using? Честно говоря, я очень редко пишу свои собственные блоки finally
в наши дни ...
1 См. Ответ Марка и плачу. Хотя обычно это не актуально.
Джон уже затронул здесь основную мысль ... но это всего лишь случайная мелочь; ваш конструктор может вернуть null
. Не совсем частый случай и, конечно, не настоящая причина того, что с использованием
делает это, и вы действительно не должны мучить свой код для этого - но это может случиться (ищите MyFunnyProxy
).
В вашем примере проверка на null не требуется, поскольку c не может иметь значение null после явного построения.
В следующем примере (аналогично тому, что генерируется оператором using), конечно же, необходима проверка на null:
SqlConnection c = null;
try {
c = new SqlConnection(@"...");
c.Open();
...
} finally {
if (c != null) //<== check for null
c.Dispose();
}
Кроме того, проверка на null потребуется в следующем случае, как вы можете Не нужно быть уверенным, что CreateConnection не вернет null:
SqlConnection c = CreateConnection(...);
try {
c.Open();
...
} finally {
if (c != null) //<== check for null
c.Dispose();
}
Интересно ... Я использую VS2010 и нахожу это с помощью собственных фрагментов кода (R :) - с использованием блоков рок.