Bob The Janitor
Bob The Janitor

Reputation: 20802

Inheritance casting problem with Generics and XmlSerializer

How do I cast an instance of an object and actually make it that type of object?

I have a class myClass1 that is the base class for myClass2 and myClass3. I want to use myClass1 for auditing, for auditing all I want is the data from myClass1. Because myClass2 and myClass3 inherit from myClass1 you can set an instance of myClass1 to an instance of myClass2 example:

myClass2 foo = new myClass2();
foo.prop1 = "some data";
foo.prop2 = "some More Data";

myClass1 bar = foo;

the problems come because I'm using a generic

 public static IXPathNavigable SerializeGeneric<T>(T serializableObject)
    {
        String XmlizedString = "Error processing request";
        XmlDocument XMLObject = new XmlDocument();
        try
        {
            MemoryStream memoryStream = new MemoryStream();
            XmlSerializer xs = new XmlSerializer(serializableObject.GetType());

to pass the class when I Serialize it and XmlSerializer throws an error because even though I have cast it as a myClass1 the underlying object is still a myClass2 you can see this by casting it an object and then checking the type and XmlSerializer get's confused because I'm telling it to make it a class1 be though it's own reflection it sees it as a myClass2

myClass2 foo = new myClass2();
foo.prop1 = "some data";
foo.prop2 = "some More Data";

myClass1 bar = foo;
object obj = bar;
string name = obj.GetType().Name;

the value of name is "myClass2" which makes sense seeing that the data in the memory is really a myClass2, underneath bar is just a pointer to a myClass2 object. Without creating a new instance and setting the values of that new instance to that object like

myClass1 bar = new myClass1(){prop1=foo.prop1, prop2=foo.prop2};

I really don't want to do it that way.

Upvotes: 1

Views: 881

Answers (2)

Nader Shirazie
Nader Shirazie

Reputation: 10776

To do xml serialization properly when you have inheritance, there are a couple of guidelines:

  1. Always create XmlSerializer with typeof(Base) (when serializing or deserializing)
  2. Set the XmlInclude(typeof(Derived)) attribute on the Base class (for every derived class)

This allows you to call Deserialize any xml that represents any object in the inheritance hierarchy. There are alternatives (such as passing in all the possible derived types to the constructor of XmlSerializer, but its debatable if that's an improvement over the attribute)

Upvotes: 0

Brian Rudolph
Brian Rudolph

Reputation: 6318

No idea if this will work but try changing it to:

XmlSerializer xs = new XmlSerializer(typeof(T));

this will tell the serializer create a serializer instance of whatever type you specify. Though i'm not sure if the serializer will even do this.

Edit: Provided you call

SerializeGeneric<MyClass1>(foo);

Edit Again:

Just tried it with this:

    public void Test()
    {
        var obj = new Foo2() { Prop1 = "Test", Prop2 = "Test2" };

        SerializeGeneric((Foo1)obj);
    }

    private void SerializeGeneric<T>(T obj)
    {
        StringWriter writer = new StringWriter();    
        XmlSerializer xs = new XmlSerializer(typeof(T));
        xs.Serialize(writer, obj);

        Console.WriteLine(writer.ToString());
    }


    public class Foo1
    {
        public string Prop1 { get; set; }
    }

    public class Foo2 : Foo1
    {
        public string Prop2 { get; set; }
    }

It throws an exception of "Unexpected Type". It turns out the serializer won't serialize an object as a different type. Not sure of any way to make it do it.

I suppose you could write a custom serializer, or write a simple reflection method that does a memberwiseclone-ish operation that only copies the properties from foo1 that you want.

Interestingly it doesn't error if you add [XmlInclude(typeof(Foo2))] to the Foo1 declaration, though it outputs this gibberish:

<Foo1 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xsi:type="Foo2">
  <Prop1>Test</Prop1>
  <Prop2>Test2</Prop2>
</Foo1>

Which is a Foo1 declaration, with Foo1 & Foo2 properties, with the type declared as Foo2... interesting.

Last one:

This works, though im not sure i'd recommend it.

    public void Test ()
    {
        var obj = new Foo2() { Prop1 = "Test", Prop2 = "Test2" };

        SerializeGeneric(ShallowCopy<Foo1>(obj));
    }

    private T ShallowCopy<T>(object input)
        where T : class, new()
    {
        T newObj = Activator.CreateInstance<T>();
        Type oldType = input.GetType();
        BindingFlags flags = BindingFlags.Instance | BindingFlags.Public | BindingFlags.GetField | BindingFlags.SetField;
        var oldProperties = oldType.GetProperties(flags);

        foreach (var pd in typeof(T).GetProperties(flags))
        {
            var oldPd = oldProperties.FirstOrDefault(x=>x.Name == pd.Name && x.PropertyType == pd.PropertyType);

            if(oldPd != null)
               pd.SetValue(newObj, oldPd.GetValue(input, null), null);
        }

        return newObj;
    }

This gives you:

<Foo1 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
  <Prop1>Test</Prop1>
</Foo1>

which looks perfect.

Upvotes: 1

Related Questions