You could iterate through all the assemblies in your app domain, find all types assignable from your base type, and pass them as known types when constructing your XmlSerializer
. However, some caveats:
- Assemblies are loaded on demand so you cannot perform the search just once, and
- The
XmlSerializer
, once constructed, must be cached in a hash table or dictionary and reused to prevent memory and resource leaks. See here for details.
Thus, you could do something like:
public static class XmlSerializerWithKnownTypeCreator<T>
{
static Dictionary<HashSet<Type>, XmlSerializer> table = new Dictionary<HashSet<Type>, XmlSerializer>(HashSet<Type>.CreateSetComparer());
public static XmlSerializer CreateKnownTypeSerializer<TRoot>()
{
return CreateKnownTypeSerializer(new Type [] {typeof(TRoot)});
}
public static XmlSerializer CreateKnownTypeSerializer(IEnumerable<Type> baseTypes)
{
var set = new HashSet<Type>(
AppDomain.CurrentDomain.GetAssemblies()
.SelectMany(a => a.GetTypes())
.Where(t => baseTypes.Any(baseType => baseType.IsAssignableFrom(t))));
lock (table)
{
XmlSerializer serializer;
if (table.TryGetValue(set, out serializer))
return serializer;
table[set] = serializer = new XmlSerializer(typeof(T), set.ToArray());
return serializer;
}
}
}
And call it like:
var serializer = XmlSerializerWithKnownTypeCreator<DocumentRoot>.CreateKnownTypeSerializer<BaseMove>();
For instance,
var serializer = XmlSerializerWithKnownTypeCreator<List<BaseClass>>.CreateKnownTypeSerializer<BaseMove>();
Update
If the parameterized generic static table of serializers looks weird (and to be fair, it kind of does), you can store the serializers in a non-generic global hash table, as is suggested in the documentation:
If you use any of the other constructors, multiple versions of the same assembly are generated and never unloaded, which results in a memory leak and poor performance. The easiest solution is to use one of the previously mentioned two constructors. Otherwise, you must cache the assemblies in a Hashtable, as shown in the following example.
However, the doc sort of glosses over how to generate a key for the XmlSerializer
. The code sample also isn't thread-safe, perhaps since all this stuff dates back to .Net 1.0. So here's some logic that will correctly key and recycle an XmlSerializer
with known extra types, in a global hash table:
public abstract class XmlserializerKey
{
readonly Type serializerType;
public XmlserializerKey(Type serializerType)
{
this.serializerType = serializerType;
}
protected Type SerializerType { get { return serializerType; } }
public override bool Equals(object obj)
{
if (ReferenceEquals(this, obj))
return true;
else if (ReferenceEquals(null, obj))
return false;
if (GetType() != obj.GetType())
return false;
XmlserializerKey other = (XmlserializerKey)obj;
if (other.serializerType != serializerType)
return false;
return true;
}
public override int GetHashCode()
{
int code = 0;
if (serializerType != null)
code ^= serializerType.GetHashCode();
return code;
}
public override string ToString()
{
return string.Format("Serializer type: " + serializerType.ToString());
}
}
public abstract class XmlserializerKeyWithExtraTypes : XmlserializerKey
{
static IEqualityComparer<HashSet<Type>> comparer;
readonly HashSet<Type> moreTypes = new HashSet<Type>();
static XmlserializerKeyWithExtraTypes()
{
comparer = HashSet<Type>.CreateSetComparer();
}
public XmlserializerKeyWithExtraTypes(Type serializerType, IEnumerable<Type> extraTypes)
: base(serializerType)
{
if (extraTypes != null)
foreach (var type in extraTypes)
moreTypes.Add(type);
}
protected Type[] MoreTypes { get { return moreTypes.ToArray(); } }
public override bool Equals(object obj)
{
if (!base.Equals(obj))
return false;
XmlserializerKeyWithExtraTypes other = (XmlserializerKeyWithExtraTypes)obj;
return comparer.Equals(moreTypes, other.moreTypes);
}
public override int GetHashCode()
{
int code = base.GetHashCode();
if (moreTypes != null)
code ^= comparer.GetHashCode(moreTypes);
return code;
}
}
public sealed class XmlSerializerKeyWithKnownTypes : XmlserializerKeyWithExtraTypes
{
public XmlSerializerKeyWithKnownTypes(Type serializerType, IEnumerable<Type> otherTypes)
: base(serializerType, otherTypes)
{
}
public XmlSerializer CreateSerializer()
{
return new XmlSerializer(SerializerType, MoreTypes);
}
}
public static class XmlSerializerHashTable
{
static Dictionary<object, XmlSerializer> dict;
static XmlSerializerHashTable()
{
dict = new Dictionary<object, XmlSerializer>();
}
public static XmlSerializer DemandSerializer(object key, Func<object, XmlSerializer> factory)
{
lock (dict)
{
XmlSerializer value;
if (!dict.TryGetValue(key, out value))
dict[key] = value = factory(key);
return value;
}
}
}
public static class XmlSerializerWithKnownDerivedTypesCreator
{
public static XmlSerializer CreateKnownTypeSerializer(Type type, IEnumerable<Type> extraTypes)
{
var allExtraTypes =
AppDomain.CurrentDomain.GetAssemblies()
.SelectMany(a => a.GetTypes())
.Where(t => extraTypes.Any(extraType => extraType.IsAssignableFrom(t)));
var key = new XmlSerializerKeyWithKnownTypes(type, allExtraTypes);
return XmlSerializerHashTable.DemandSerializer(key, k => ((XmlSerializerKeyWithKnownTypes)k).CreateSerializer());
}
}
And then call it in the same way as you would call the equivalent XmlSerializer
constructor:
public static void Test2()
{
List<BaseClass> list = new List<BaseClass>();
list.Add(new BaseClass());
list.Add(new MidClass());
list.Add(new DerivedClass1());
list.Add(new DerivedClass2());
var serializer = XmlSerializerWithKnownDerivedTypesCreator.CreateKnownTypeSerializer(list.GetType(), new Type[] { typeof(BaseClass) });
string xml = XmlSerializationHelper.GetXml(list, serializer, false);
Debug.WriteLine(xml);
// No assert below:
Debug.Assert(object.ReferenceEquals(serializer, XmlSerializerWithKnownDerivedTypesCreator.CreateKnownTypeSerializer(list.GetType(), new Type[] { typeof(BaseClass) })));
}