Simple Data Serialization in .NET

It certainly is possible to serialize an object in .NET without all the fuss of dealing with encodings, data contracts, the XmlSerializer class, and annotations like [DataContract] or [Serializable]. I want to illustrate a solution using Linq and XElement objects, one that I’ve grown quite fond of by the way. You do not need to use the XmlSerializer or the DataContractSerializer or any class or property annotations like [DataContract] or [Serializable] for that matter, if you do not wish to do so. In a nutshell, here it is:

using System;
using System.Linq;
using System.Xml.Linq;

namespace SerializationTesting
{

    class Person
    {

        // Notice how this object type uses private setters, something that the traditional XmlSerializer will complain about if you don't use a wrapper class..
        public string Name { get; private set; }
        public DateTime Birthday { get; private set; }
        public long HeightInMillimeters { get; private set; }
        public Gender Gendrality { get; private set; }

        // Generate a serialized XElement from this Person object.
        public XElement ToXElement()
        {
            return new XElement("person",
                new XAttribute("name", Name),
                new XAttribute("birthday", Birthday),
                new XAttribute("heightInMillimeters", HeightInMillimeters),
                new XAttribute("gendrality", (long)Gendrality)
            );
        }

        // Serialize this Person object to an XElement.
        public static Person FromXElement(XElement x)
        {
            return new Person(
                (string)x.Attribute("name"),
                (DateTime)x.Attribute("birthday"),
                (long)x.Attribute("heightInMillimeters"),
                (Gender)(long)x.Attribute("gendrality")
            );
        }

        public Person(string name, DateTime birthday, long heightInMillimeters, Gender gender)
        {
            Name = name;
            Birthday = birthday;
            HeightInMillimeters = heightInMillimeters;
            Gendrality = gender;
        }

        // You must override this in conjunction with overriding GetHashCode (below) if you want .NET collections (HashSet, List, etc.) to properly compare Person objects.
        public override bool Equals(object obj)
        {
            if (obj.GetType() == typeof(Person))
            {
                Person objAsPerson = (Person)obj;
                return Name == objAsPerson.Name && Birthday == objAsPerson.Birthday && HeightInMillimeters == objAsPerson.HeightInMillimeters && Gendrality == objAsPerson.Gendrality;
            }
            return false;
        }

        // You must override this in conjunction with overriding Equals (above) if you want .NET collections (HashSet, List, etc.) to properly compare Person objects.
        public override int GetHashCode()
        {
            return Name.GetHashCode() ^ Birthday.GetHashCode() ^ HeightInMillimeters.GetHashCode() ^ Gendrality.GetHashCode();
        }

        // This allows us to compare Person objects using the == operator.
        public static bool operator ==(Person a, Person b)
        {
            return a.Equals(b);
        }

        // This allows us to compate Person objects using the != operator.
        public static bool operator !=(Person a, Person b)
        {
            return !a.Equals(b);
        }
    }

    public enum Gender
    {
        Male,
        Female
    }

    class Program
    {
        static void Main(string[] args)
        {
            // Create first person (note how UTC time saves and loads properly when casting).
            Person personOne = new Person("Alexandru", DateTime.UtcNow, 1000, Gender.Male);
            // Save the first person to a local file on the hard disk.
            personOne.ToXElement().Save("PersonOne.dat");
            // Create second person (not using UTC time this time around).
            Person personTwo = new Person("Alexandria", DateTime.Now, 900, Gender.Female);
            // Save the second person to a local file on the hard disk.
            personTwo.ToXElement().Save("PersonTwo.dat");
            // Load the first person from a local file on the hard disk.
            XDocument personOneDocument = XDocument.Load("PersonOne.dat");
            Person personOneLoadedFromDocument = Person.FromXElement(personOneDocument.Elements().First());
            // Load the second person from a local file on the hard disk.
            XDocument personTwoDocument = XDocument.Load("PersonTwo.dat");
            Person personTwoLoadedFromDocument = Person.FromXElement(personTwoDocument.Elements().First());
            // Serialize the first person to a string and then load them from that string.
            string personOneString = personOne.ToXElement().ToString();
            XDocument personOneDocumentFromString = XDocument.Parse(personOneString);
            Person personOneLoadedFromDocumentFromString = Person.FromXElement(personOneDocumentFromString.Elements().First());
            // Check for equalities between persons (all outputs will be "true").
            Console.WriteLine(personOne.Equals(personOneLoadedFromDocument));
            Console.WriteLine(personTwo.Equals(personTwoLoadedFromDocument));
            Console.WriteLine(personOne == personOneLoadedFromDocument);
            Console.WriteLine(personTwo == personTwoLoadedFromDocument);
            Console.WriteLine(personOne != personTwo);
            Console.WriteLine(personOneLoadedFromDocument != personTwoLoadedFromDocument);
            Console.WriteLine(personOne.Equals(personOneLoadedFromDocumentFromString));
            Console.WriteLine("Press any key to exit.");
            Console.ReadKey();
        }
    }
}

The output of all of the equality checks in the console application above will be true, as expected. This does not suffer from annoyances like having to keep track of encodings or the way you parse data because it does all of that for you, and it does not limit your class to public setters like the XmlSerializer or some others do. This gives you much more flexibility in the long run, and will help ensure your code-base does not grow where it doesn’t need to.

Alexandru

"To avoid criticism, say nothing, do nothing, be nothing." - Aristotle

"It is wise to direct your anger towards problems - not people; to focus your energies on answers - not excuses." - William Arthur Ward

"Science does not know its debt to imagination." - Ralph Waldo Emerson

"Money was never a big motivation for me, except as a way to keep score. The real excitement is playing the game." - Donald Trump

"All our dreams can come true, if we have the courage to pursue them." - Walt Disney

"Mitch flashes back to a basketball game held in the Brandeis University gymnasium in 1979. The team is doing well and chants, 'We're number one!' Morrie stands and shouts, 'What's wrong with being number two?' The students fall silent." - Tuesdays with Morrie

I'm not entirely sure what makes me successful in general programming or development, but to any newcomers to this blood-sport, my best guess would be that success in programming comes from some strange combination of interest, persistence, patience, instincts (for example, someone might tell you that something can't be done, or that it can't be done a certain way, but you just know that can't be true, or you look at a piece of code and know something doesn't seem right with it at first glance, but you can't quite put your finger on it until you think it through some more), fearlessness of tinkering, and an ability to take advice because you should be humble. Its okay to be wrong or to have a bad approach, realize it, and try to find a better one, and even better to be wrong and find a better approach to solve something than to have had a bad approach to begin with. I hope that whatever fragments of information I sprinkle across here help those who hit the same roadblocks.

Leave a Reply

Your email address will not be published. Required fields are marked *