43

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?

Chris
  • 27,596
  • 25
  • 124
  • 225
  • 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 Apr 07 '11 at 17:57
  • 1
    IdString will always be derived from the Id property, which does get serialized AND deserialized. – Chris Apr 07 '11 at 18:22
  • 11
    In this case why do you serialise IdString? – Justin Apr 08 '11 at 07:51
  • @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 Jul 11 '14 at 15:52
  • 4
    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 Jan 07 '19 at 18:23

5 Answers5

20

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");
    }
}
Justin
  • 84,773
  • 49
  • 224
  • 367
  • 9
    Wouldn't that cause the deserialization to fail, just because of this exception then being thrown? – primfaktor Jul 22 '11 at 07:53
  • 2
    @primfaktor Yes, but as I understood the question that is the point (it will never be deserialised). If that wasn't the desired behaviour you could just do nothing in the setter, or even parse the string and set the `Id` property (possibly only if its not already been set). – Justin Jul 22 '11 at 08:35
  • 1
    Oh, sure. I was propably low on caffeine when writing this. – primfaktor Jul 25 '11 at 11:25
14

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; }
Marc Gravell
  • 1,026,079
  • 266
  • 2,566
  • 2,900
  • 5
    `IXmlSerializable` actually works really well for this problem, especially if you are only serializing and not deserializing, and it isn't that hard to do. Have a look at this: http://www.codeproject.com/Articles/43237/How-to-Implement-IXmlSerializable-Correctly – cjbarth Jan 25 '13 at 19:55
6

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.

Yair Halberstadt
  • 5,733
  • 28
  • 60
1

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.

  • 2
    Shouldn't the first `if` throw if `_id` is ge zero, not lt zero? – Craig May 23 '18 at 21:11
  • Well actually the first `if` is preventing any possible `set`. The right implementation should be `If (_id<0) _id = value; else throw new Exception("");` – Shyguy Aug 24 '20 at 15:14
-3

In most cases when we define a read-only property, they must be ignored in serializations. So just simply ignore them:

public class Classname
{
    [JsonIgnore]
    [XmlIgnore]
    public virtual string IdString { get; private set; }
}
AmiN Zamani
  • 45
  • 1
  • 4