views:

3891

answers:

5
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.

A: 

See XmlSerializer.Deserialize Method: You could create a static method like the following:

    public static Options DeserializeFromFile(string filename) {    
    // Create an instance of the XmlSerializer specifying type and namespace.
    XmlSerializer serializer = new XmlSerializer(typeof(Options));

    // A FileStream is needed to read the XML document.
    using (FileStream fs = new FileStream(filename, FileMode.Open)) {
        XmlReader reader = new XmlTextReader(fs);
        return (Options) serializer.Deserialize(reader);
    } // using
    }

The above can be called as:

 Options foo = Options.DeserializeFromFile(@"C:\Options.xml");
eed3si9n
1) Please read his question more carefully. 2) -1 for not using "using" blocks.
John Saunders
You are right, I should.
eed3si9n
You still need a using around the XmlReader.
John Saunders
+4  A: 

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.

John Saunders
Good reminder, will change to using block.
david.healed
+4  A: 

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.

amazedsaint
-1 for not implementing "using" blocks, and for not using generics.
John Saunders
using (XmlSerializer deserializer = new XmlSerializer(_type)) doesn't work. XmlSerializer didn't implement IDisposable.The right one should be like what John's answer, putting XmlSerializer outside the using block.
david.healed
Oops, forgot for a moment that XmlSerializer didn't implement IDisposable, corrected :)
amazedsaint
And modified to use generics
amazedsaint
Thanks for the update. At least now I know what John mean by "not using generics".
david.healed
The whole point of using generics is to avoid casting.// In helper classpublic void Save( string path, T @object );public T Read( string path );return (T)result;// In caller classobj = helper.Read( "yourpath" ); // No casting required
Schmuli
+1 for the edits!
John Saunders
Yep, now I've wrote that in the IDE and made sure it is compiling, lol;)
amazedsaint
+5  A: 

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");
Joel Coehoorn
I agree, this IMO is the cleanest solution that answers the question.
spoon16
I can't comment for others, but this fit my current project best... very neat.
CJM
A: 

I went for this approach (in vb)

    Public Class SerialisableClass

    Public Sub SaveToXML(ByVal outputFilename As String)

        Dim xmls = New System.Xml.Serialization.XmlSerializer(Me.GetType)
        Using sw = New IO.StreamWriter(outputFilename)
            xmls.Serialize(sw, Me)
        End Using

    End Sub

    Private tempState As Object = Me
    Public Sub ReadFromXML(ByVal inputFilename As String)

        Dim xmls = New System.Xml.Serialization.XmlSerializer(Me.GetType)

        Using sr As New IO.StreamReader(inputFilename)
            tempState = xmls.Deserialize(sr)
        End Using

        For Each pi In tempState.GetType.GetProperties()

            Dim name = pi.Name

            Dim realProp = (From p In Me.GetType.GetProperties
                            Where p.Name = name And p.MemberType = Reflection.MemberTypes.Property).Take(1)(0)

            realProp.SetValue(Me, pi.GetValue(tempState, Nothing), Nothing)

        Next

    End Sub

End Class

I can then simply use something like this:

Public Class ClientSettings

    Inherits SerialisableClass

    Public Property ZipExePath As String
    Public Property DownloadPath As String
    Public Property UpdateInstallPath As String

End Class

and call it like this:

Dim cs As New ClientSettings
cs.ReadFromXML("c:\myXMLfile.xml")

or even better (if I add the necessary constructor):

Dim cs as New ClientSettings("c:\myXMLFile.xml")

It seems nice and clean to me and works well in my situation.

Cheers

wheelibin