Как компилятор С# удаляет Debug.Assert в версиях сборки?
Недавно я просмотрел код и подумал, нужно ли быть осторожным с выражениями, размещенными внутри операторов Debug.Assert
, такими как дорогостоящие операции или те, у кого есть побочные эффекты. Однако, похоже, компилятор довольно умен в отношении полного удаления оператора Assert
и внутренних выражений.
Например, следующее будет выводиться только на отладочные сборки:
static void Main(string[] args)
{
Debug.Assert(SideEffect());
}
private static bool SideEffect()
{
Console.WriteLine("Side effect!");
return true;
}
И это будет жаловаться на то, что o
используется до инициализации в выпусках:
static void Main(string[] args)
{
object o;
Debug.Assert(Initialize(out o));
o.ToString();
}
private static bool Initialize(out object o)
{
o = new object();
return true;
}
Он даже, кажется, не согласен с такими выражениями (печать "После" в обоих случаях):
static void Main(string[] args)
{
if (false) Debug.Assert(true);
Console.WriteLine("After");
}
Я был немного удивлен, насколько умным является компилятор и его способность правильно обнаруживать случаи, когда удаляется Debug.Assert
. Поэтому мне было любопытно.
- Как точно удаляется оператор? Дерево выражений должно быть создано до того, как оператор будет удален, чтобы правильно выполнить вышеуказанный оператор
if
.
- Является ли класс
System.Diagnostics.Debug
особенным здесь, или можно создать собственные методы с аналогичной обработкой?
- Есть ли способы "обмануть" препроцессор здесь? Еще лучше, есть ситуации, с которыми можно столкнуться в реальном коде, где это может быть проблематично?
Ответы
Ответ 1
Debug.Assert
объявлен ConditionalAttribute
; как указано в документации, это "[i] указывает компиляторам, что вызов или атрибут метода следует игнорировать, если не определен определенный условный символ компиляции".
Компилятор С# имеет определенную поддержку этого атрибута и удаляет Debug.Assert во время сборки релиза, поэтому он никогда не является частью встроенного дерева выражений.
Если вы щелкните правой кнопкой мыши на одном из своих операторов Debug.Assert
, вы сможете перейти к определению. Visual Studio покажет вам "код", сгенерированный из метаданных, и там вы можете увидеть атрибут [Conditional("DEBUG")]
. Поэтому этот код соблюдается только тогда, когда DEBUG
является #define
'd как часть вашей сборки.
Ответ 2
В методах отладчика используется псевдостандартный атрибут ConditionalAttribute
, который компилятор обнаруживает и удаляет любые вызовы любым методам с помощью этот атрибут, если не задан указанный символ компиляции (в данном случае DEBUG
). Любой может использовать атрибут в методах void
без каких-либо параметров out
.
Ответ 3
Я не считаю, что Debug.Assert особенным образом; он просто использует атрибут Conditional
, чтобы компилятор удалял его, когда обнаружил, что определение препроцессора не существует (у С# нет препроцессора!).
Вы можете использовать его так, чтобы сделать то же самое (до тех пор, пока вы определили DEBUG
(или какой-либо символ, который вы хотите включить, TRACE
является еще одним популярным):
[Conditional("DEBUG"), Conditional("TRACE")]
public void DebugOnlyMethod() {
Console.WriteLine("Won't see me unless DEBUG or TRACE is defined");
}