Makale Özeti

Bu makalemde sizlerle ileri düzey serileştirme işlemleri konusunda bilgi paylaşarak; bir örnek üzerinden size ait olmayan, ayrı assembly içerisinde yer alan ve SerializableAttribute ile işaretlenmemiş olan bir sınıfı nasıl serileştirebileceğizi anlatacağım.

Makale

   Nesnelerin serileştirilerek saklanması konusunda C# işlerimizi oldukça kolaylaştırmakta. Çoğu zaman sınıfımızın üzerine yerleştireceğimiz bir SerializableAttribute ile nesnelerimizi serileştirilebilir hale getirebiliyor, ardından da BinaryFormatter sınıfı yardımıyla bu nesneleri bir stream’e yazabiliyor ve yine stream’den okuyabiliyoruz.

   Serializable ve NonSeriazable attribute’leri ve BinaryFormatter sınıfını kullanmak çoğu senaryo da bizleri hızlıca sonuca ulaştırsa da malesef ki sınıf hiyerarşisi, yapısı ya da açık anahtar simgesi değişmiş assembly’lerde geriye dönük uyumluluk, serileştirilmiş eski verilerin yeniden açılması tam bir baş ağrısı olabiliyor. Özellikle uygulamamızın yeni sürümlerinde yaşanabilecek olan sınıf ve namespace değişikliklerinde önceki makalemde sizlerle paylaştığım yöntemle SerializationBinder sınıfı üzerinden yapılacak yönlendirme ile problemi aşabilmektesiniz.

   Kimi senaryolarda sizin kontrolünüzde olmayan, 3. parti,  sınıfların da serileştirilmesi gerekebiliyor. Müdahale edemediğiniz bu 3. parti sınıfların SerializableAttribute ile işaretlenmediği de varsayarsak serileştirme anlamında yapılabilecek çok fazla şey kalmıyor. Elimizin kolumuzun bağlandığı bu durumda ne BinaryFormatter, ne de SerializationBinder derdimize çare olamayacaktır; çünkü yapılması gereken doğrudan serileştirme sürecine müdahale etmektir. Bu tarz bir durumla karşılaştığınızda hemen farkedeceksiniz ki standart serileştirme işleminde yaşadığınız konfor ve kolaylık artık sizinle değil. .Net framework serileştirme iç sürecine müdahale etmeyi az sayıda arayüz ve sınıf ile sınırlandırmış durumdadır.

public class Ogrenci {
    public string Adi;
    public string Bolum;
}

   Yukarıdaki örnek sınıfı ele alalım, size ait olmayan 3. parti bir kütüphane içerisinde yer aldığını varsaydığım -dolayısıyla da müdahale edemediğim- bu sınıf üzerinde serileştirme ile ilgili bir bilgi bulunmadığı için standart yöntemler ve BinaryFormatter kullanarak sınıf örneğini serileştirmeye çalışırsanız aşağıdaki hata mesajı ile karşılaşırsınız;

SerializationException was unhandled

'SerilestirmeDeneme, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null' Derlemesindeki 'SerilestirmeDeneme.Ogrenci' Türü seri hale getirilebilir olarak işaretlenmedi.

   Bu durumda Ogrenci sınıfının serileştirme işleminde kullanılmak üzere sizin kontrol edebileceğiniz bir vekil sınıf oluşturmalı ve BinaryFormatter’a Ogrenci türündeki tüm sınıf örneklerinde bu vekil’i kullanması yönünde zorlamalısınız. Şimdi isterseniz adım adım bunu nasıl yapabileceğimizi görelim.

   Herşeyden önce, .net framework yerine Ogrenci sınıfının serileştirmesini yapacağımız olan serileştirme vekil sınıfımız System.Runtime.Serialization.ISerializationSurrogate arayüzünü uygulamalıdır. Bu arayüzü inceleyecek olursak nesnenin serileştirilmesinde çağrılan GetObjectData ve serileştirilmiş nesnenin okunduğu SetObjectData fonksiyonlarının bulunduğunu görürüz;

public interface ISerializationSurrogate {
    void GetObjectData(Object nesneOrnegi,
               SerializationInfo serilestirmeBilgisi,
               StreamingContext kapsam);

    Object SetObjectData(Object nesneOrnegi,
               SerializationInfo serilestirmeBilgisi,
               StreamingContext kapsam,
               ISurrogateSelector vekilSecici);
}

   Aşağıda Ogrenci sınıfının serileştirmesinde vekil olacak olan OgrenciSerializationSurrogate sınıfını bulabilirsiniz;

public sealed class OgrenciSerializationSurrogate : ISerializationSurrogate {
    public void GetObjectData(Object nesne,
        SerializationInfo serilestirmeBilgisi,
        StreamingContext kapsam) {

        Ogrenci ogrenciOrnegi = (Ogrenci)nesne;

        serilestirmeBilgisi.AddValue("Adi", ogrenciOrnegi.Adi);
        serilestirmeBilgisi.AddValue("Bolum", ogrenciOrnegi.Bolum);
    }

    public Object SetObjectData(Object nesne,
        SerializationInfo serilestirmeBilgisi,
        StreamingContext kapsam,
        ISurrogateSelector vekilSecici) {

        Ogrenci ogrenciOrnegi = (Ogrenci)nesne;

        ogrenciOrnegi.Adi = serilestirmeBilgisi.GetString("Adi");
        ogrenciOrnegi.Bolum = serilestirmeBilgisi.GetString("Bolum");

        return null;
    }
}

   Tahminimce daha önceden ISerializable arayüzünü kullanarak serileştirme yapmış olanlarınızın ilk farkedeceği şey GetObjectData fonksiyonu olacaktır. Her iki arayüzde de bulunan bu fonksiyon ISerializable’den farklı olarak ISerializationSurrogate içerisinde gerçek nesnenin gönderildiği ek parametresidir. Serileştirme vekil sınıfınızda, size iletilen bu gerçek nesneyi kullanarak, gerekli olan serileştirme bilgisini .net framework’e verebilirsiniz. Benzer bir kullanımla da SetObjectData fonksiyonu yardımıyla .net framework tarafından gönderilen bilgiler yardımıyla serileştirilmiş veri nesne içerisine geri eklenebilir.

Bu noktada düşmem gereken iki önemli not var;

  • Gerçek örneklerde serileştirmeniz gereken nesne her zaman için bu kadar basit olmayabilir. Sınıf salt okunur özelliklere sahip ve asıl verisi arka alanda bulunan / hesaplanan private değişkenler barındırabilir. Bu durumda serileştirme sırasında farklı işlemler yapılması gerekecektir.
  • Her ne kadar SetObjectData fonksiyonu nesne dönecek şekilde tanımlanmışta olsa Microsoft tarafından implemente edilmiş olan SoapFormatter ve BinaryFormatter sınıfları fonksiyondan dönen değeri dikkate almayıp fonksiyonun ilk parametresi olarak geçilen nesneyle işlem yapmaya devam etmektedir. Bu durum yukarıdaki Ogrenci sınıfı gibi referans türler (reference type) için bir sıkıntı oluşturmaz iken struct gibi değer tiplerin (value type) bu yöntem ile serileştirilemez.

   Serileştirme de kullanacağımız vekil sınıfımızı tanımladıktan sonra bunun formatter nesnesine bildirilmesi gerekecektir. Bu işlem için öncelikle System.Runtime.Serialization namespace’i altında bulunan SurrogateSelector sınıfının bir örneği oluşturmalı, ardından da bu sınıf içerisinde tanımlı olan AddSurrogate fonksiyonu yardımıyla Ogrenci türü için OgrenciSerializationSurrogate vekil sınıfının kullanılması gerektiğini belirtmeliyiz. Bu işleme dair örnek kod aşağıda bulunabilir;

SurrogateSelector vekilSecici = new SurrogateSelector();

OgrenciSerializationSurrogate serilestirmeVekili = new OgrenciSerializationSurrogate();
vekilSecici.AddSurrogate(typeof(Ogrenci), new StreamingContext(StreamingContextStates.All), serilestirmeVekili);

   İş mantığımız gereği birden fazla sınıf için serileştirme vekil sınıf tanımlanmasının gerektiği durumlarda AddSurrogate fonksiyonu ilgili tür bilgileriyle birlikte her bir tür için çağırılabilir.

   Yukarıdaki işlemler ardından, hazırlamış olduğumuz vekil seçicimizi aşağıdaki gibi formatter’ımıza belirterek özelleşmiş serileştirmeye hazır oluruz;

formatter.SurrogateSelector = vekilSecici;

   Şimdiye kadar anlattıklarımı bir araya toplayacak olursak bu özelleşmiş serileştirme işlemi için gerekli olan kod parçacığı aşağıdaki gibi olacaktır;

namespace SerilestirmeDeneme {
    public class Ogrenci {
        public string Adi;
        public string Bolum;
    }

    static class Program {
        static void Main() {
            BinaryFormatter formatter = new BinaryFormatter();

            SurrogateSelector vekilSecici = new SurrogateSelector();

            OgrenciSerializationSurrogate serilestirmeVekili = new OgrenciSerializationSurrogate();
            vekilSecici.AddSurrogate(typeof(Ogrenci), new StreamingContext(StreamingContextStates.All), serilestirmeVekili);

            formatter.SurrogateSelector = vekilSecici;

            Ogrenci ogrenciOrnegi = new Ogrenci();
            ogrenciOrnegi.Adi = "Fatih Boy";
            ogrenciOrnegi.Bolum = "Bilgisayar Mühendisliği";

            formatter.Serialize(new System.IO.MemoryStream(), ogrenciOrnegi);
        }

        public sealed class OgrenciSerializationSurrogate : ISerializationSurrogate {
            public void GetObjectData(Object nesne,
                SerializationInfo serilestirmeBilgisi,
                StreamingContext kapsam) {

                Ogrenci ogrenciOrnegi = (Ogrenci)nesne;

                serilestirmeBilgisi.AddValue("Adi", ogrenciOrnegi.Adi);
                serilestirmeBilgisi.AddValue("Bolum", ogrenciOrnegi.Bolum);
            }

            public Object SetObjectData(Object nesne,
                SerializationInfo serilestirmeBilgisi,
                StreamingContext kapsam,
                ISurrogateSelector vekilSecici) {

                Ogrenci ogrenciOrnegi = (Ogrenci)nesne;

                ogrenciOrnegi.Adi = serilestirmeBilgisi.GetString("Adi");
                ogrenciOrnegi.Bolum = serilestirmeBilgisi.GetString("Bolum");

                return null;
            }
        }
    }
}

   Serileştirme işlemi sırasında Formatter sınıfı her bir tür için tanımlı bir serileştirme vekil sınıfı olup olmadığını vekil seçici sınıfı üzerinden sorgular. Tür için tanımlanmış bir serileştirme vekil sınıfı bulunması durumunda ilgili bilgiler ile bu sınıfın GetObjectData (eğer bir serileştirme işlemi ise) ya da SetObjectData (eğer serileştirilmiş bir verinin okunması işlemi ise) fonksiyonları çağırılacaktır. Bu fonksiyonlar içerisindeki iş mantıklarının işletilmesi sonrası nesnemiz başarılı bir şekilde serileştirilebilecektir.

Fatih Boy

http://www.enterprisecoding.com
http://twitter.com/fatihboy