Upcasting an object "for real"

This week, I had a problem with serialization in a web service. Theorically, any serializable object or object collection can be send or received by a web service. The problem occured when the web service's tried to send a object collection including objects of type A and objects of type B inherited from A. While it tried to serialize the collection (an array or a typed list) as a collection of type A, the runtime throwed this exception:

System.InvalidOperationException: The type [inheritedType] was not expected. Use the XmlInclude or SoapInclude attribute to specify types that are not known statically.

Here is what happened (as I undersand): The SOAP protocol defines a strict serialisation format constructed with the declared type in the [WebMethod] functions, and this format is in the web service's contract. That's why the formatter refuses to serialise inherited types. For example, if type A have a Name and a Id attributes, the web service returning a A object defines theese two attributes it it's SOAP contract. But if the service tries to send a B object, inherited from A but having a added Employer attribute, the formatter will normally want to serialize it with this Employer attribute, whitch is not in the SOAP contract. That is why the exception is throwned.

Normally, upcasting objects is about upcasting reference, not instances. In other words, the program can use an object of type B as if it was an object of type A, but the .net runtime kows it is a object of type B. If you observe an upcasted or downcasted object with the compiler, you will see that the runtime can tell the original type.

Thas is why a object of type B can be upcasted to its parent class type A, then downcasted to type B and still preserve it's properties restricted to class B.

Now, how can I face my web service serialization problem without touching the serialisation process itself ? The serialization can be customized, but that's not what I wanted. I needed a process to handle this particular casting problem in a generic way. That's why I made a class to transform an object of type b in a new object of it's parent type and still preserve it's values. The class uses generic and reflection to convert an object B to a new object A having it's original field values. Note that by reflection, we can access objects private fields and set their values.

Here is the class:


/// <summary>
/// Helper class to Upcast an object to it's parent type in a definitive way.
/// To get an objet with a runtime type of the target parent type.
///
/// By reflection, it gets child instance fields values (event privates)
/// and assign them to a new instance of the given parent class.
/// </summary>
/// <typeparam name="ParentClass">Class to upcast object to</typeparam>
public class InstanceUpCaster<ParentType>
where ParentType : new()
{
/// parent class fields
FieldInfo[] fParents;

/// flags to get all instance fields, even privates
BindingFlags flags = BindingFlags.NonPublic |
BindingFlags.Public | BindingFlags.Instance;

/// <summary>
/// Getting target class fields
/// </summary>
public InstanceUpCaster()
{
fParents = typeof(ParentType).GetFields(flags);
}

/// <summary>
/// Convert the object to a new object of the parent class.
/// Assign field values of the child instance to the new instance,
/// if theese fiels exists in the parent type.
/// (public and private fields can be set by reflection)
/// </summary>
/// <param name="instance">Object to upcast to ParentType</param>
/// <returns>New object with same values (where possible in this type)</returns>
public ParentType ToParentClassInstance(ParentType childTypeObject)
{
/// Object already of the wright type (not derived) ?
if (childTypeObject.GetType() == typeof(ParentType)) return childTypeObject;

// No -> new object to upcast to
ParentType newObject = new ParentType();

/// transfering child field values to new object
FieldInfo[] fChilds = childTypeObject.GetType().GetFields(flags);
object tempValue;
foreach (FieldInfo fParent in fParents)
{
foreach (FieldInfo fChild in fChilds)
{
if (fParent.Name == fChild.Name)
{
tempValue = fChild.GetValue(childTypeObject);
if (tempValue != null)
fParent.SetValue(newObject, tempValue);
}
}
}

return newObject;
}
}

1 comment:

Anonymous said...

Hello, everythіng is going ѕound here
anԁ οfсoursе every onе іs ѕharing ԁata, thаt's really fine, keep up writing.

Feel free to surf to my web site BlackChipPoker Offer ()