Tuesday, February 8, 2011

How do I deserialize an XML file into a class with a read only property?

I've got a class that I'm using as a settings class that is serialized into an XML file that administrators can then edit to change settings in the application. (The settings are a little more complex than the App.config allows for.)

I'm using the XmlSerializer class to deserialize the XML file, and I want it to be able to set the property class but I don't want other developers using the class/assembly to be able to set/change the property through code. Can I make this happen with the XmlSerializer class?

To add a few more details: This particular class is a Collection and according to FxCop the XmlSerializer class has special support for deserializing read-only collections, but I haven't been able to find any more information on it. The exact details on the rule this violates is:

Properties that return collections should be read-only so that users cannot entirely replace the backing store. Users can still modify the contents of the collection by calling relevant methods on the collection. Note that the XmlSerializer class has special support for deserializing read-only collections. See the XmlSerializer overview for more information.

This is exactly what I want, but how do it do it?

Edit: OK, I think I'm going a little crazy here. In my case, all I had to do was initialize the Collection object in the constructor and then remove the property setter. Then the XmlSerializable object actually knows to use the Add/AddRange and indexer properties in the Collection object. The following actually works!

public class MySettings
{
    private Collection<MySubSettings> _subSettings;
    public MySettings()
    {
       _subSettings = new Collection<MySubSettings>();
    }

    public Collection<MySubSettings> SubSettings
    {
         get { return _subSettings; }
    }
}
  • I dont think you can use the automatic serialization since the property is read only.

    My course of action would be to implement the ISerializable interface and do it manually. You will be able to set the internal values from here.

    However, if your sub-objects (that are exposed as read only) can take care of serializing themselves, it should all just work..

    I think the rule FxCop is moaning about is that you have something like:

    public List<MyObject> Collection
    {
       get { return _collection; }
       set { _collection = value; }
    }
    

    Is it not? If not, can you paste some code so I can see what exactly it is you are doing? There are several ways to do all of the above :)

    Lloyd Cotten : Yeah, thanks for the answer... I was hoping I wouldn't have to do it manually. Turns out that I actually didn't in this case (see edit to question). If it was any more complex I probably would've had to go that route.
    Rob Cooper : Sorry for the delay in edit, site went down before submit! :D
    From Rob Cooper
  • You have to use a mutable list type, like ArrayList (or IList IIRC).

    Lloyd Cotten : Yeah thanks, it's acutally IEnumerable or ICollection, but yes you're answer is very close to what I discovered.
    From leppie
  • @Rob Cooper had it right, just implement the ISerializable interface and you will be able to have custom control over how your class serializes and deserialzes and set the fields manually. It's a bit more leg-work but it will achieve your desired goal. Good luck.

  • @leppie's response was actually the closest. This is the actual relevant text in the XmlSerializer documentation and see my edit to the question above for more details:

    The XmlSerializer gives special treatment to classes that implement IEnumerable or ICollection. A class that implements IEnumerable must implement a public Add method that takes a single parameter. The Add method's parameter must be of the same type as is returned from the Current property on the value returned from GetEnumerator, or one of that type's bases. A class that implements ICollection (such as CollectionBase) in addition to IEnumerable must have a public Item indexed property (indexer in C#) that takes an integer, and it must have a public Count property of type integer. The parameter to the Add method must be the same type as is returned from the Item property, or one of that type's bases. For classes that implement ICollection, values to be serialized are retrieved from the indexed Item property, not by calling GetEnumerator.

0 comments:

Post a Comment