Подтвердить что ты не робот

Как компилятор С# удаляет 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 особенным здесь, или можно создать собственные методы с аналогичной обработкой?
  • Есть ли способы "обмануть" препроцессор здесь? Еще лучше, есть ситуации, с которыми можно столкнуться в реальном коде, где это может быть проблематично?
4b9b3361

Ответ 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");
}