Struct, Class ve Default Constructors - İnanmak İstiyorum

Merhaba Arkadaşlar,

Sanıyorum 90 yıllarda en çok izlediğim dizilerden birisi The X Files (Gizli Dosyalar) idi. Her bölümde olağan dışı konuların ele alındığı ve birbirlerine tamamen zıt iki karekter olan ama bu sayede gizli dosyaların işlenmesinde birbirlerini sürekli olarak dengeleyen Ajan Dana Scully ve Fox Mulder' ın maceraları gerçeklikten uzak olsa da, benim gibi bilim kurgu severleri ekrana bağlamak için yeterliydi.

Diziyi her izleyişimde en çok hoşuma giden sahneler ise genellikle Fox Mulder' ın bir soru sorup ortağı dahil herkesi bir kaç saniyeliğine de olsa düşünmeye itmesiydi(yeni nesil argoda ifade etmek istersek dumura uğratmasıydı). Geçtiğimiz günlerde yine X Files dizisinin bir kaç bölümünü heyecanlı bir şekilde tekrardan izlerken bazen yaptığımız işte de, Ajan Fox Mulder' ın ürettiği gibi şüphe uyandırıcı soruların oluştuğunu düşünmeye başladım. Tabi soruyu hemen sormak istemiyorum. Olaya biraz daha heyecan katmakta yarar var. Bakalım bu yazımızın Fox Mulder' ı olabilecek miyim?

Konumuz C# programlama dilinin temelleri arasında sayılmaktadır. Class ve Struct tiplerinde Default Constructor(Varsayılan Yapıcı Metod) kullanımı. Bildiğiniz üzere .Net Framework Base Class Library üzerinde tiplere en üst seviyeden baktığımızda değer(Value) ve referans(Reference) türleri olarak ikiye ayırmaktayız. Struct tipleri değer türü, Class' lar ise referans türleri olarak ele alınmaktadır. Bu iki veri türününün bellekte tutuluş şekilleri, aralarındaki atamalarda gösterdikleri davranışsal farklılıklar bir kenara, özellikle kendi geliştirdiğimiz taslaklarında dikkat etmemiz gereken noktaların başında da, yapıcı metod kullanımları gelmektedir. Dilerseniz aşağıdaki kod parçasını göz önüne alarak işe koyulalım.

namespace ClassStructAndConstructors
{
    class Program
    {
        static void Main(string[] args)
        {
        }
    }

    class Person
    {
        public int PersonId;
        public string Name;
        public double Salary;

        public Person()
        {
            
        }
    }

    struct Product
    {
        public int ProductId;
        public string Title;
        public double ListPrice;

        public Product()
        {

        }
    }
}

Bu kod parçasında Person isimli bir Class ve Product isimli bir Struct tanımlanmıştır. Her iki tip içerisinde de varsayılan yapıcı metod(Default Constructor) kullanımı söz konusudur. Ancak kodu derlediğimizde bir hata mesajı ile karşılaştığımızı görürüz.

Kural 1 geliyor : Bu Struct olmanın kurallarından birisidir. Buna göre bir Struct' ın, Class' ların aksine parametresi olmayan yapıcı metod içermesi söz konusu değildir. Çok doğal olarak aşağıdaki gibi bir kullanım tercih edilebilir.

namespace ClassStructAndConstructors
{
    class Program
    {
        static void Main(string[] args)
        {
        }
    }

    class Person
    {
        public int PersonId;
        public string Name;
        public double Salary;

        public Person(int pId,string name,double salary)
        {
            
        }
    }

    struct Product
    {
        public int ProductId;
        public string Title;
        public double ListPrice;

        public Product(int pId,string title,double listPrice)
        {          
        }
    }
}

Bu kez hem Class hem de Struct için parametre alan yapıcı metodlar söz konusudur. Ancak yine bir Compile Time hatası alınılması kaçınılmazdır.

Dikkat edileceği üzere Class tipi için parametreli kullanım söz konusu iken, Struct için aynı durum gerçeklenememektedir.

Kural 2 geliyor : Bir Struct için parametreli yapıcı metod kullanılması halinde, içerideki tüm alanlara ilk değerlerinin atanması gerekmektedir. Dolayısıyla Struct' ımıza ait parametreli yapıcı metod içeriği aşağıdaki gibi düzenlenirse sorun kalmayacaktır.

public Product(int pId,string title,double listPrice)
{
   ProductId = pId;
   Title = title;
   ListPrice = listPrice;
}

Şimdi kod parçasını aşağıdaki gibi değiştirdiğimizi düşünelim.

namespace ClassStructAndConstructors
{
    class Program
    {
        static void Main(string[] args)
        {
            Person burak = new Person();
            burak.PersonId = 1001;
            burak.Name = "Burak Selim Şenyurt";
            burak.Salary = 1;

            Product mouse = new Product();
            mouse.ProductId = 2001;
            mouse.Title = "Microsoft Optical Mouse";
            mouse.ListPrice = 9.99;
        }
    }

    class Person
    {
        public int PersonId;
        public string Name;
        public double Salary;
    }

    struct Product
    {
        public int ProductId;
        public string Title;
        public double ListPrice;
    }
}

Bu sefer ne Class ne de Struct için bir yapıcı metod bildiriminde bulunulmamıştır. Kod başarılı bir şekilde derlenmiştir. Main metodu içerisinde ise her iki tipe ait nesne örnekleri oluşturulmuş ve alanlarına bazı değerler atanmıştır. Hımmm...İşte Fox Mulder sorusu geliyor?

Struct' lar için açık bir şekilde varsayılan yapıcı metod tanımlayamıyorsak eğer, nasıl oluyorda new Struct() şeklinde bir metod çağrısı yapabiliyoruz?

Çok doğal olarak burada Dana Scully olsaydı söz konusu soruyu cevaplamak için hasta üzerinde otopsi yapmaya, bir başka deyişle IL(Intermediate Language) koduna bakmaya karar verirdi Wink Biz de böyle yapıyor olacağız. (Bu amaçla ILDASM veya Red Gate' s .Net Reflector gibi araçlardan yararlanabiliriz)

Person sınıfı için üretilen IL çıktısı aşağıdaki gibidir.

.class private auto ansi beforefieldinit Person
    extends [mscorlib]System.Object
{
    .method public hidebysig specialname rtspecialname instance void .ctor() cil managed
    {
        .maxstack 8
        L_0000: ldarg.0 
        L_0001: call instance void [mscorlib]System.Object::.ctor()
        L_0006: ret 
    }

    .field public string Name
    .field public int32 PersonId
    .field public float64 Salary

}

Dikkat edileceği üzere varsayılan bir yapıcı metod otomatik olarak üretilmiştir. Ajan Dana Scully bu durum karşısında Product isimli Struct için de aynı durumun söz konusu olacağını düşünmektedir. Yüzünde bir tebessümle Mulder' a döner ve "Tamam Buldum" der gibi bakar. Oysaki mercekten tekrar baktığında aşağıdaki IL çıktısı ile karşılaşacaktır.

.class private sequential ansi sealed beforefieldinit Product
    extends [mscorlib]System.ValueType
{
    .field public float64 ListPrice
    .field public int32 ProductId
    .field public string Title
}

Fark edileceği üzere burada ctor gibi bir metod tanımı bulunmamaktadır. Demek ki yanlış bir yerde arama yapılmaktadır. Scully derhal merceği ayarlar ve bu kez Main metodu içeriğine odaklanır.

.class private auto ansi beforefieldinit Program
    extends [mscorlib]System.Object
{
    .method public hidebysig specialname rtspecialname instance void .ctor() cil managed
    {
        .maxstack 8
        L_0000: ldarg.0 
        L_0001: call instance void [mscorlib]System.Object::.ctor()
        L_0006: ret 
    }

    .method private hidebysig static void Main(string[] args) cil managed
    {
        .entrypoint
        .maxstack 2
        .locals init (
            [0] class ClassStructAndConstructors.Person burak,
            [1] valuetype ClassStructAndConstructors.Product mouse)
        L_0000: nop 
        L_0001: newobj instance void ClassStructAndConstructors.Person::.ctor()
        L_0006: stloc.0 
        L_0007: ldloc.0 
        L_0008: ldc.i4 0x3e9
        L_000d: stfld int32 ClassStructAndConstructors.Person::PersonId
        L_0012: ldloc.0 
        L_0013: ldstr "Burak Selim \u015eenyurt"
        L_0018: stfld string ClassStructAndConstructors.Person::Name
        L_001d: ldloc.0 
        L_001e: ldc.r8 1
        L_0027: stfld float64 ClassStructAndConstructors.Person::Salary
        L_002c: ldloca.s mouse
        L_002e: initobj ClassStructAndConstructors.Product
        L_0034: ldloca.s mouse
        L_0036: ldc.i4 0x7d1
        L_003b: stfld int32 ClassStructAndConstructors.Product::ProductId
        L_0040: ldloca.s mouse
        L_0042: ldstr "Microsoft Optical Mouse"
        L_0047: stfld string ClassStructAndConstructors.Product::Title
        L_004c: ldloca.s mouse
        L_004e: ldc.r8 9.99
        L_0057: stfld float64 ClassStructAndConstructors.Product::ListPrice
        L_005c: ret 
    }
}

new Person() çağrısı için IL tarafında newobj çağrısını gerçekleştirilirken, new Product() satırına karşılık olarak initobj isimli bir çağrının gerçekleştirildiği görülmektedir. initobj için MSDN kaynaklarında yapılan açıklama şu şekildedir.

Bir başka deyişle initobj çağrısının uygulandığı Struct içerisindeki Primitive tipler için, varsayılan ilk değer atamaları gerçekleştirilmektedir. Buna göre sayısal değerler için 0 veya 0.0, bool için false ve referans türleri için de null değerlerin atanması söz konusudur. Açıkça ifade etmek gerekirse Struct' lar için varsayılan bir yapıcı metod söz konusu olmasa dahi, IL tarafında bu fonksiyonelliği üstelenen bir çağrı yapılmaktadır (Base Class Library Team tarafından bu durum implicit default constructor) olarak adlandırılmaktadır.

Ajan Scully gözlerini mercekten kaldırır ve Fox' a döner. Bu kez yüzünde haklı bir tebessüm vardır ve "Endişelerin boşunaymış Mulder. Uzaylıların bu işle hiç bir alakası yok" der Laughing Başka bir macerada görüşünceye dek hepinize mutlu günler dilerim.

ClassStructAndConstructors.rar (21,32 kb) [Örnek Visual Studio 2010 Ultimate RTM sürümü üzerinde geliştirilmiş ve test edilmiştir]

Yorumlar (7) -

  • Çok güzel bir yazı olmuş elinize sağlık, default constructor olayını hiç düşünmemiştim. Kullanıyoruz ama, biride içini açıp bakmalı. Harbiden içini açıp bakmışsınız Smile.

    Bir araları kafama feci takıldığından soruyorum, belki buda bir X-Files bölümü olabilirmi bilmem ama Smile.

    public struct Kategori
    {
       public List<Kategori> Kategoriler = new List<Kategori>();
    }

    Struct içinde şöyle bir yazım biçimi hemen "'StructAndClass.Program.Kategori.Kategoriler': cannot have instance field initializers in structs" şeklinde hata veriyor :O. Ama bu bir class olsaydı.

    public class Kategori
    {
        public List<Kategori> Kategoriler = new List<Kategori>();
    }

    ozaman "build succeeded!" (Y). Ama struct'ta da biçare ne yaptım bende aşağıdaki gibi bir constructor içinde elimle tanımladım.

    public struct Kategori
    {
        public List<Kategori> Kategoriler;
        public Kategori (string parametreninbiri)
        {
            Kategoriler=new List<Kategori>();
        }
    }

    aslında sanırım bütün bu anlattıklarınız buna da bir cevap oluyor gibi. Çünkü bu struct'ların bir "ctor" yerine "initobj" olayı varsa bu "initobj" class'larda olduğu gibi içindeki collectionlarıda construct etmiyor demek ki.
  • Hocam bundan sonra Star Wars'ada geçermiyiz.
    Amlatım mükemmel olmuş elinize emeğinize dilinize sağlık.
  • Hocam sizi yakında sci-fi senaristi olarak görürmüyüz.
    Şahane olmuş makale. Aklınıza ve parmaklarınıza sağlık
  • Valla aslında bunu düşünmedim değil. Hani ille de teknik bir içerik yazmak şart olmamalı. Belki bir gün bir deneme yazısı yayınlayabilirim. Zaten Gravatar' ınızdaki profesor Emett ile büyümüş birisiyim Smile Yani bir eğilim var bilim kurguya karşı Smile
  • Yazınız çok açıklayıcı ve ilgi çekici, elinize sağlık ..
  • Bir X-Files hayranı olarak heyecanla okudum. Çok keyif aldım. Elinize sağlık.
    Daha az teknik, daha bir kurgu olan yazılarınızı dört gözle bekliyorum.

Yorum ekle

Loading