I\'m trying to use XMLSerializer to generate XML such as the following, where the contents of is an array, but the elements can be of differing t
Assuming all possible types in the array are known at compile time, you can apply multiple [XmlArrayItem(String, Type)] attributes to the array for each known type that could appear in the array. The Type argument is a specific derived type that could appear in the array while the String argument is the element name to associate with that type. Also apply the [XmlArray(String)] attribute to the overall array property to specify the name of the array and that it is to be serialized in two levels rather than one.
E.g.:
public class Document
{
[XmlArray("create")]
[XmlArrayItem("vendor", typeof(Vendor))]
[XmlArrayItem("customer", typeof(Customer))]
[XmlArrayItem("asset", typeof(Asset))]
public CreateBase [] Create { get; set; }
}
Where
public abstract class CreateBase
{
}
public class Vendor : CreateBase
{
public string vendorid { get; set; }
public string name { get; set; }
public string vcf_bill_siteid3 { get; set; }
}
public class Customer : CreateBase
{
public string CUSTOMERID { get; set; }
public string NAME { get; set; }
}
public class Asset : CreateBase
{
public string createdAt { get; set; }
public string createdBy { get; set; }
public string serial_number { get; set; }
}
(Using an abstract base type is just my preference. You could use object as you base type: public object [] Create { get; set; })
Update
Serializing polymorphic collections containing derived types not known at compile time is difficult with XmlSerializer because it works through dynamic code generation. I.e. when you create an XmlSerializer for the first time it uses reflection to write c# code to serialize and deserialize all statically discoverable referenced types, then compiles and loads that code into a dynamic DLL to do the actual work. No code gets created for types that cannot be discovered statically, so (de)serialization will fail for them.
You have two options to work around this limitation:
Discover all derived types in the list at runtime, then construct an XmlAttributeOverrides, add XmlAttributes for the polymorphic array property, then fill in the XmlArrayItems for the array property with the discovered subtypes. Then pass the XmlAttributeOverrides to the appropriate XmlSerializer constructor.
Note - you must cache and reuse the XmlSerializer in an appropriate hash table or you will have an enormous resource leak. See here.
For an example of how this might be done, see here: Force XML serialization of XmlDefaultValue values.
Discover all derived types in the list at runtime, then store then in a custom List subclass that implements IXmlSerializable.
Due to the nuisance of having to cache the XmlSerializer, I lean towards the second approach.
To discover all derived types:
public static class TypeExtensions
{
public static IEnumerable DerivedTypes(this IEnumerable baseTypes)
{
var assemblies = baseTypes.SelectMany(t => t.Assembly.GetReferencingAssembliesAndSelf()).Distinct();
return assemblies
.SelectMany(a => a.GetTypes())
.Where(t => baseTypes.Any(baseType => baseType.IsAssignableFrom(t)))
.Distinct();
}
}
public static class AssemblyExtensions
{
public static IEnumerable GetAllAssemblies()
{
// Adapted from
// https://stackoverflow.com/questions/851248/c-sharp-reflection-get-all-active-assemblies-in-a-solution
return Assembly.GetEntryAssembly().GetAllReferencedAssemblies();
}
public static IEnumerable GetAllReferencedAssemblies(this Assembly root)
{
// WARNING: Assembly.GetAllReferencedAssemblies() will optimize away any reference if there
// is not an explicit use of a type in that assembly from the referring assembly --
// And simply adding an attribute like [XmlInclude(typeof(T))] seems not to do
// the trick. See
// https://social.msdn.microsoft.com/Forums/vstudio/en-US/17f89058-5780-48c5-a43a-dbb4edab43ed/getreferencedassemblies-not-returning-complete-list?forum=netfxbcl
// Thus if you are using this to, say, discover all derived types of a base type, the assembly
// of the derived types MUST contain at least one type that is referenced explicitly from the
// root assembly, directly or indirectly.
var list = new HashSet();
var stack = new Stack();
stack.Push(root);
do
{
var asm = stack.Pop();
yield return asm;
foreach (var reference in asm.GetReferencedAssemblies())
if (!list.Contains(reference.FullName))
{
stack.Push(Assembly.Load(reference));
list.Add(reference.FullName);
}
}
while (stack.Count > 0);
}
public static IEnumerable GetReferencingAssemblies(this Assembly target)
{
if (target == null)
throw new ArgumentNullException();
// Assemblies can have circular references:
// http://stackoverflow.com/questions/1316518/how-did-microsoft-create-assemblies-that-have-circular-references
// So a naive algorithm isn't going to work.
var done = new HashSet();
var root = Assembly.GetEntryAssembly();
var allAssemblies = root.GetAllReferencedAssemblies().ToList();
foreach (var assembly in GetAllAssemblies())
{
if (target == assembly)
continue;
if (done.Contains(assembly))
continue;
var refersTo = (assembly == root ? allAssemblies : assembly.GetAllReferencedAssemblies()).Contains(target);
done.Add(assembly);
if (refersTo)
yield return assembly;
}
}
public static IEnumerable GetReferencingAssembliesAndSelf(this Assembly target)
{
return new[] { target }.Concat(target.GetReferencingAssemblies());
}
}
Then, the polymorphic list that discovers its own types:
public class XmlPolymorphicList : List, IXmlSerializable where T : class
{
static XmlPolymorphicList()
{
// Make sure the scope of objects to find isn't *EVERYTHING*
if (typeof(T) == typeof(object))
{
throw new InvalidOperationException("Cannot create a XmlPolymorphicList