Makale Özeti

Bu makalemde sizlerle ileri düzey serileştirme işlemlerini paylaşmaya devam ediyorum. Önceki makalemde sizlerle paylaşmış olduğum yapıyı daha genel-geçer bir hale getirerek vekil seçim zinciri'ni(ISurrogateSelector) nasıl kullanabileceğinizi paylaşıyorum.

Makale

   Bir önceki makalemde .Net framework serileştirme işlemlerini detaylı olarak incelemiş ve ileri düzey kullanımları örneklemeye çalışmıştım. Bir örnek üzerinden 3.parti assembly’ler içerisinde bulunan ve serileştirilebilir olarak işaretlenmemiş sınıfların ileri düzey yöntemlerle nasıl serileştirilebileceğini paylaşmıştım. Bu makalemde aynı örnekler üzerinden giderek vekil seçim zinciri ve nasıl genel-geçer bir vekil seçici yapabileceğinizi paylaşacağım.

   Serileştirme sırasında, seyrekte olsa, kimi senaryolarda birden fazla vekil seçicinin bir zincirin parçası şeklinde ardıl olarak kullanımı gerekebilir. İki farklı AppDomain arasında iletişimi sağlayan bir serileştirmeyi düşünün, versiyon değişikliği sonrası kabul etmeniz gereken iki farklı grup nesne modeli (versiyon 1 ve versiyon 2 gibi) arasında da seçim yapmanız gerekebilir. Böyle bir senaryoda  birden fazla vekil seçiciye ihtiyaç duyulacaktır.

   Serileştirme mimarisi tasarlanırken böylesi senaryolar da öngörülerek birden fazla vekil seçiciden oluşan zincirlerin oluşturulabilmesine olanak sağlanmıştır. Tüm vekil seçicilerin implemente etmek zorunda oldukları ISurrogateSelector arayüzünde yer alan ChainSelector ve GetNextSelector fonksiyonları bu amaç için kullanılmaktadır.

public interface ISurrogateSelector {
    void ChainSelector(ISurrogateSelector secici);

    ISurrogateSelector GetNextSelector();

    ISerializationSurrogate GetSurrogate(Type tur, StreamingContext kapsam, out ISurrogateSelector secici);
}

   Bu fonksiyonlardan ChainSelector kullanılarak zincirin bir sonraki halkasını belirtebilir ve GetNextSelector fonksiyonu ile de bir sonraki vekil seçiciye ulaşabilirsiniz. GetSurrogate fonksiyonuna gönderilen tür ve kapsam parametreleri yardımıyla kullanılması gereken serileştirme vekiline karar vererek sistem tarafından kullanılması üzere değer olarak geri döndürebilirsiniz.  Eğer tanımladığınız vekil seçici verilen parametrelere uygun bir serileştirme vekili bulamazsa, zincirin bir sonraki halkasına sorulması için boş (null) değer döndürmelidir. İsterseniz ISurrogateSelector arayüzünü implemente ederek kendi vekil seçicilerinizi de oluşturmanız mümkün.

   Kendi vekil seçicimizi oluşturmadan önce isterseniz önceki makalemde yer alan örnekte kullandığım şu iki satırı inceleyelim;

SurrogateSelector vekilSecici = new SurrogateSelector();
formatter.SurrogateSelector = vekilSecici;

   Burada .net framework ile birlikte gelen SurrogateSelector sınıfını kullanılarak ilgili tür için kullanılması gereken vekil’in bulunması işinden kurtulmuş ve hızlıca türün serileştirilebilmesine odaklanabilmiştik. Ogrenci sınıfının serileştirilmesi görevini üstlenen OgrenciSerializationSurrogate sınıfımızı hazırladıktan sonra  aşağıdaki satırlar yardımıyla da bunu vekil seçicimize tanıtmıştık;

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

   Bu basit örnek için sıkıntısız işleyecek olan bu kod parçası, yönetilmesi gereken Ogrenci sınıfına ek olarak yeni başka sınıflar çıktığında işleri karmaşıklaştıracaktır. Bu karmaşıklığa bir de 3. parti kütüphane içerisinden hangi türlerin gelebileceğiniz bilemediğimiz senaryoları da eklersek sanırım kafanızda kötü bir resim oluşturmuş olurum. Böylesi  bir durumda .net framework ile birlikte gelen SurrogateSelector’ün işimizi görmeyeceği ortadadır ve yapılması gereken özel bir vekil seçici sınıfın oluşturulmasıdır.

   Aşağıda yer alan NonSerializedSurrogateSelector sınıfı serileştirilemeyen Ogrenci ve benzeri sınıflar için oluşturulabilecek özelleşmiş vekil seçicilere bir örnektir;

public sealed class NonSerializedSurrogateSelector : ISurrogateSelector {
    private ISurrogateSelector sonrakiVekilSecici;
    private ISerializationSurrogate serilestirmeVekili = new GenericSerializationSurrogate();

    #region ISurrogateSelector Members

    public void ChainSelector(ISurrogateSelector selector) {
        sonrakiVekilSecici = selector;
    }

    public ISurrogateSelector GetNextSelector() {
        return sonrakiVekilSecici;
    }

    public ISerializationSurrogate GetSurrogate(Type type, StreamingContext context, out ISurrogateSelector selector) {
        if (!type.IsDefined(typeof(SerializableAttribute), true)) {
            selector = this;
            return serilestirmeVekili;
        }

        selector = null;
        return null;
    }

    #endregion
}

   ISurrogateSelector arayüzünü implemente eden NonSerializedSurrogateSelector  sınıfım zincirin bir sonraki  halkasını tutmak için 2. satırda tanımlanan sonrakiVekilSecici değişkenini kullanmakta, ChainSelector ve GetNextSelector fonksiyonlarında bu değişkeni ile işlem yapmakta. GetSurrogate fonksiyonu içerisinde ise gelen türün SerializableAttribute ile işaretlenip işaretlenmediğini kontrol ederek serileştirilemez işaretlenen türler için 3. satırda tanımı yapılmış olan serilestirmeVekili’ni dönmektedir. Bu örnekte, serileştirilebilen türler için bir efor sarfedilmesi gerekmediğinden, bu türlerde sınıfımız bir vekil dönmeyerek işin sistem tarafından yapılması sağlanmıştır.

   NonSerializedSurrogateSelector sınıfı bize tüm serileştirilemeyen türlerde istediğimiz bir serileştirme vekilini kullanabilme şansı sunmakta. NonSerializedSurrogateSelector sınıfı içerisinde yer alan GenericSerializationSurrogate sınıfı bizim tarafımızdan yazılacak olan ve ISerializationSurrogate arayüzünü implemente eden bir sınıftır. Bir önceki makalemde kullandığım OgrenciSerializationSurrogate’den farklı olarak genel geçer bir vekil tanımlamasıdır ve tür bilgilerinin alınması için reflection yöntemine başvurulmuştur. Aşağıda bu sınıfın içeriğini bulabilirsiniz;

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

        FieldInfo[] nesneAlanlari = nesne.GetType().GetFields();

        foreach (var nesneAlani in nesneAlanlari) {
            if (nesneAlani.MemberType == MemberTypes.Field || nesneAlani.MemberType == MemberTypes.Property) {
                serilestirmeBilgisi.AddValue(nesneAlani.Name, nesneAlani.GetValue(nesne));
            }
        }
    }

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

        FieldInfo[] nesneAlanlari = nesne.GetType().GetFields();

        foreach (var nesneAlani in nesneAlanlari) {
            if (nesneAlani.MemberType == MemberTypes.Field || nesneAlani.MemberType == MemberTypes.Property) {
                nesneAlani.SetValue(nesne, serilestirmeBilgisi.GetValue(nesneAlani.Name, nesneAlani.FieldType));
            }
        }

        return null;
    }
}

   Sınıf içerisinde yer alan ve ISerializationSurrogate  arayüzünden gelen GetObjectData ile SetObjectData fonksiyonları içerisinde gelen nesnenin tüm alanları (6. ve 20. satırda) listelenmekte. Ardından bu alanlardan türü değişken ve özellik olanların (9. ve 24. satır) isimleri ve taşıdıkları değer serileştirilmekte ve geri okunmakta.

   Yukarıdaki adımlar sonrası artık elimizde generik bir serileştirme kodu oluşacaktır. Aşağıdaki örnek kod parçacığını kullanarak test yapabilirsiniz;

BinaryFormatter formatter = new BinaryFormatter();
formatter.SurrogateSelector = new NonSerializedSurrogateSelector();

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

var stream = new MemoryStream();
formatter.Serialize(stream, ogrenciOrnegi);

stream.Seek(0, SeekOrigin.Begin);
var sonuc = (Ogrenci)formatter.Deserialize(stream);

   Yazmış olduğumuz kod serileştirilebilir olarak sınıflar da çalıştığı gibi aşağıda örneklenen karma sınıflarda da sorunsuz şekilde çalışabilmektedir;

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

[Serializable]
public class Bolum {
    public string Adi;
    public string Kodu;
}

   Aşağıdaki kodunu kullanarak yeni sınıflarımızı test edebilirsiniz;

BinaryFormatter formatter = new BinaryFormatter();
formatter.SurrogateSelector = new NonSerializedSurrogateSelector();

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

var stream = new MemoryStream();
formatter.Serialize(stream, ogrenciOrnegi);

stream.Seek(0, SeekOrigin.Begin);
var sonuc = (Ogrenci)formatter.Deserialize(stream);

   Bolum sınıfı başında bulunan Serializable özeniteliğini kaldırdığınızda da örneğimiz hatasız şekilde çalıştığını görebilirsiniz.

Fatih Boy

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