Artykuł

freeimages.com freeimages.com
lip 03 2015
0

Efektywne wykorzystanie dyrektyw preprocessora

Tworzenie instrukcji warunkowych jest czymś naturalnym w praktycznie każdym języku programowania. Trudno sobie jest wyobrazić kod źródłowy nawet najprostszej aplikacji, który byłby pozbawiony konstrukcji IF - w pewnym sensie tego rodzaju instrukcje, kontrolują przepływ sterowania. Warto wiedzieć, że w C# nasze możliwości na tym polu są jeszcze większe m.in. dzięki dyrektywom preprocesora.

Dyrektywy preprocesora wpływają na to co zostanie skompilowane. Możemy np. inny fragment kodu przeznaczyć dla kompilacji Debug, a inny dla Release. W aplikacjach uniwersalnych możemy tworzyć warunki z perspektywy systemu docelowego (oczywiście ten element dotyczy części Shared/bibliotek Portable). Możliwości są znacznie większe, ale najważniejsze jest to, że nie musimy polegać na predefiniowanych przez Microsoft symbolach, lecz możemy też tworzyć swoje własne:)

Dyrektywy

Na stronie MSDNu można znaleźć listę 14 dyrektyw preprocesora, które możemy zastosować w naszym kodzie. Nie będę ich tutaj wszystkich szczegółowo opisywał, ale poniżej zaprezentuję krótką listę moich ulubionych:

  • #region - początek definicji regionu. Przez region rozumiemy określony blok kodu. Regiony bardzo często wykorzystywane są przez VS do oznaczenia metod implementowanych w ramach interfejsu X. W regionie możemy umieszczać dyrektywy z rodziny #if (instrukcja musi zostać zamknięta w obrębie regionu). Region można nazywać
  • #endregion - zamyka wcześniej otwarty region. Konstrukcja ta jest wymagana dla każdego otwartego regionu
    #region My first region
        // code
    #endregion
  • #define - tworzy definicję nowego symbolu. Przy instrukcjach warunkowych będzie on zwracać wartość True (warunek spełniony)
    #define DEBUG
  • #undef - pozwala na wyłączenie określonego symbolu. W instrukcjach warunkowych będzie on zwracać wartość False, co jest tożsame z niezdefiniowaniem
    #undef DEBUG
  • #if - sprawdza czy określony symbol jest włączony. Dyrektywa #if zawsze musi występować w parze z dyrektywą #endif
  • #elif - standardowa konstrukcja, która w normalnym języku jest równoznaczna z wyrażeniem else if. Sprawdza czy inny z podanych symbolów jest włączony. Instrukcja #elif musi zawsze występować w zestawie z dyrektywami #if oraz #endif
  • #else - dyrektywa, która jest wykorzystywana jeśli wszystkie pozostałe warunki zawiodą
  • #endif - zamyka blok instrukcji warunkowych

W dalszej części tekstu skupie się głównie na instrukcjach warunkowych oraz definiowaniu symboli. Na temat regionów, pisałem już trochę wcześniej przy okazji tekstu 6 rzeczy, których nie lubię w kodzie.

Instrukcje warunkowe - prosty przykład

Jednym z symboli, który nie jako jest definiowany z automatu, jest DEBUG. Staje się on aktywny (włączony) w sytuacji gdy w Visual Studio kompilujemy aplikację za pomocą konfiguracji Debug. Przy standardowej konfiguracji projektu, możemy bardzo łatwo sprawdzić jego działanie w praktyce:

#if DEBUG
Console.WriteLine("Aplikacja została skompilowana z konfiguracją Debug.");
#else
Console.WriteLine("Aplikacja została skompilowana z konfiguracją Release.");
#endif

Jak widać, przypomina to standardowe instrukcje if znane z języków programowania. Niestety dyrektywy preprocesora są w tym obszarze trochę ograniczone funkcjonalnie. Tak naprawdę z ich pomocą możemy sprawdzić tylko czy określony symbol jest włączony, czy też nie. Jeśli warunek nie jest spełniony, to:

  • Zostanie wykonane kolejne sprawdzenie, jeśli tylko zdefiniowano jakąkolwiek dyrektywę #elif i symbol w niej wskazany jest aktywny
  • Zostanie wykonana część kodu z dyrektywy #else, gdy wszystkie poprzednie sprawdzenia zawiodą
  • Nie stanie się nic, w sytuacji gdy żaden z warunków nie zostanie spełniony i nie określono dyrektywy #else

DEBUG jest predefiniowany w każdym standardowym projekcie w VS. W naszym kodzie możemy korzystać z innych utworzonych przez Microsoft symboli, a także możemy tworzyć swoje własne za pomocą dyrektywy #define, choć przeważnie nie daje nam to jakiś większych korzyści. Dużo ciekawszym rozwiązaniem jest tworzenie symboli per konfiguracja (o tym napiszę w kolejnym akapicie;-)

Visual Studio jest w stanie automatycznie wyszarzeć kod, który przy aktualnej konfiguracji nie zostanie wykonany (przykład na screenie poniższej):

Podobną sytuację możemy również zaobserwować w kodzie wspólnym Universal Apps (wspominałem o tym we wstępie). Tutaj za pomocą predefiniowanych symboli możemy warunkować kod ze względu na platformę docelową.

Przykładowe użycie własnych symboli

Najlepszym miejscem do tworzenia symboli, jest ekran właściwości projektu, na którym możemy zdefiniować nasz własny symbol. Wystarczy tylko w oknie projektu udać się na zakładkę Build, a następnie wybrać konfigurację oraz platformy i później dopisać nasz symbol do predefiniowanej listy:

Według przyjętej konwencji, symbol powinien być zapisany dużymi literami i w jego nazwie nie powinny znajdować się spacje (te zastępujemy znakiem podkreślenia). Z tak utworzonego symbolu możemy skorzystać w dyrektywie #if:

#if MY_SYMBOL
// do some code
#else
// do some code otherwise
#endif

Warto również wspomnieć, że nie musimy się ograniczać do domyślnych konfiguracji utworzonych w Visual Studio. Za pomocą Configuration managera możemy stworzyć swoje własne. Dzięki czemu istnieje szansa na utworzenie konfiguracji dla każdego środowiska docelowego na którym będzie funkcjonować nasza aplikacja. Możemy np. utworzyć pary konfiguracji - symboli:

  • Dev - DEV
  • PreProduction - PRE_PRODUCTION
  • Production - PRODUCTION

Co później może nam pozwolić na utworzenie np. takiego kodu:

#if DEV
string apiUrl = "http://api.localhost:12345";
#elif PRE_PRODUCTION
string apiUrl = "http://api.preproduction.com";
#elif PRODUCTION // bądź też po prostu #else
string apiUrl = "http://api.production.com";
#endif

Oczywiście to jest tylko przykład. Konfigurację adresu zawsze lepiej jest trzymać w pliku ustawień (np. Web.Config dla ASP.NET (MVC)) - szczególnie w przypadku aplikacji scentralizowanych, co innego jednak mobile..

Niezależnie od sytuacji, warto przynajmniej z grubnie znać możliwości dyrektyw, gdyż w pewnych sytuacjach mogą stanowić naprawdę ciekawą opcję;-)

Podoba Ci się ten wpis? Powiedz o tym innym!

Send to Kindle

Komentarze

blog comments powered by Disqus