40
votes

In C#, I have a class which has a derived property that should be serialized via XML. However, XML serialization (by default) doesn't serialize read=only properties. I can work around this by defining an empty setter like so:

public virtual string IdString
{
    get { return Id.ToString("000000"); }
    set { /* required for xml serialization */ }
}

But is there a cleaner more semantically correct way, short of writing my own ISerializable implementation?

4
If I understand this correctly, you have an IdString that's being serialized to the XML file, but ignored when the class is deserialized? - Dan Bryant
IdString will always be derived from the Id property, which does get serialized AND deserialized. - Chris
In this case why do you serialise IdString? - Justin
@Justin that comment is better than your answer :) One doesn't need to serialize properties if state is all it matters, since it's fields which represent state. But since XmlSerializer only cares about public definition which are properties often, I wished it somehow handles this case. - nawfal
Common cases would be where the XML property is intended to be human-readable, or otherwise for exporting to another system. XML is a common data exchange format, after all. - Jonathan Lidbeck

4 Answers

20
votes

Honestly this doesn't seem too bad to me as long as it is documented

You should probably throw an exception if the setter is actually called:

/// <summary>
/// Blah blah blah.
/// </summary>
/// <exception cref="NotSupportedException">Any use of the setter for this property.</exception>
/// <remarks>
/// This property is read only and should not be set.  
/// The setter is provided for XML serialisation.
/// </remarks>
public virtual string IdString
{
    get
    {
        return Id.ToString("000000");
    }
    set
    {
        throw new NotSupportedException("Setting the IdString property is not supported");
    }
}
14
votes

In short, no. With XmlSerializer you can either implement IXmlSerializable (which is non-trivial), or write a basic DTO (that is fully read-write) and then translate from the DTO model to your main model.

Note that in some cases DataContractSerializer is a viable option, but it doesn't offer the same control over the XML. However, with DCS you can do:

[DataMember]
public int Id { get; private set; }
3
votes

With C# 8, obsoleting set is allowed, so you can do this:

public virtual string IdString
{
    get { return Id.ToString("000000"); }
    [Obsolete("Only used for xml serialization", error: true)]
    set { throw new NotSupportedException(); }
}

This will error if anyone uses the setter accidentally.

1
votes

To take the solution a little further to allow deserialization to work as well...

public class A
{
    private int _id = -1;

    public int Id
    {
        get { return _id; }
        set
        {
            if (_id < 0)
                throw new InvalidOperationException("...");

            if (value < 0)
                throw new ArgumentException("...");

            _id = value;
        }
    }
}

This will allow Id to be set exactly one time to a value greater than or equal to 0. Any attempts to set it after will result in InvalidOperationException. This means that XmlSerializer will be able to set Id during deserialization, but it will never be able to be changed after. Note that if the property is a reference type then you can just check for null.

This may not be the best solution if you have a lot of read-only properties to serialize/deserialize as it would require a lot of boilerplate code. However, I've found this to be acceptable for classes with 1-2 read-only properties.

Still a hack, but this is at least a little more robust.