Esempio d’uso di DataContractSerializer (C#)…

Ogni tanto capita la necessità di serializzare/deserializzare rapidamente degli oggetti in C#.

Da Wikipedia:

In informatica, la serializzazione è un processo per salvare un oggetto in un supporto di memorizzazione lineare (ad esempio, un file o un’area di memoria), o per trasmetterlo su una connessione di rete.

La serializzazione può essere in forma binaria o può utilizzare codifiche testuali (ad esempio il formato XML) direttamente leggibili dagli esseri umani.

Lo scopo della serializzazione è di trasmettere l’intero stato dell’oggetto in modo che esso possa essere successivamente ricreato nello stesso identico stato dal processo inverso, chiamato deserializzazione.

Concettualmente l’idea è facile: spostare o memorizzare la rappresentazione degli oggetti in uso per poi usarli dove servono, magari su un altro computer.

Il framework .Net e quindi C# offrono un supporto integrato e nativo a questo procedimento. Anzi, a dirla tutta, forniscono molte classi di supporto la più famosa della quali è la XmlSerializer.

Confesso, non sono mai stato un fan di quella classe per quanto nnon sia difficile da usare. In pieno stile .Net si usano degli attributi, usati come marcatori per indicare esplicitamente le classi serializzabili (attributo Serializable) e poi gli eventuali campi e proprietà della classe (XmlElement, XmlAttribute, …). Ci penserà C# a tradurre gli oggetti opportunamente marcati in XML, ossia a serializzarli.

Quello che non ho mai gradito di questa classe, XmlSerializer, è il suo approccio “opt-out“: tutto è serializzabile, fino a prova contraria. Questo comporta che bisogna marcare esplicitamente tutto ciò che non deve essere serializzato con l’attributo XmlIgnore: lungo e piuttosto fastidioso. Ma c’è dell’altro…

DataContractSerializer

Considero la classe DataContractSerializer, introdotta a partire dal framework 3.0 e usata da/in Windows Communication Foundation (WCF), la classe XmlSerializer rifatta correttamente, togliendo alcune (stupide) limitazioni proprie di quest’ultima.
In particolare, dal forum ufficiale di ASP.Net:

Advantages:

  1. Opt-in rather than opt-out properties to serialize. This mean you specify what you want serialize
  2. Because it is opt in you can serialize not only properties, but also fields. You can even serialize non-public members such as private or protected members. And you dont need a set on a property either (however without a setter you can serialize, but not deserialize)
  3. Is about 10% faster than XmlSerializer to serialize the data because since you don’t have full control over how it is serialize, there is a lot that can be done to optimize the serialization/deserialization process.
  4. Can understand the SerializableAttribute and know that it needs to be serialized
  5. More options and control over KnownTypes

Disadvantages:

  1. No control over how the object is serialized outside of setting the name and the order

Sostanzialmente la classe fa e supporta quello che ci si aspetta (es: che senso ha fornire una classe come XmlSerializer se poi non si può serializzare dati privati o tipi generici come un dizionario?) e, lo ammetto, lo fa bene. Se vogliamo, ha lo svantaggio di supportare solo elementi XML e non attributi, ma tutto sommato non è una grande limitazione.

Per cui, eccovi un piccolo – e spero chiaro – codice di esempio.

Non fa nulla di particolare: definisce una classe base ed una derivata marcate come serializzabili (si usa l’apposito attributo DataContract). Per ciascuna di esse, a loro volta, vengono marcati esplicitamente come serializzabili (attributo DataMember) i campi e le proprietà che vogliamo serializzare/deserializzare.

Per aiutare la classe DataContractSerializer nell’opera di ricostruzione degli oggetti serializzati (e per rendere il codice più snello) alla classe “base” è stato aggiunto un riferimento esplicito alla classe derivata tramite l’attributo KnownType: in pratica avvisiamo in anticipo il parser che c’è una dipendenza fra le due classi.

Inoltre, per evitare l’unico svantaggio della classe DataContractSerializer (solo elementi XML, no attributi), ho rinominato tutto ciò che verrà serializzato imponendo degli identificativi di 2-3 caratteri al massimo (DataContract(Name=”[NAME]“)): questo permette di continuare ad usare nomi comprensibili per gli oggetti in C#, ma di accorciare di molto la loro rappresentazione in formato XML e quindi consumare meno banda in fase di eventuale trasmissione.

Fatto ciò, possiamo finalmente passare alla fase di serializzazione e deserializzazione vera e propria, comodamente effettuata da due extension method: il risultato finale è che avremo “aggiunto” alla classe generica object un metodo che serializza l’oggetto in stringa (XML) ed un metodo alla classe stringa che deserializza in un certo tipo di oggetto.

Suona strano e complicato, ma a vederlo tutto appare immediato ed intuitivo.

Piccola nota: dal momento che creare oggetti DataContractSerializer pare pesante, ho aggiunto una sorta di cache basta su un dizionario thread-safe (ConcurrentDictionary) che memorizza gli oggetti in base al tipo dell’oggetto serializzato. In questo modo se avete del codice che continua ad operare serializzazioni e deserializzazioni, non pagherete i costi di creazioni degli oggetti DataContractSerializer.

Una piccola variazione viene richiesta per i tipi enum, i cui valori vanno esplicitamente marcati con l’attributo EnumMember.

Il codice di esempio

/**
 *	FILE      : Program.cs
 *	AUTHOR    : Gian Paolo "JP" Ghilardi (http://rejex.wordpress.com)
 *	LICENSE   : released under the terms of GPL v2.0 ("only")
 *	PURPOSE   : simple C# example showing how to use the .Net DataContractSerializer class
 *	NOTES     : code based on [1]
 *
 *	TESTED ON :
 *	- Windows XP 32-bit, Windows 7 x86 32-bit, VS2010 (.Net 4.0)
 *	
 *  NOTE:
 *  - serializable data names are shortened to save characters in the XML strings, 
 *    by explicitly specifying the name of each serialized class, DataMember, ...
 *
 *	REFERENCES:
 *	[1]: http://billrob.com/archive/2010/02/09/datacontractserializer-converting-objects-to-xml-string.aspx
 *	[2]: http://msdn.microsoft.com/en-us/library/aa347875.aspx
 *  [3]: http://msdn.microsoft.com/en-us/library/dd287191.aspx
 *  [4]: http://stackoverflow.com/questions/221687/can-you-use-where-to-require-an-attribute-in-c/221727#221727
 */

using System;
using System.Collections.Generic;
using System.Runtime.Serialization;

namespace DataContractSerializerTest
{
    #region Some Test classes
    // "Enumeration Types in Data Contracts" [2]
    [DataContract(Name = "SE")]
    public enum SerializedEnum
    {
        [EnumMember] TEST1,
        [EnumMember] TEST2
    }

    // A simple base class
    [DataContract(Name = "SB")]
    [KnownType(typeof(SerializedDerived))] // Derived class is explicitly specified
    public class SerializedBase
    {
        [DataMember(Name="OM")]
        private int OnlyMine = 101; // Serializing a private field

        [DataMember(Name = "ID")]
        public int Id = -1;         // Serializing a public field

        [DataMember(Name = "MK")]   // Serializing a public field (enum)
        public SerializedEnum Marker = SerializedEnum.TEST1;

        [DataMember(Name = "PT")]   // Serializing a public property
        public int PropTest { get; set; }

        public string NotSerialized; 
    }

    // A simple derived class
    [DataContract(Name = "SD")]
    public class SerializedDerived: SerializedBase
    {
        [DataMember(Name = "VS")]
        public Dictionary<string, string> Values = new Dictionary<string, string>() 
        {
            { "1st", "A" },
            { "2nd", "B" }
        };
    }
    #endregion

    class Program
    {
        static void Main(string[] args)
        {
            Console.WriteLine("\nC# EXAMPLE:");
            Console.WriteLine("Simple example showing C# 3+ object serialization via DataContractSerializer\n");

            SerializedDerived test = new SerializedDerived() { Id = 42 };
            string strTest = test.Serialize<SerializedDerived>(); // Serialize to string

            Console.WriteLine(string.Format("* Serialized\n\n{0}\n", strTest));

            // Testing DataContractSerializer cache
            for (int i = 0; i < 5; i++) 
            {
                Console.WriteLine(string.Format("\n\n* [RUN {0}]", i + 1));

                SerializedDerived des = strTest.Deserialize<SerializedDerived>(); // Deserialize to a SerializedDerived object

                des.Id += i; // Just to change a bit the deserialized object...

                Console.WriteLine("  * Deserialized:");
                Console.WriteLine(string.Format("  Id: {0}", des.Id));

                // Print the contents of the Dictionary
                foreach (string key in des.Values.Keys)
                    Console.WriteLine(string.Format("  Values['{0}']: {1}", key, des.Values[key]));
            }

            Console.WriteLine("\n[JOB DONE]");
            Console.ReadKey();
        }
    }
}

/**
 *	FILE      : ObjSerializationExtensions.cs
 *	AUTHOR    : Gian Paolo "JP" Ghilardi (http://rejex.wordpress.com)
 *	LICENSE   : released under the terms of GPL v2.0 ("only")
 *	PURPOSE   : simple C# example showing how to use the .Net DataContractSerializer class
 *	NOTES     : code based on [1]
 *
 *	TESTED ON :
 *	- Windows XP 32-bit, Windows 7 x86 32-bit, VS2010 (.Net 4.0)
 *
 *	REFERENCES:
 *  [1]: http://billrob.com/archive/2010/02/09/datacontractserializer-converting-objects-to-xml-string.aspx
 *  [2]: http://msdn.microsoft.com/en-us/library/aa347875.aspx
 *  [3]: http://msdn.microsoft.com/en-us/library/dd287191.aspx
 *  [4]: http://stackoverflow.com/questions/221687/can-you-use-where-to-require-an-attribute-in-c/221727#221727
 */

using System;
using System.Collections.Concurrent;
using System.IO;
using System.Runtime.Serialization;
using System.Xml;

namespace DataContractSerializerTest
{
    #region Object Serialization (Extension methods)
    public static class ObjSerializationExtensions
    {
        // Thread-safe cache for DataContractSerializers (avoids unneeded object re-creations)
        private static ConcurrentDictionary<Type, DataContractSerializer> _cache = 
            new ConcurrentDictionary<Type, DataContractSerializer>();

        // Check if a class is marked as DataContract
        private static bool IsClassDCSerializable<T>()
        {
            object[] attributes = typeof(T).GetCustomAttributes(typeof(DataContractAttribute), true);
            return (attributes != null && attributes.Length > 0);
        }

        // Handle access to the thread-safe DataContractSerializer cache
        private static DataContractSerializer GetSerializer<T>()
        {
            // Look for the correct DataContractSerializer in the cache (if available) ...
            if (_cache.ContainsKey(typeof(T))) // Cache hit: return the stored DataContractSerializer
            {
                Console.WriteLine("  --cache hit!--");
                return _cache[typeof(T)];
            }
            else // Cache miss: create a new DataContractSerializer, store it in the cache then return it
            {
                var serializer = new DataContractSerializer(typeof(T));
                if (_cache.TryAdd(typeof(T), serializer))
                    return serializer;
                else
                    return null;
            }
        }

        // Extension method adding type-safe serialization-to-string support to a generic object
        public static string Serialize<T>(this object obj)
        {
            if (!IsClassDCSerializable<T>()) // [4]
                return default(string);

            try
            {
                using (var backing = new StringWriter())
                {
                    using (var writer = new XmlTextWriter(backing))
                    {
                        GetSerializer<T>().WriteObject(writer, obj);
                        return backing.ToString();
                    }
                }
            }
            catch (Exception e)
            {
                Console.WriteLine(string.Format("Exception thrown while serializing: {0}", e.Message));
            }

            return default(string);
        }

        // Extension method adding type-safe deserialization-to-object support to strings
        public static T Deserialize<T>(this string serializedObj)
        {
            if (!IsClassDCSerializable<T>()) // [4]
                return default(T);

            try
            {
                using (var backing = new StringReader(serializedObj))
                {
                    using (var reader = new XmlTextReader(backing))
                    {
                        return (T)GetSerializer<T>().ReadObject(reader);
                    }
                }
            }
            catch (Exception e)
            {
                Console.WriteLine(string.Format("Exception thrown while deserializing: {0}", e.Message));
            }

            return default(T);
        }
    }
    #endregion  
}

Il codice dal vivo

DataContractSerializerTest - Example

Contrassegnato da tag , , , , , ,

2 thoughts on “Esempio d’uso di DataContractSerializer (C#)…

  1. Stefano scrive:

    Bell’esempio.

    Ttempo fa (ai tempi delle MFC, di CDocument e della Serialize, per intenderci) avevo una certa avversione per la serializzazione. Sembrava facile lavorarci, all’inizio, ma se volevi un po’ di controllo diventava tutto troppo complicato.

    Adesso, per fortuna, le cose sono cambiate..

  2. jp scrive:

    Sì, ora le cose sono decisamente migliorate, le soluzioni impiegabili sono molte e, soprattutto, usabili. :)

    Ciau e grazie di essere passato! ^^

Lascia un Commento

Fill in your details below or click an icon to log in:

Logo WordPress.com

You are commenting using your WordPress.com account. Log Out / Modifica )

Foto Twitter

You are commenting using your Twitter account. Log Out / Modifica )

Foto di Facebook

You are commenting using your Facebook account. Log Out / Modifica )

Connecting to %s

Follow

Get every new post delivered to your Inbox.

Join 259 other followers