Один мой друг как-то захотел сделать демо написанного собственным потом и кровью SDK на .NET. Естественно нужно было урезать часть очень важного функционала. Сделать это нужно было так как всем известно как в .NET происходит защита исходного кода. Плохо если не сказать другим словом – обфускация.
Обфускация по сути переименование и переименование это касается только приватной или внутренней реализации программы. Но SDK в силу того что нужно выставлять часть функционала наружу поддается защите обфускатором заметно хуже.
Поэтому необходимо было сделать демо. Можно было конечно покромсать код вручную – но это не выход. Во-первых, потому что это не разовая работа, а такое проделывать необходимо столько раз сколько билдов отдаются на растерзание публики и, во-вторых, это прямой путь к мешанине, неразберихе, путанице( так как какой-то код добавляется, какой-то удаляется) и как следствие к багам. Тем более, что на такую пустую работу при наличие большого количества “непубличного” функционала будет уходить достаточно много ценного времени.
Как же в такой ситуации быть? Есть необходимость собирать билд с включением или выключением определенного функционала условно.
В это случае поможет условная компиляция. Такую возможность C# как язык унаследовал от С/С++ (всем достаточно известно использование #define, #ifdef и т.п. в С/С++ ). Их хотя в шарпе уже не напишешь тех изощренных макросов как в си, но все же аналогичные директивы в C# имеют свое применение.
Пользоваться этим достаточно просто. Необходимо используя директиву #define объявить константу условной компиляции.
#define TEST
Также константу условной компиляции можно объявить используя опцию компилятора /define. А так как это можно сделать в опциях, тогда то же самое можно сделать и в свойствах проекта используя Visual Studio (вкладка Builde > Conditional compilation symbols):
Правда есть отличия константа объявленная директивой распространяет свое действие только на файл в котором она объявлена, для того чтобы ее действие распространялось на весь проект следует ее объявить через опции.
При создании нового проекта для него создаются две конфигурации – Debug и Release, для первой объявлено по умолчанию две константы DEBUG и TRACE, для релизной конфигурации – только TRACE.
После объявления такую константу можно использовать либо с #if, #endif, #else директивами, либо в связке с атрибутом ConditionalAttribute. Например, код можно обернуть директивами:
#if (DEBUG)
Debug.Listeners.Remove("Default");
Debug.Listeners.Add(new SuperAssertTraceListener());
#endif
или с #else:
#if (DEBUG)
stream = new StreamWriter("c:\\temp\\WorkTimeTracer.txt");
#else
stream = null;
#endif
На директивы никаких ограничений в отличии атрибута – нет, если что-то не так код не скомпилируется. Но с атрибутом код более читабелен. На метод помеченные ConditionalAttribute накладываются следующие ограничения:
- Такой метод не должен возвращать значений
- Не должен быть объявлен в интерфейсе
- Не должен быть методом с идентификатором override.
Хотя ConditionalAttribute имеет один недостаток по сравнению с использованием директив. При компиляции в Release варианте, метод помеченный таким атрибутом сохраняется в MSIL коде.
Суть этих ограничений вполне логична. Ведь в конфигурациях отличных от указанной в атрибуте, тело метода будет восприниматься как комментарий. Если посмотреть на класс System.Diagnostics.Debug к примеру то там многие методы помечены этим атрибутом.
[Conditional("DEBUG")]
public static void Trace(string message)
{
Console.WriteLine(message);
}
На самом деле применять условную компиляцию можно не только для того чтобы исключать функционал нужный для дебага, или для того чтобы сделать демку. Условную компиляцию стоит применять в “кроссплатформенной” разработке, например когда пишется клиентская часть на Silverlight и WPF, или для обычного и .NET Compact Framework
SabMakc: Следует помнить, что использовать такие директивы следует с очень большой осторожностью, т.к. могут возникнуть труднонаходимые ошибки (особенно если проект развивается длительное время).
Происходит это чаще всего тогда когда в директивы помещается часть какой-то целой функциональности или логики, которая влияет на результат. Хотя всегда остается возможность применить атрибут.