I'm posting this on a FIFTY-FITY basis.
50% likely I am missing something obvious and others will tell me why I am a doofus - in which case, can this be done more simply? Tell me I'm wrong if you are willing -- but please don't bite too hard!
50% my solution is actually useful and others might appreciate it.
Either way, I hope to learn something.
I am using XML serialization for a very heavy-duty project where I have a production-line of analytics. There are many steps in the production line so I have written some very abstract base machinery to underlie them all. It has been a lot of work! 60k lines of code. 1,000 Types.
What I want to do now is serialize any part of the production line to XML without having to write custom serialization code for each component in the production line. I want a single approach, held in a base class. Nothing unusual about that so far.
But the snitch is that C# (well, .NET)
- Cannot serialize Dictionaries
- Cannot serialize Interfaces
- Offers limited debugging, logging and inspectability in XML serialization.
So I wrote the following function in my base class that everything inherits.
public void WriteXml(XmlWriter writer)
{
Console.WriteLine("############### WRITING XML FOR " + GetType() + " " + this.Name);
foreach (FieldInfo fieldInfo in this.GetFieldInfos(this))
{
try
{
string fieldName = fieldInfo.Name;
var fieldValue = fieldInfo.GetValue(this);
if (!IsBackFieldName(fieldName))
{
Console.WriteLine("Serializing\t" + fieldInfo.FieldType + "\t\t" + fieldName + "\t" + fieldValue);
if (fieldInfo.FieldType.IsDictionary())
{
// TODO intercept any Dictionary type and convert to SerializableDictionary
;
}
writer.WriteStartElement(fieldName);
if (fieldInfo.FieldType.IsXmlSerializable())
{
XmlSerializer xmlSerializer;
if (fieldInfo.FieldType.IsInterface)
{
// look through the interface to the underlying type, which will have a parameterless constructor for serialization
IData castedValue = fieldValue as IData;
Type lookThroughClass = castedValue.SerializationType;
xmlSerializer = new XmlSerializer(lookThroughClass);
}
else
xmlSerializer = new XmlSerializer(fieldInfo.FieldType);
// serialization here can be built-in or overriden if IXmlSerializable is implemented
xmlSerializer.Serialize(writer, fieldValue);
}
else
{
writer.WriteComment("Not serializable " + fieldInfo.FieldType);
}
writer.WriteEndElement();
}
else
{
// skip backing field
Console.WriteLine("SKIPPING\t" + fieldInfo.FieldType + "\t\t" + fieldName + "\t" + fieldValue);
;
}
}
catch (Exception e)
{
Console.WriteLine("Error writing XML: " + e.Message);
}
}
foreach (PropertyInfo propertyInfo in GetPropertyInfos(this))
{
try
{
string propertyName = propertyInfo.Name;
var propertyValue = propertyInfo.GetValue(this);
if (!IsBackFieldName(propertyName))
{
Console.WriteLine("Serializing\t" + propertyInfo.PropertyType + "\t\t" + propertyName + "\t" + propertyValue);
// TODO intercept any Dictionary type and convert to SerializableDictionary
if (propertyInfo.PropertyType.IsDictionary())
{
// TODO intercept any Dictionary type and convert to SerializableDictionary
;
}
writer.WriteStartElement(propertyName);
if (propertyInfo.PropertyType.IsXmlSerializable())
{
XmlSerializer xmlSerializer;
if (propertyInfo.PropertyType.IsInterface)
{
// look through the interface to the underlying type, which will have a parameterless constructor for serialization
IData castedValue = propertyValue as IData;
Type lookThroughClass = castedValue.SerializationType;
xmlSerializer = new XmlSerializer(lookThroughClass);
}
else
xmlSerializer = new XmlSerializer(propertyInfo.PropertyType);
// serialization here can be built-in or overriden if IXmlSerializable is implemented
xmlSerializer.Serialize(writer, propertyValue);
}
else
{
writer.WriteComment("Not serializable " + propertyInfo.PropertyType);
}
writer.WriteEndElement();
}
else
{
// skip backing field
Console.WriteLine("SKIPPING\t" + propertyInfo.PropertyType + "\t\t" + propertyName + "\t" + propertyValue);
}
}
catch (Exception e)
{
Console.WriteLine("Error writing XML: " + e.Message);
}
}
return;
}
The supporting infrastructure is:
public interface IData
: IXmlSerializable
{
#region Members
string Name { get; }
#endregion
#region Methods
/// <summary>
/// Allows us to pass the underlying type back out through an interface.
/// We have to know the actual type for serialization, because interfaces cannot be instantiated.
/// </summary>
Type SerializationType { get; }
#endregion
}
IData is implemented by all members of the production line which also inherit the XML write and read methods in the base class. The key (possibly sneaky) part is that each child class has to implement SerializationType
locally:
public Type SerializationType => GetType();
This allows the locally-scoped Type to be made visible in the interface.
These are helper functions in the main method:
public PropertyInfo[] GetPropertyInfos(object theObject)
{
// https://stackoverflow.com/questions/6536163/how-to-list-all-variables-of-class
BindingFlags bindingFlags = BindingFlags.Instance |
BindingFlags.NonPublic |
BindingFlags.Public;
// Type theType = this.GetType();
Type theType = theObject.GetType();
PropertyInfo[] propertyInfos = theType.GetProperties(bindingFlags);
return propertyInfos;
}
public FieldInfo[] GetFieldInfos(object theObject)
{
// https://stackoverflow.com/questions/6536163/how-to-list-all-variables-of-class
BindingFlags bindingFlags = BindingFlags.Instance |
BindingFlags.NonPublic |
BindingFlags.Public;
// Type theType = this.GetType();
Type theType = theObject.GetType();
FieldInfo[] fieldInfos = theType.GetFields(bindingFlags);
return fieldInfos;
}
And in a static class of extensions:
public static bool IsXmlSerializable(this Type testType)
{
if (testType.IsSerializable)
return true;
// just for good measure
var interfaces = testType.GetInterfaces().ToList();
if (interfaces.Contains(typeof(IXmlSerializable)))
return true;
return false;
}
So
- Is this approach sound?
- Can it be improved or abbreviated?
- Has someone else already published a better solution?