C# IClonable ile Referans Tiplerini Klonlamak

Bilindiği üzere referans tipleri belleğin heap bölgesinde tutulmaktadır. Bu durumun sonucu olarak iki referans tipi arasında bir atama işlemi söz konusu olduğunda, aslında bu referans tiplerinin heap bellek bölgesinde yer alan adresleri eşitlenmektedir. Böyle olunca da referans tiplerinin her hangi birisinde yapılan değişiklik diğerini de otomatikman etkileyecektir. Bu referans tiplerinin değer tiplerinden farklı olduğu bi yönüdür. Çünkü değer tiplerinde bir eşitleme söz konusu olduğunda nesnenin yenisi yaratılmaktadır.

Referans tiplerinde de biz değer tiplerinde olduğu gibi eşitleme yaptığımızda bu referansların birbirlerini etkilememelerini isteyebiliriz. Bu durumda IClonable arayüzünü uygulayarak referans tipinin klonlanmasını sağlayabiliriz. Bunun nasıl yapıldığına geçmeden önce iki referans tipinin birbirine eşitlenmesi sonucu nasıl adreslerinin eşitlendiğine bir örnek verelim.


public class Araba
    {
        public string Marka { get; set; }
        public int Model { get; set; }

        public Araba()
        {
            this.Marka = "Opel";
            this.Model = 2010;
        }
        public Araba(string marka, int model)
        {
            this.Marka = marka;
            this.Model = model;
        }
        public override string ToString()
        {
            string arabaBilgisi = "Marka: " + Marka.ToString() 
                    + " Model: " + Model.ToString();
            return arabaBilgisi;
        }
    }

   
  static void Main(string[] args)
        {
            Araba araba1 = new Araba();
            // araba1 nesnesinin özelliklerini yazdırıyoruz.
            Console.WriteLine(araba1.ToString()); 

            Araba araba2;
            araba2 = araba1;
            // araba2 nesnesinin özelliklerini yazdırıyoruz.
            Console.WriteLine(araba2.ToString()); 

            araba2.Marka = "Fiat";
            araba2.Model = 2009;

            Console.WriteLine(araba2.ToString());
            Console.WriteLine(araba1.ToString());
            Console.ReadLine();
        }

Program Çıktısı :


Bu örneğimizde olduğu gibi araba1 nesnesi önce yaratılmış daha sonra araba2 = araba1 denerek araba2 nesnesine atanmıştır. Bu durumda araba2 nesnesine yapılan değişiklik doğrudan araba1 nesnesini etkilemiştir. Bu durumun nedeni atama yapılırken yukarıda anlattığımız şekilde referans tiplerinin heap bellek bölgesinde yer alan adresleri eşitlenmesidir.

Gelin şimdi de IClonable arayüzünü kullanarak bu durumu nasıl çözeceğimize bakalım:


public class Araba:ICloneable
    {
        public string Marka { get; set; }
        public int Model { get; set; }

        public Araba()
        {
            this.Marka = "Opel";
            this.Model = 2010;
        }
        public Araba(string marka, int model)
        {
            this.Marka = marka;
            this.Model = model;
        }

        public override string ToString()
        {
            string arabaBilgisi = "Marka: " 
               + Marka.ToString() + " Model: " + Model.ToString();
            return arabaBilgisi;
        }

        #region ICloneable

        public object Clone()
        {
            return new Araba(this.Marka,this.Model);
        }

        //NOT: Bu şekilde de Klonlama yapılabilmektedir. 
        //Fakat bu yöntemde eğer clasın içinde başka bir 
        //reference type varsa o kopyalanmaz.
        //public object Clone()
        //{
        //    return this.MemberwiseClone();
        //}
        #endregion
    }

static void Main(string[] args)
        {
            Araba araba1 = new Araba();
            // araba1 nesnesinin özelliklerini yazdırıyoruz.
            Console.WriteLine(araba1.ToString()); 

            Araba araba2;
            araba2 = araba1;
            // araba2 nesnesinin özelliklerini yazdırıyoruz.
            Console.WriteLine(araba2.ToString()); 

            araba2.Marka = "Fiat";
            araba2.Model = 2009;

            Console.WriteLine("araba2: " + araba2.ToString());
            Console.WriteLine("araba1: " + araba1.ToString());
            Console.ReadLine();
        }

Programın Çıktısı :


İkinci örneğimizde IClonable arayüzü implement edilerek bu arayüzün Clone metodu kullanılmıştır. Ve bu sayede atama yapılırken Clone metodu kullanılarak yeni bir araba nesnesi ve adresi oluşturulması sağlanmıştır. Programın çıktısından açıkça görüldüğü gibi bu sefer araba2 nesnesine yapılan değişiklikler araba1 nesnesini etkilememiştir.

Burada kod içerisinde commentli olarak yazılmış önemli bir nokta var.


        //NOT: Bu şekilde de Klonlama yapılabilmektedir. 
        //Fakat bu yöntemde eğer clasın içinde başka bir 
        //reference type varsa o kopyalanmaz.
        //public object Clone()
        //{
        //    return this.MemberwiseClone();
        //}

Klonlama bu şekilde de yapılabilir fakat Araba nesnesi içerisinde bir referans tipi tanımlı ise bu metod bu referans tipini klonlamaz yani ataması yapılan nesnenin içerisindeki referans tipinin adresi aynen kopyalanır. Bu yüzden bu metodu eğer sınıfınızda referans tipi tanımlamadıysanız kullanmanız gerekmektedir.

C# Static Kullanımı



Static'in kullanıldığı yerler:

• Bir sınıf(class) içerisinde bulunan metodlar(methods).
• Bir sınıf(class) içerisinde bulunan alanlar (fields)
• Bir sınıfa ait static bir yapıcı metod (constructors)
• Değişmezler (constants) implicit olarak static tanımlama.
• Readonly referanslar.

  • Bir sınıf(class) içerisinde bulunan metodlar(methods).

    Static olarak tanımlanan bir metodun kullanılabilmesi için tanımlanmış olduğu tipin nesne örneğini oluşturmaya gerek yoktur. Bu durum çoğunlukla bir tipin asıl iş yapan fonksiyonelliklerin kullanılabilmesi için, tüm nesneyi örneklemenin gereksiz olduğu durumlarda ele alınır. Instance oluşturup bir statik metodu çağaramayız.

     
        public class Dikdortgen
        {
             //Dikdortgen sınıfı ile ilgili diğer kodlar.
            public static int AlanHesapla(int aKenari, int bKenari)
            {
                return aKenari * bKenari;
            }
        }


        class Program
        {
            static void Main(string[] args)
            {
                int alan = Dikdortgen.AlanHesapla(3,5);
            }
        }

    Görüldüğü gibi Dikdörtgen sınıfı içerisinde static olarak tanımalanan AlanHesapla metdodu Dikdörtgen sınıfının instance'ı oluşturulmadan sadece sınıf ismi ardından nokta denerek kullanılmıştır. Bu buze yuraıda yazdığımız gibi nesneyi boşuna yaratmadan Dikdörgen sınıfı içerisindeki metodu çağırmamızı sağlamıştır. Örneğin Console uygulamalarını geliştirirken çok sık kullandığımız Console sınıfına ait WriteLine, ReadLine vb metodlar static olarak tanımlanmışlardır. Hatta bir console uygulaması geliştirdiğimizde, programın giriş noktası olan Main metodunun static olarak tanımlandığını farketmişizdir.

    Static metodların kullanılması sırasında dikkat edilmesi gereken bazı durumlar vardır. Öncelikli olarak, static olarak tanımlanmış sınıf metodlarına static olmayan sınıf üyeleride erişebilir. Örneğin:


        public class Dikdortgen
        {
             //Dikdortgen sınıfı ile ilgili diğer kodlar.
            public static int AlanHesapla(int aKenari, int bKenari)
            {
                return aKenari * bKenari;
            }

            public bool AlanKontorlu(int aKenari, int bKenari,int kontrolAlani)
            {
                if (kontrolAlani > AlanHesapla(aKenari, bKenari))
                    return true;
                else
                    return false;
            }
        }

    Bu kod parçasında görüldüğü gibi static olarak tanımlanan AlanHesapla metodu static olmayan AlanKontrolu metodu içerisinde çağırılabilmiştir. Fakat bu durumun tam tersi mümkün değildir yani static üyeler sadece static üyelere erişebilir.

    • Bir sınıf(class) içerisinde bulunan alanlar (fields) 

    Static metodlar gibi, bir sınıf içerisinde kullanılabilecek static alanlarda tanımlayabiliriz. Bir örnekle bunu inceleyecek olursak :


        public class Dikdortgen
        {
             //Dikdortgen sınıfı ile ilgili diğer kodlar.
            public static int kontrolAlani = 20;
            public static int AlanHesapla(int aKenari, int bKenari)
            {
                return aKenari * bKenari;
            }

            public bool AlanKontorlu(int aKenari, int bKenari,int kontrolAlani)
            {
                if (kontrolAlani > AlanHesapla(aKenari, bKenari))
                    return true;
                else
                    return false;
            }
        }

        class Program
        {
            static void Main(string[] args)
            {
                Dikdortgen dikdortgen1 = new Dikdortgen();
                Console.WriteLine(Dikdortgen.kontrolAlani);

                Dikdortgen dikdortgen2 = new Dikdortgen();
                Console.WriteLine(Dikdortgen.kontrolAlani);

                Dikdortgen dikdortgen3 = new Dikdortgen();
                Console.WriteLine(Dikdortgen.kontrolAlani);

                Console.ReadLine();
            }
        }

    Bu kod parçacığında görüldüğü gibi int tipinde static kontrolAlani adında bir değişken tanımlanmıştır ve Dikdörtgen nesnesinin 3 defa instance'ı oluşturulup bu değişkenin değerlerine bakılmıştır. Bu program parçacığının çalışması sonucunda şu şekilde olacaktır:


    c# static

    Burada görüldüğü gibi Dikdörtgen sınıfına ait 3 nesne örneğinin oluşturulmasına ve kullanılmasına rağmen kontrolAlani değerinin hiç bir şekilde değişmediğidir. İşte bu etkinin nedeni kontrolAlani değerinin static olarak tanımlanmış oluşudur. Dolayısıyla Dikdörtgen nesnelerinden kaç tane oluşturulursa oluşturulsun hepsi aynı kontrolAlani değerine işaret etmektedir. Fakat bir static alanın değeri çalışma zamanında değiştirilebilir. Bu duruma şöyle bir örnek verilebilir :


    public class Dikdortgen
        {
             //Dikdortgen sınıfı ile ilgili diğer kodlar.
            public static int kontrolAlani = 20;
            public void KontrolAlaniDegistir(int yeniKontrolAlani)
            {
                kontrolAlani = yeniKontrolAlani;
            }
            public static int AlanHesapla(int aKenari, int bKenari)
            {
                return aKenari * bKenari;
            }

            public bool AlanKontorlu(int aKenari, int bKenari,int kontrolAlani)
            {
                if (kontrolAlani > AlanHesapla(aKenari, bKenari))
                    return true;
                else
                    return false;
            }
        }

        class Program
        {
            static void Main(string[] args)
            {
                Dikdortgen dikdortgen1 = new Dikdortgen();
                Console.WriteLine(Dikdortgen.kontrolAlani);

                dikdortgen1.KontrolAlaniDegistir(30);

                Dikdortgen dikdortgen2 = new Dikdortgen();
                Console.WriteLine(Dikdortgen.kontrolAlani);

                Dikdortgen dikdortgen3 = new Dikdortgen();
                Console.WriteLine(Dikdortgen.kontrolAlani);

                Console.ReadLine();
            }
        }
    Görüldüğü gibi Dikdortgen sınıfının içerisine kontrolAlaniDegistir adında bir metod tanımlanarak static bir değişken olan kontrolAlani'ni çalışma anında değiştirme amaçlanmıştır. Bu programın çıktısı şu şekilde olacaktır:



    Fakat burada contructer işin içene girince durum farklı bir boyut alacaktır. Bu örneğin bir farklı versiyonunun contructer'lı yazılmış hanina bakarsak :

    public class Dikdortgen
        {
             //Dikdortgen sınıfı ile ilgili diğer kodlar.
            public static int kontrolAlani = 20;

            public Dikdortgen()
            {
                kontrolAlani = 25;
            }
            public void KontrolAlaniDegistir()
            {
                kontrolAlani = 40;
            }
            public static int AlanHesapla(int aKenari, int bKenari)
            {
                return aKenari * bKenari;
            }

            public bool AlanKontorlu(int aKenari, int bKenari,int kontrolAlani)
            {
                if (kontrolAlani > AlanHesapla(aKenari, bKenari))
                    return true;
                else
                    return false;
            }
        }

        class Program
        {
            static void Main(string[] args)
            {
                Dikdortgen dikdortgen1 = new Dikdortgen();
                Console.WriteLine(Dikdortgen.kontrolAlani);

                dikdortgen1.KontrolAlaniDegistir();

                Dikdortgen dikdortgen2 = new Dikdortgen();
                Console.WriteLine(Dikdortgen.kontrolAlani);

                Dikdortgen dikdortgen3 = new Dikdortgen();
                Console.WriteLine(Dikdortgen.kontrolAlani);

                Console.ReadLine();
            }
        }

    Bu örneğin çıktısı şu şekilde olacaktır:


    Görüldüğü gibi bu durumda KontrolAlaniDegistir  metodu işe yaramamıştır. Çünkü her yeni nesne örneği oluşturulduğunda kontrolAlani değişkenine 25 değeri atanmıştır. Bu durumdan kurtulmanın yolu bir sonraki başlıkta açıklanacaktır.


    • Bir sınıfa ait static bir yapıcı metod (constructors)
    Static yapıcı metodu çoğunlukla bir sınıfın static değişkenlerine ilk nesne örneği oluşturulduğunda bir kereliğine değer atmak için kullanabiliriz. Bu bilgiler ışığında Dikdortegen sınıfımızı aşağıdaki gibi değiştirelim ve static yapıcı metodumuz içerisinde static kontrolAlani değişkeninin değerini belirleyelim.


    public class Dikdortgen
        {
             //Dikdortgen sınıfı ile ilgili diğer kodlar.
            public static int kontrolAlani = 20;
            //public Dikdortgen()
            //{
            //    kontrolAlani = 25;
            //}
            //Bu sefer statik constructer kullandık.
            static Dikdortgen()
            {
                kontrolAlani = 35;
            }
            static void KontrolAlaniDegistir()
            {
                kontrolAlani = 40;
            }
            public static int AlanHesapla(int aKenari, int bKenari)
            {
                return aKenari * bKenari;
            }

            public bool AlanKontorlu(int aKenari, int bKenari,int kontrolAlani)
            {
                if (kontrolAlani > AlanHesapla(aKenari, bKenari))
                    return true;
                else
                    return false;
            }
        }
     


    Bu durumda program çıktısı :


    bu şekilde farklı bir sonuç elde etmemizin nedeni static yapıcılar(constructor)sadece ilk nesne örneği oluşturulduğunda çalışır. Aynı tipe ait sonraki nesne örneklemelerinde çalışmazlar.

    • Değişmezler (constants) implicit olarak static tanımlama. 
     Constant’ lar uygulamanın çalışması boyunca değişmeyecek değerleri saklamak için kullandığımız bir değişken çeşididir. Basit olarak değer türünden (value types) sabit bir değişkeni aşağıdaki gibi tanımlayabiliriz.


    public const double PI = 3.14;;

    Çağırırkende şu şekile çağırılabilir :


    double pi = Derived.PI;

    yani yazılmamış olsa bile cons değişkenler static değişkenlerdir bu yüzden bir const değişken tanımlarken static kelimesini eklersek tanımlamaya hata alırız.

    • Readonly referanslar.

    Constant’ lar derleme zamanında (compile time) tanımlanan değişkenler için geçerlidir. Bu nedenlede sadece değer türlerine (value types) uygulanabilirler. Oysaki bazı durumlarda sabit olarak tanımladığımız değişkenlerin değerleri çalışma zamanında belirlenebilir. Bu nedenle referans tiplerini sabit olarak kullanılabilmek için readonly tanımlarız. Bununla ilgilide örneğimizi şu şekilde genişletebiliriz :


         public class Dikdortgen
        {
             //Dikdortgen sınıfı ile ilgili diğer kodlar.
            public static int kontrolAlani = 20;
            //public Dikdortgen()
            //{
            //    kontrolAlani = 25;
            //}
            //Bu sefer statik constructer kullandık.
            static Dikdortgen()
            {
                kontrolAlani = 35;
            }
            public void KontrolAlaniDegistir()
            {
                kontrolAlani = 30;
            }
            public static int AlanHesapla(int aKenari, int bKenari)
            {
                return aKenari * bKenari;
            }

            public bool AlanKontorlu(int aKenari, int bKenari,int kontrolAlani)
            {
                if (kontrolAlani > AlanHesapla(aKenari, bKenari))
                    return true;
                else
                    return false;
            }
        }
        public class Sekiller
        {
            public static readonly Dikdortgen dikdortgen;

            static Sekiller()
            {
                dikdortgen = new Dikdortgen();
            }
        }
        class Program
        {
            static void Main(string[] args)
            {
                Dikdortgen dikdortgen1 = new Dikdortgen();
                Console.WriteLine(Dikdortgen.kontrolAlani);

                dikdortgen1.KontrolAlaniDegistir();

                Dikdortgen dikdortgen2 = new Dikdortgen();
                Console.WriteLine(Dikdortgen.kontrolAlani);

                Dikdortgen dikdortgen3 = new Dikdortgen();
                Console.WriteLine(Dikdortgen.kontrolAlani);

                Dikdortgen dikdortgen4 = Sekiller.dikdortgen;

                Console.ReadLine();
            }
        }

    C# Volatile Anahtar Sözcüğü



    Derleyiciler kodunuzun daha hızlı çalışabilmesi için çoğu zaman kodunuza mudahalede bulunurlar. Bu müdehaleler kod optimizasyonu olarak bilinir. Ne gibi optimizasyonlar yapılır derseniz. Mesela bir değişken tanımladınız ve hiç çağırmadınız. Bu durumda derleyici bu değişkeni derlenecek kodun içerisine almayarak derlenecek kod boyutunu küçültür. Ayni şekilde yanlışlıkla hiç bir şekilde içine girilmiyecek bir if yazdınız bu sefer derleyici bu if içerisinde yaptığınız işlemleri derlenecek koda eklemeyecektir. İşte tüm bunlar bize performans olarak geri döner. İkinci bir optimizasyon ise microişlemci düzeyinde optimizasyondur. Bu derleyicinin değişkenlerin elde edilmesinde yada tekrar yazılmasında belleğin yada işlemcinin tercih edilmesi ile ilgilidir. Değerin mikroişlemci tarafından belleğe(ram) yazılması ile mikroişlemcideki register bölgesine yazılması arasında büyük fark vardır. Bu fark elbette hız faktörüdür. İşte tam bu noktada ikinci tip optimizasyon kuralını tanımlayabiliriz. Derleyici öyle bloklara rastlayabilir ki, bu bloklar içinde bulunan bir değişkenin değerini her defasında bellekten okuyacağına bu değişkenin değerini bir defaya mahsus olmak üzere mikroişlemcinin ilgili register bölgesine bölgesine kaydeder ve sonraki okumalarda işlemci bellek yerine bu register bölgesini kullanır. Böylece kodunuzun çalışma süresinde önemli sayılabilecek bir azalma görülür.


    Bildiğiniz üzere uygulamalar genellikle çoklu iş parçacıklarından(multi thread) ve proseslerden oluşur. Her bir proses diğer bir proses teki değişkene işletim sisteminin izin verdiği ölçüde erişip üzerinde işlemler yapabilir. Aynı şekilde bir iş parçacığıda diğer bir iş parçacığında bulunan değişkene erişip üzerinde çeşitli işlemler yapabilir. Peki bunun bizim optimizasyon kurllarımızla bağlantısı ne? Şöyle ki : derleyici bir değişkenin değerinin farklı bir iş parçacağı tarafından yada farklı bir proses tarafından işleneceği üzerinde durmaz. Bu tamamen işletim sisteminin yönetimindedir. Hal böyleyken bizim yukarıda bahsettiğimiz ikinci optimizasyon tipi bazı durumlarda yarar getireceğiniz zarar getirebilir. Zira optimizasyon adına bir değişkenin değerini her defasında bellekten okuma yerine mikroişlemcideki ilgili register dan okurken o anda farklı bir iş parçacağı yada farklı bir proses hatta ve hatta işletim sistemi sizin erişmeye çalıştığınız değişkenin değerini sizin uygulamanızın mantığına göre değiştirebilir. Bu durumda siz o değişkenin son halini kullanmamış olursunuz. Dolayısıyla programınızda farklı thread lar yada prosesler arasında paylaşılan veya işletim sistemi tarafından değiştirilmesi muhtemel olan değişkenlerinizi optimizasyon kuralına tabi tutmamanız gerekir. Bu durumda  volatile anahtar sözcüğü burada imdadımıza yetişiyor. Bir değişkeni volatile anahtar sözcüğü ile bildirdiğiniz takdirde derleyicinizin optimizasyon ile ilgili parametresini açık tutsanız bile ilgili değişken yukarıda bahsi geçen tehlikeli optimizasyon kurallarına tabi tutulmayacaktır. Yani volatile ile bildirilen değişkenlere programın akışı sırasında her ihtiyaç duyulduğunda değişkenin gerçek yeri olan belleğe başvurulur. 

    Volatile aşağıdaki değişken tipleri ile birlikte kullanılabilir.


    • Herhangi bir referans tipindeki değişken ile

    • byte, sbyte, short, ushort, int, uint, char, float yada bool. türünden olan değişkenler ile

    • byte, sbyte, short, ushort, int, yada uint türünden semboller içeren numaralandırmalar(enums) ile

    • unsafe modda iken herhangi bir gösterici türü ile
    Bu konuyla ilgili MSDN'de bulunan aşağıdaki örneği inceleyerek konuyu daha iyi anlayabiliriz.

    using System;
    using System.Threading;

    class Test
    {
        public static int result;
        public static volatile bool finished;

        static void Thread2()
        {
            result = 143;
            finished = true;
        }

        static void Main()
        {
            finished = false;
            new Thread(new ThreadStart(Thread2)).Start();

            for (; ; )
            {
                if (finished)
                {
                    Console.WriteLine("result = {0}", result);
                    return;
                }
            }
        }
    }
    Yukarıda görüldüğü gibi boolean bir değer olan finish değişkeni volatile olarak tanımlanmıştır. Bu programdaki püf nokta finished isimli değişkenin ana thread ve ana thread içinde başlatılan yeni thread tarafından ortak kullanılan bir değişken olmasıdır. Eğer finished değişkeni volatile olarak bildirilmemiş olsaydı, akış thread2 metoduna gelmiş olmasına rağmen Main metodu içindeki if bloğu çalıştırılmayabilirdi. Çünkü derleyici ana thread içinden finished değişkeninine tampolanmış bir bölgeden(register) erişebilir. Bu durumda finished değişkeninin gerçek değeri true olmasına rağmen ana thread de finished değişkeni halen false olarak ele alınır. Bu yüzden finished değişkeninin her durumda son versiyonunu elde etmek için bu değişken volatile anahtar sözcüğü ile bildirilmiştir.

    Kaynak : CSharpNedir.com