Image
Mayıs 03 2016 02:30

C# ICloneable - Deep Copy

Merhaba uzun zamandır yazılım ile ilgili bir şey yazmadığımın farkındayım. İşlerimizin yoğunlu nedeni ile günün yarısından çoğunda doğrudan yada dolaylı yazılım ile uğraştığım için açıkçası günün kalan bölümünde gerekmedikçe kodlara bulaşmamaya çalışıyorum. Onlar bulaşmadığı sürece :) 

Hazır İstanbula güzel güzel kar yağmaya başlamışken, birşeyler yazayım istedim. Ne alakası var diyeceksiniz, bilmiyorum. Belki çocukluktan kalan, o karda oynama, kayma,  soğuktan donan elleri ve ayakları odun sobasında ısıtma. Sonra yine dışarı çıkma halleri bilinçaltına işlemiş olabilir. 
Kar yağdığında bir tebessüm bir sevinç kaplıyor içimi, arada  bir ne kadar yağmış diye pencereden bakmalar falan... Güzel oluyor yani.
Yağmur yağdığında ise sanki arka fonda Hüzzam makamında bir taksim yapılıyor... Konumuza dönecek olursak;

Bugün hemen herkesin bildiği bir şeyden söz edeceğim. .Net kütüphanesinde ki ICloneable.  interface-sinden söz etmek ve bu interfacesi implemente eden bir örnek yapmak istiyorum. 

Not: Biliyorsunuz bir obje yi kopyalamanın tek yolu ICloneable interfacesini implemente etmek değildir. Aklıma gelen bir kaç yok daha var

  1. Klonlamak istediğiniz object Manuel kopyalarsınız. Evet en sıkıcı iştir ama en garantili yoldur
  2. Reflection yaparak klonlayabilirsiniz.
  3. Objenizi Serialize edip Deserialize ederek klonlayabilirsiniz
  4. IL ile yani Intermediate Language ile kopyalabilirsiniz
  5. kendi yazdığınız bir Extension Metod ile klonlayabilirsiniz
  6. Ya da bizim bu makalede yapacağımız gibi MemberwiseClone ile kopyalayabilirsiniz.

 

Makelenin girişinde referans tipleri, değer tipleri , heap, stack  bahsettiğimiz için onları es geçip direkt örnekle devam etmek istiyorum. 

Order, OrderItem ve Customer isminde 3 clasımız var. Bunlardan Order clasına ICloneable interfacesi uygulanmış. Yani istediğimiz anda .Clone() deyip bir kopyasını alabiliriz.
Farkettiğiniz gibi Order classının içinde başka yapılar da var. Biz Clone metodunu doldurduk ve gayet güzel bir kopyamız oldu fakat bu nesneler işin içine girince ne olacak? 
 

  public class Order : ICloneable
    {
        public int Id { get; set; }
        public int CustomerId { get; set; }
        public string Description { get; set; }
        public Customer Customer { get; set; }
        public List<OrderItem> OrderItems { get; set; }
        public object Clone()
        {
            return this.MemberwiseClone();
        }
        public override string ToString()
        {
            return string.Format("Order Id: {0}, Customer:{1}, Desc: {2}, Total:{3}, Items Count:{4}", 
                this.Id, this.Customer.Name,this.Description, this.OrderItems.Sum(o => o.Total), this.OrderItems.Count());
        }
    }
    public class OrderItem
    {
        public int Id { get; set; }
        public int OrderId { get; set; }
        public Decimal Total { get; set; }
        public Order Order { get; set; }
    }
    public class Customer
    {
        public int Id { get; set; }
        public string Name { get; set; }
    }

Görüldüğü gibi ICloneable yi implemente edip Clone metodunada Orderin(Referans olmayan) bir kopyasını gönderdik.
Peki Orderin içinde birde OrderItem listesi vardı ya onlar? onlarında kopyaları alınıyor mu?

 static void Main(string[] args)
        {
            var order = new Order
            {
                Id = 1,
                Customer = new Customer
                {
                    Id = 1,
                    Name = "Star Wars bakkaliyesi"
                },
                CustomerId=1,
                Description = "Ben Asılım",
                OrderItems = new List<OrderItem>
                {
                    new OrderItem{Id = 1, Total = 150M},
                    new OrderItem{Id = 2, Total = 40M},
                    new OrderItem{Id = 3, Total = 10M}
                }
            };

            Console.WriteLine(order.ToString());

            var order2 = (Order)order.Clone();//Yeni bir kopya oluştu
            order2.Id = 2; // id 2 set edildi
            order2.Description = "Ben Kopyasıyım"; // açıklama değiştirildi 
            order2.OrderItems.FirstOrDefault().Total = 125000M; // OrderItems listesinin ilk Item inin Totali değiştirildi.
            Console.WriteLine(order2.ToString());// Order2 değerleri ekrana baslıyor
            Console.WriteLine(order.ToString());// Asıl Order durumu ekrana baslıyor
        }

Bakalım çıktı nasıl olmuş.

sql

İlk satırda Order nesnesi ekrana doğru bir şekilde basılmış. 
İkinci satırda Clone edilen yani kopyalanan nesne değerleri değişmiş bir şekilde ekrana basılmış. 
Üçüncü satırda Asıl Orderin tekrar ekrana basıldığında görüyoruz ki Clone Order üzerinde OrderItems listesinin ilk elemanı için yaptığımız değiklik aslında Asıl Order nesnesinin Items-ini değiştirmiş.
Bu durumda şunu anlıyoruz ki ICloneable sadece nesnenin kendisini kopyalıyor ancak içindeki diğer referansları olduğu gibi copy nesnesine taşıyor. Ehh zaten başka türlüsü de olmazdı.

Nasıl oluyorda oluyor?
Biz Order nesnesini New - create ettiğimizde belleğin heap bölgesine atıldı. Benzer bir şekilde OrderItems te heap e atıldı.
Order için Clone() dedik ama o sadece kendisinin referans ettiği değerleri kopyaladı. List<OrderItems> yada Customer nesneleri yine referans olarak Kopyalanan Order-e atıldı.
Bu yüzden de clonlanan Order nesnesi içinde OrderItems ve Customer-in heap teki adresleri geldi.

Eğer biz order2.CustomerId yi ve order2.Customer.Name değiştirseydik yani

order2.CustomerId = 999; 
order2.Customer.Name = "Star Trek Market";

yapsaydık o zaman asıl Orderin Customer.Name ="Star Trek Market" olacaktı ama CustomerId si yine 999 olacaktı. 
Çünkü CustomerId kopyanana order2 in içindedir. Asıl Order ile ilgisi kesilmiştir.

Peki ne yapmamız gerek?  
Order içindeki OrderItems-ları da ICloneable yapmamız gerekiyor.
Bunun için öncelikle OrderItem classına da ICloneable interfacesini ekliyoruz.
Order clasının Clone metodunuda OrderItems leri alacak şekilde genişletiyoruz.

 public object Clone()
        {
            var order = (Order)this.MemberwiseClone();
            order.OrderItems= new List<OrderItem>();
            foreach (var item in this.OrderItems)
            {
                order.OrderItems.Add((OrderItem)item.Clone());  
            }
            return order;
        }

Uygulamamızı yeniden çalıştırdığımızda bakalım nasıl bir çıktı alacağız.

sql

Gördüğünüz gibi Asıl ve Kopya olan Orderin Items de düzgün bir şekilde çalıştı. 

Herkese iyi yıllar diliyorum :)=

2015 : memet tayanç