C#'ta Önişlemci Direktifleri

Yazar: Cenk Özdemir
Kategori: C#.NET & VB.NET
Eklenme Tarihi: 13.5.2009 01:34:31



Herkese Merhabalar,Bu makalemizde, özellikle C programcılarının yakından tanıdıkları önişlemci direktiflerinin C# tarafında nasıl yer aldıklarını incelemeye çalışacağız.

Bu makalemizde, özellikle C programcılarının yakından tanıdıkları önişlemci direktiflerinin C# tarafında nasıl yer aldıklarını incelemeye çalışacağız. C ile daha önceden uğraşmış bir uygulama geliştiricisi için "önişlemci" ve "önişlemci direktifi" kavramları çok da yabancı değildir; ancak bir .NET programcısı açısından bakarsanız önişlemci direktifleri çok da fazla kullanılmayan, hakkında fazla konuşulmayan bir konudur. Bu yüzden işin C# tarafına geçmeden önce çok kısa bir şekilde "önişlemci" ve "önişlemci direktifi" kavramlarına C penceresinden bakmakta fayda vardır. Aşağıda, bir C programının çalışma sürecini gösteren şemayı incelediğimizde .NET mimarisinden farklı olarak derleyiciden önce çalışan başka bir yapı görürüz: Önişlemci. Bu yapının görevi, en kaba tabirle, kaynak kodu derleyiciye ulaşmadan önce düzenlemektir. Biraz daha açmak gerekirse C önişlemcisinin; yorum satırlarının atılması, makro tanımlanması, boş karakterlerin silinmesi gibi işleri yaptığı sayılabilir.



C önişlemcisinden kısaca bahsettiğimize göre gelelim önişlemci direktiflerine. Önişlemci direktifleri ise kaynak kod içerisine yazdığımız ve önişlemci tarafından ele alınarak bazı işler yapmamıza imkan veren özel direktiflerdir. Önişlemci, bu direktifler doğrultusunda bir çıktı oluşturarak derleyiciye ( compiler ) verir. Böylece derleme işlemine yön verilebilir, ekstra bir takım işler yaptırılabilir. Sanırım önişlemci direktifleriyle ilgili birçok soru işaretini yok etmiş olduk; tabi bununla birlikte birçok soru işareti de oluşturduk. C’deki süreç tamam, ama C# tarafında işler nasıl yürüyor olabilir? .NET mimarisi içinde de önişlemci diye bir yapı bulunur mu? C#’ta kullanabileceğimiz önişlemci direktifleri nelerdir ve hangi amaçla kullanılırlar? Yazının geri kalanında bu ve bunun gibi sorulara cevap vermeye çalışacağız. Öncelikle C#’ta kullanabileceğimiz önişlemci direktiflerini kullanım amaçlarına göre listeleyelim. Daha sonra bu direktiflerin her birini kısa kısa örneklerle mercek altına alalım.

Koşullu derleme
 #define
 #undef
 #if
 #else
 #endif
 #elif

Derleme
#error
#warning
#line
#pragma

Görsel
#region
#endregion
  C#’ta önişlemci direktifleri her ne kadar "önişlemci direktifleri" olarak adlandırılsalar da aslında bu isimlendirme yüzde yüz doğru değildir. Çünkü, .Net mimarisinde önişlemci diye bir kavram bulunmamaktadır. Ancak, alışılagelen önişlemci direktifleri gibi kullanıldıkları ve aynı davranışları gösterdikleri için bu şekilde bir adlandırmaya da yanlış denilemez.


A)Koşullu Derleme

a)Nedir?

   Temel çalışma mantığı if-else karar yapısından farklı olmamakla birlikte, #if-#endif anahtar kelimeleriyle tanımlanmış bir kod bloğu, yalnızca koşul sağlandığı taktirde derlenir. Örnek olarak aşağıdaki kodu inceleyelim.

static void Main(string[] args)
{
   Console.WriteLine("TEST");

   #if true
      Console.WriteLine("Kosul saglandi");
   #else
      Console.WriteLine("Kosul saglanmadi");
   #endif
}

Programın CIL çıktısına baktığımızda, koşulun sağlanmadığı durumlarda çalışacak olan kodun derlenmediğini görürürüz.
 .method private hidebysig static void Main(string[] args) cil managed
{
   .entrypoint
   // Code size 24 (0x18)
   .maxstack 8
   IL_0000: nop
   IL_0001: ldstr "TEST"
   IL_0006: call void [mscorlib]System.Console::WriteLine(string)
   IL_000b: nop
   IL_000c: ldstr "Kosul saglandi"
   IL_0011: call void [mscorlib]System.Console::WriteLine(string)
   IL_0016: nop
   IL_0017: ret
} // end of method Program::Main

Ancak burada #if direktifinin kullanımındaki kısıtların altını çizmek gerekmektedir. Tipik if karar yapısında, parantez içerisinde bool tipinden bir değişken kullanabilirken, #if önişlemci direktifinde böyle bir imkanımız yoktur. Benzer şekilde, #if direktifinde koşul olarak a<b, 1==1 gibi ilişkisel operatörlerin kullanımları da mümkün değildir. Peki öyleyse bu direktifi yalnızca true ya da false anahtar kelimeleriyle mi kullanabiliriz? Tabi ki hayır. Zaten takdir edersiniz ki bu kullanım çok da işe yarar görünmüyor. Bunun yerine, yine bir önişlemci direktifi olan #define ifadesinden faydalanarak kendi koşullarımızı tanımlayabilir ve bu koşulları #if ile birlikte kullanabiliriz.

  if(true)
    Console.Write("a");
 else
    Console.Write("b");

 Normal if kullanımında da gerçeklememesi kesin olan satırlar ( Unreachable code ) programın IL çıktısında yer almaz.

b) #define önişlemci direktifi nasıl kullanılır?

   #if ifadesinde kullanmak üzere koşul tanımlamak için #define direktifinden faydalanırız. Örnek kullanım için aşağıdaki kodları inceleyelim.

#define KONTROL
using System;

namespace OnIslemciDirektifleri
{
   class Program
   {
      static void Main(string[] args)
      {
                 
         #if KONTROL
            Console.Write("Kosul ");
            Console.WriteLine("saglandi");
         #else
            Console.WriteLine("Kosul saglanmadi");
         #endif
      }
   }
}

Yukarıdaki kaynak kodu çalıştırdığımızda, KONTROL koşulunun daha önce tanımlanmış olmasından ötürü #if-#else içerisinde kod bloğu işletilecektir.
Dikkat etmek gereken nokta, koşulun tanımlandığı yerin tüm kodun üzerinde ( using bloğunun da) oluşudur. Aksi taktirde kaynak kod derlenmez.


c)Farklı "koşul tanımlama" yolları

   1-Proje özelliklerinde koşul tanımlama
   
    #define önişlemci direktifi dışında, proje özelliklerinden faydalanarak da koşul tanımlaması yapılabilir. Bunun için proje özellikleri altındaki Build seçeneğinde, "Conditional compilation symbols" yazan yere derleme koşullarını söylemek gerekir. Birden fazla koşul tanımlaması yapılırken, koşullar arasında bir boşluk bırakılabilir,virgül ya da noktalı virgül konabilir. Örnek olarak aşağıdaki gibi bir koşul tanımlamasının sonuçlarını inceleyelim.



using System;

namespace OnIslemciDirektifleri
{
   class Program
   {
      static void Main(string[] args)
      {
                 
         #if CENK
            Console.WriteLine("CENK kosulu tanimli");
         #endif
         #if OZDEMIR
            Console.WriteLine("OZDEMIR kosulu tanimli");
         #endif


         // Tanımlanan kosullar iki kelimeden olusamazlar.Dolayısıyla asagidaki kod hata üretir.
         //#if CENK OZDEMIR
         //   Console.WriteLine("CENK OZDEMIR kosulu tanimli");
         //#endif
      }
   }



 2-Komut satırında koşul tanımlama

    Komut satırından csc.exe’yi kullanarak yaptığımız derleme sırasında da koşul tanımlama imkanımız vardır. Bunun için, derlenmesini istediğimiz kaynak kod dosyasının adını söyledikten sonra /define anahtarından faydalanmak gereklir.

Test.cs
using System;

namespace OnIslemciDirektifleri
{
   class Program
   {
      static void Main(string[] args)
      {                 
         #if KOSUL1
            Console.WriteLine("KOSUL1 tanimli");
         #endif

         #if KOSUL2
            Console.WriteLine("KOSUL2 tanimli");
         #endif
      }
   }
}

Daha sonra kaynak kodumuzu komut satırından aşağıdaki gibi derleyip çalıştıralım.



d)Tanımlanmış bir koşulu kaldırmak = #undef önişlemci direktifi

   Tanımlanmış bir koşulu kaldırmak için #undef direktifinden faydalanılır. Örnek olarak aşağıdaki kodu inceleyelim.

#define DENEME
#undef DENEME

using System;

namespace OnIslemciDirektifleri
{
   class Program
   {
      static void Main(string[] args)
      {                 
         #if DENEME
            Console.WriteLine("DENEME tanimli...");
         #else
            Console.WriteLine("DENEME tanimli degil...");
         #endif
      }
   }
}


Ancak, kabul edersiniz ki kaynak kod içerisinde bir koşul tanımlayıp kaldırmak demek, o koşulun hiç tanımlanmamış olmasıyla eşdeğerdir. Dolayısıyla #undef direktifini işe yarar şekilde kullanabilmek için koşul tanımlamasını kaynak kod dışında yapmak gerekir. Bu da proje özelliklerini ya da komut satırını kullanarak yaptığımız koşul tanımlamalarını kaynak kod içerisinde ezebileceğimiz anlamına gelir. Yani kaynak kod dışında bir koşul tanımlaması yapılırsa, tanımlanmış koşul kaynak kod içerisinden #undef direktifi ile kaldırılabilir.

Test.cs
#undef IKINCI

using System;

namespace OnIslemciDirektifleri
{
   class Program
   {
      static void Main(string[] args)
      {
         #if BIRINCI&&IKINCI
            Console.WriteLine("BIRINCI ve IKINCI kosul tanimli");
         #elif BIRINCI
            Console.WriteLine("BIRINCI kosul tanimli,IKINCI kosul tanimli degil");
         #elif IKINCI
            Console.WriteLine("IKINCI kosul taimli,BIRINCI kosul tanimli degil");
         #endif
      }
   }
}



e)Conditional Niteliği ( Attribute )

   Conditional niteliği metot ya da nitelik sınıflarına ( attribute classes ) uygulanarak, ilgili metot ya da niteliğin kullanılmaya çalışılığındaki davranışını belirler. Bahsettiğimiz bu noktalarda #if-#endif kullanımı mümkün olmadığından böyle bir niteliğe ihtiyaç duyulmuştur. Benzer şekilde #if-#endif kullanabildiğimiz noktalarda da Conditional niteliğini kullanamayız.

  Conditional niteliği, yalnızca geriye değer döndürmeyen metotlar ve nitelik sınıflarıyla ( attribute classes ) birlikte kullanılabilir.

Aşağıdaki örneği inceleyelim.

using System;
using System.Diagnostics;

namespace OnIslemciDirektifleri
{
   class Program
   {
      static void Main(string[] args)
      {
         UcgenCiz();
      }

      [Conditional("TEST")]
      static void UcgenCiz()
      {
         Console.WriteLine("*\n**\n***\n****\n*****");
      }
   }
}

Programın çıktısı aşağıdaki gibidir.



UcgenCiz() metodunun çalışmadığını görüyoruz. Peki metodun kendisi IL kodunda yer almış mıdır? Test  için IL Dissambler ( ildasm ) aracını kullanarak programın IL çıktısına bakalım.




        IL çıktısından da görüldüğü üzere Conditional niteliği uygulanmış metot derlenir; ancak çağırılmaz.

B)#error, #warning, #line, #pragma

a)Derleyiciye hata ve uyarı verdirmek

   Derleyicinin, derleme işlemini engelleyecek şekilde bir hata üretmesini sağlamak için #error, derleme işlemini engellemeden bir uyarı üretmesini sağlamak için de #warning direktifi kullanılır.

using System;

namespace OnIslemciDirektifleri
{
   class Program
   {
      static void Main(string[] args)
      {
         int sayi = 1 / 0;
      }

      static void Baglan()
      {
         #error Baglan metodu henüz yazılmadı.
      }

      static bool KullaniciBul(int id)
      {
         #error KullaniciBul metodu henüz yazılmadı.
      }

      static bool KullaniciSil(int id)
      {
         #warning KullaniciSil metodu henüz yazılmadı.
      }
   }
}

Yukarıdaki kodu derlemeye çalıştığımızda Error List aşağıdaki gibi görünür.



Burada dikkatinizi çekmek istediğim nokta, #error direktifini kullandığımızda, kodtaki diğer hataları ele alamıyor oluşumuzdur. Aslında kodumuz içerisinde üç tane hata mevcut ( 0’a bölmeye çalıştığımız satır, geriye bir şey döndürmeyen KullaniciBul(int id) ve KullaniciSil(int id) metotları ) olmasına rağmen, yalnızca  #error ve #warning direktiflerince üretilen hata ve uyarılar ele alınabilmektedirler.

b)#line önişlemci direktifi

    1-Satır numaralarıyla oynamak

     #line direktifini kullanarak kaynak kodumuzun nasıl satırlandırılacağını söyleyebiliriz. Bu kullanıma örnek olarak da aşağıdaki kodu inceleyelim.

using System;

namespace OnIslemciDirektifleri
{
   class Program
   {
      static void Main(string[] args)
      {
         #line 55 "bizimDosya"
            int sayi1 = 1 / 0; //10
         #line 10 "bizimDosya" //11
            int sayi1; //12
         #line 80 "bizimDosya" //13
                                      //14
            int sayi2 = 1 / 0; //15
         #line 1 "sizinDosya"  //16
            int sayi2; //17
      }
   }
}

Kod derlendikten sonra, hata oluşan satırlar 10,12,15 ve 17 olmasına rağmen aşağıdaki Error List görünür.



2-Hata ayıklayıcısına yön vermek

    #line önişlemci direktifinin bir diğer kullanım alanı da hata ayıklama sırasında içerisine girmek istemediğimiz kod blokları oluşturmaktır.

using System;

namespace OnIslemciDirektifleri
{
   class Program
   {
      static void Main(string[] args)
      {
         #line hidden
         Console.WriteLine("Cenk");
         Console.WriteLine("Özdemir");

         #line default
         Console.Write("C#");

         #line hidden
         Console.WriteLine(" Nedir?");
      }
   }
}

Örnekteki kod içerisindeki hatalar ayıklanırken ( debug ) adım adım ilerlediğimizde, "#line hidden" satırından sonraki satırlara geçilmez, "#line default" satırına gelindikten sonra ise normal davranışa geri dönülür.

c) #pragma önişlemci direktifi

   1- Uyarıları düzenlemek = #pragma warning

   C#’ta zaman zaman kodun çalışmasına engel teşkil etmeyen uyarılarla karşılaşırız. #pragma warning ifadesi ise bize tüm uyarıları ya da belli bir uyarıyı kapatıp/açma, kısaca uyarıları yönetme olanağı sunar. Örnek kullanım için aşağıdaki kodu inceleyelim.




  Tüm uyarılarlarla ilgili seçenekleri, proje özelliklerinde ( Project Properties ) kod analiz ( Code Analysis ) seçeneği altında bulabilirsiniz.

   2- #pragma checksum

   Aynı isme sahip kaynak kod dosyalarının hata ayıklanması ( debug ) sırasında karışıklık yaratmaması için, ilgili pdb dosyasında bir kontrol verisi ( checksum ) bulunur. Bu özellik Visual Studio 2005 ile birlikte gelmiştir ve bahsettiğimiz isim karmaşasının önüne geçerek, hata ayıklayıcısının ( debugger ) doğru dosyayı kullandığının doğrulamasını sağlar. Ancak bu yöntem ASP.NET projelerinde çalışmaz. Çünkü, kontrol verisi .aspx sayfaları için değil kaynak kod için oluşturulur. Dolayısıyla farklı yollar altındaki aynı isimli .aspx dosyaları, hata ayıklanması sırasında problem yaratabilir. Böyle bir durumda #pragma checksum ifadesi ile ASP.NET projelerinde .aspx sayfaları için cheksum oluşturulabilir.

#pragma checksum "Default.aspx" "{3673e4ca-6098-4ec1-890f-8fceb2a794a2}" "012345678AB14232"

C)Görsel önişlemci direktifleri: #region-#endregion

   #region ifadesi, Visual Studio ile çalışırken kaynak kodları saklamak için kullanılır. Sadece, uygulama geliştiricisine görsel anlamda kolaylık sağlar, bunun dışında programa herhangi bir etkisi yoktur.

   Örnek



Böylece, C# önişlemci direktiflerini incelemeye çalıştığımız makalemizin de sonuna geldik. Bir dahaki makalede görüşmek üzere, hoşçakalın.

Cenk Özdemir


Cenk Özdemir

Programcılığa C programlama dili ile başlayan Cenk ÖZDEMİR, Visual Basic, PHP, Java gibi farklı diller ile de uygulamalar geliştirmiştir. Uzun süredir .NET Platformunda çalışmalarını sürdüren ÖZDEMİR, özellikle C# programlama dili, Object Oriented Programlama ve .NET Framework mimarisi üzerinde uzmanlaşmıştır.  Kendi geliştirdiği eğitim içeriğiyle Türkiye'nin önde gelen kurumlarından birinde yazılım eğitimleri vermekte olup, çalışmalarına tüm hızıyla devam etmektedir.

Blog: http://www.cenkozdemir.com
Bu makaleye ilk yorum yapan siz olun.

Yorumunuz