views:

1040

answers:

4

I would like to XML serialize an object that has (among other) a property of type IModelObject (which is an interface).

public class Example
{
    public IModelObject Model { get; set; }
}

When I try to serialize an object of this class, I receive the following error:
"Cannot serialize member Example.Model of type Example because it is an interface."

I understand that the problem is that an interface cannot be serialized. However, the concrete Model object type is unknown until runtime.

Replacing the IModelObject interface with an abstract or concrete type and use inheritance with XMLInclude is possible, but seems like an ugly workaround.

Any suggestions?

A: 
: IXmlSerializable

Then think what will happen with two objects referencing same model...

ima
+1  A: 

Unfortunately there's no simple answer, as the serializer doesn't know what to serialize for an interface. I found a more complete explaination on how to workaround this on MSDN

MattH
+1  A: 

Replacing the IModelObject interface with an abstract or concrete type and use inheritance with XMLInclude is possible, but seems like an ugly workaround.

If it is possible to use an abstract base I would recommend that route. It will still be cleaner than using hand-rolled serialization. The only trouble I see with the abstract base is that your still going to need the concrete type? At least that is how I've used it in the past, something like:

 public abstract class IHaveSomething
 {
  public abstract string Something { get; set; }
 }

 public class MySomething : IHaveSomething
 {
  string _sometext;
  public override string Something 
  { get { return _sometext; } set { _sometext = value; } }
 }

 [XmlRoot("abc")]
 public class seriaized
 {
  [XmlElement("item", typeof(MySomething))]
  public IHaveSomething data;
 }
csharptest.net
+6  A: 

This is simply an inherent limitation of declarative serialization where type information is not embedded within the output.

On trying to convert <Flibble Foo="10" /> back into

public class Flibble { public object Foo { get; set; } }

How does the serializer know whether it should be an int, a string, a double (or something else)...

To make this work you have several options but if you truly don't know till runtime the easiest way to do this is likely to be using the XmlAttributeOverrides.

Sadly this will only work with base classes, not interfaces the best you can do there is to ignore the property which isn't sufficient for your needs.

If you really must stay with interfaces you have three real options:

Hide it and deal with it in another property

Ugly, unpleasant boiler plate and much repetition but most consumers of the class will not have to deal with the problem:

[XmlIgnore()]
public object Foo { get; set; }

[(XmlElement("Foo")]
[EditorVisibile(EditorVisibility.Advanced)]
public string FooSerialized 
{ 
  get { /* code here to convert any type in Foo to string */ } 
  set { /* code to parse out of get and make an instance of the proper type*/ } 
}

This is likely to become a maintenance nightmare...

Implement IXmlSerializable

Similar to the first option in that you take full control of things but

  • Pros
    • You don't have nasty 'fake' properties hanging around.
    • you can interact directly with the xml structure adding flexibility/versioning
  • Cons
    • you may end up having to re-implement the wheel for all the other properties on the class

Issues of duplication of effort are similar to the first.

Modify your property to use a wrapping type

public sealed class XmlAnything<T> : IXmlSerializable
{
    public XmlAnything() {}
    public XmlAnything(T t) { this.Value = t;}
    public T Value {get; set;}

    public void WriteXml (XmlWriter writer)
    {
        if (Value == null)
        {
            writer.WriteAttributeString("type", "null");
            return;
        }
        Type type = this.Value.GetType();
        XmlSerializer serializer = new XmlSerializer(type);
        writer.WriteAttributeString("type", type.AssemblyQualifiedName);
        serializer.Serialize(writer, this.Value);   
    }

    public void ReadXml(XmlReader reader)
    {
        if(!reader.HasAttributes)
            throw new FormatException("expected a type attribute!");
        string type = reader.GetAttribute("type");
        reader.Read(); // consume the value
        if (type == "null")
            return;// leave T at default value
        XmlSerializer serializer = new XmlSerializer(Type.GetType(type));
        this.Value = (T)serializer.Deserialize(reader);
    }

    public XmlSchema GetSchema() { return(null); }
}

Using this would involve something like (in project P):

public namespace P
{
    public interface IFoo {}
    public class RealFoo : IFoo { public int X; }
    public class OtherFoo : IFoo { public double X; }

    public class Flibble
    {
        public XmlAnything<IFoo> Foo;
    }


    public static void Main(string[] args)
    {
        var x = new Flibble();
        x.Foo = new XmlAnything<IFoo>(new RealFoo());
        var s = new XmlSerializer(typeof(Flibble));
        var sw = new StringWriter();
        s.Serialize(sw, x);
        Console.WriteLine(sw);
    }
}

which gives you:

<?xml version="1.0" encoding="utf-16"?>
<MainClass 
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xmlns:xsd="http://www.w3.org/2001/XMLSchema"&gt;
 <Foo type="P.RealFoo, P, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null">
  <RealFoo>
   <X>0</X>
  </RealFoo>
 </Foo>
</MainClass>

This is obviously more cumbersome for users of the class though avoids much boiler plate.

A happy medium may be merging the XmlAnything idea into the 'backing' property of the first technique. In this way most of the grunt work is done for you but consumers of the class suffer no impact beyond confusion with introspection.

ShuggyCoUk