Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

C# - How to xml deserialize object itself?

public class Options
    {
        public FolderOption FolderOption { set; get; }

        public Options()
        {
            FolderOption = new FolderOption();
        }


        public void Save()
        {
            XmlSerializer serializer = new XmlSerializer(typeof(Options));
            TextWriter textWriter = new StreamWriter(@"C:\Options.xml");
            serializer.Serialize(textWriter, this);
            textWriter.Close();
        }

        public void Read()
        {
            XmlSerializer deserializer = new XmlSerializer(typeof(Options));
            TextReader textReader = new StreamReader(@"C:\Options.xml");
            //this = (Options)deserializer.Deserialize(textReader);
            textReader.Close();

        }
    }
}

I managed to Save without problem, all members of FolderOption are deserialized. But the problem is how to read it back? The line - //this = (Options)deserializer.Deserialize(textReader); won't work.

Edit: Any solution to this problem? Can we achieve the same purpose without assigning to this? That is deserialize Options object back into Option. I am lazy to do it property by property. Performing on the highest level would save of lot of effort.

like image 915
david.healed Avatar asked Jul 04 '09 02:07

david.healed


3 Answers

Build your .Read() method as a static function that returns the read object:

public static Options Read(string path)
{
    XmlSerializer deserializer = new XmlSerializer(typeof(Options));
    using (TextReader textReader = new StreamReader(path))
    {
        return (Options)deserializer.Deserialize(textReader);
    }
}

Then change your calling code so rather than something like this:

Options myOptions = new Options();
myOptions.Read(@"C:\Options.xml");

You do something like this:

Options myOptions = Options.Read(@"C:\Options.xml");

The nice difference is that it's impossible to ever have an Options object that doesn't have some data behind it.

like image 69
Joel Coehoorn Avatar answered Oct 22 '22 16:10

Joel Coehoorn


This will work if your Options type is a struct, as you can a alter a struct itself.

If Options is a class (reference type), you can't assign to the current instance of a reference type with in that instance. Suggesting you to write a helper class, and put your Read and Save methods there, like this

     public class XmlSerializerHelper<T>
    {
        public Type _type;

        public XmlSerializerHelper()
        {
            _type = typeof(T);
        }


        public void Save(string path, object obj)
        {
            using (TextWriter textWriter = new StreamWriter(path))
            {
                XmlSerializer serializer = new XmlSerializer(_type);
                serializer.Serialize(textWriter, obj);
            }

        }

        public T Read(string path)
        {
            T result;
            using (TextReader textReader = new StreamReader(path))
            {
                XmlSerializer deserializer = new XmlSerializer(_type);
                result = (T)deserializer.Deserialize(textReader);
            }
            return result;

        }
    }

And then consume it from your caller, to read and save objects, instead of trying it from the class.

//In the caller

var helper=new XmlSerializerHelper<Options>();
var obj=new Options();

//Write and read
helper.Save("yourpath",obj);
obj=helper.Read("yourpath");

And put the XmlSerializerHelper in your Util's namespace, it is reusable and will work with any type.

like image 21
amazedsaint Avatar answered Oct 22 '22 16:10

amazedsaint


An object cannot deserialize itself, by definition: it already exists, and deserialization creates a new instance of the type.

It sometimes makes sense to create a new, empty instance of a class, then fill it in with information brought in from XML. The instance could also be "almost empty". You might do this, for instance, in order to load user preferences, or in general, to set the instance back up to the way it used to be. The "empty" or "near empty" state of the instance would be a valid state for the class: it just wouldn't know what state it used to be in before it was persisted.


Also, I recommend you get into the habit of implementing "using" blocks:

public void Save()
{
    XmlSerializer serializer = new XmlSerializer(typeof(Options));
    using (TextWriter textWriter = new StreamWriter(@"C:\Options.xml"))
    {
        serializer.Serialize(textWriter, this);
        // no longer needed: textWriter.Close();
    }
}

public void Read()
{
    XmlSerializer deserializer = new XmlSerializer(typeof(Options));
    using (TextReader textReader = new StreamReader(@"C:\Options.xml"))
    {
        // no longer needed: textReader.Close();
    }
}

This will ensure that the TextReaders are disposed of even if an exception is thrown. That's why the Close calls are no longer needed.

like image 45
John Saunders Avatar answered Oct 22 '22 17:10

John Saunders