Как компилятор C # удаляет Debug.Assert в сборках релиза? - PullRequest
24 голосов
/ 22 апреля 2011

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

Ответы [ 3 ]

23 голосов
/ 22 апреля 2011

Debug.Assert объявлено с ConditionalAttribute;как указано в документации, это «[i] указывает компиляторам, что вызов метода или атрибут следует игнорировать, если не определен определенный символ условной компиляции.»

Компилятор C # имеет специальную поддержку для этого атрибута и удаляетDebug.Assert во время сборок релиза, поэтому он никогда не входит в составное дерево выражений.

Если вы щелкнете правой кнопкой мыши по одному из операторов Debug.Assert, вы сможете перейти к определению.Visual Studio покажет вам «код», сгенерированный из метаданных, и там вы увидите примененный атрибут [Conditional("DEBUG")].Так что этот код соблюдается, только когда DEBUG равен #define d как часть вашей сборки.

5 голосов
/ 22 апреля 2011

Методы в отладчике используют псевдо-настраиваемый атрибут ConditionalAttribute, который компилятор обнаруживает и удаляет любые вызовы любых методов с этим атрибутом, кроме указанного символа компиляции (в данном случае DEBUG) определяется. Любой может использовать атрибут в методах void без каких-либо параметров out.

4 голосов
/ 22 апреля 2011

Я не верю, что Debug.Assert каким-либо образом особенный; он просто использует атрибут Conditional, так что компилятор удаляет его, когда обнаруживает, что определение «препроцессора» не существует (в C # нет препроцессора!).

Вы можете использовать его, чтобы сделать то же самое (если вы определили DEBUG (или любой другой символ, который вы хотите включить, TRACE - еще один популярный):

[Conditional("DEBUG"), Conditional("TRACE")]
public void DebugOnlyMethod() {
    Console.WriteLine("Won't see me unless DEBUG or TRACE is defined");
}
...