假设数组中所有可能的类型在编译时都是已知的,您可以为数组中可能出现的每个已知类型应用多个[XmlArrayItem(String, Type)] 属性。 Type 参数是可以出现在数组中的特定派生类型,而 String 参数是与该类型关联的元素名称。还将[XmlArray(String)] 属性应用于整个数组属性,以指定数组的名称,并且将其序列化为两级而不是一级。
例如:
public class Document
{
[XmlArray("create")]
[XmlArrayItem("vendor", typeof(Vendor))]
[XmlArrayItem("customer", typeof(Customer))]
[XmlArrayItem("asset", typeof(Asset))]
public CreateBase [] Create { get; set; }
}
在哪里
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; }
}
(使用抽象基类型只是我的偏好。您可以使用object 作为基类型:public object [] Create { get; set; })
更新
使用XmlSerializer 序列化包含在编译时未知的派生类型的多态集合很困难,因为它通过动态代码生成工作。 IE。当您第一次创建 XmlSerializer 时,它使用反射编写 c# 代码来序列化和反序列化所有静态可发现的引用类型,然后编译并将该代码加载到动态 DLL 中以完成实际工作。不会为无法静态发现的类型创建代码,因此(反)序列化将失败。
您有两个选项可以解决此限制:
-
在运行时发现列表中的所有派生类型,然后为多态数组属性构造XmlAttributeOverrides、add XmlAttributes,然后用发现的子类型为数组属性填写XmlArrayItems。然后将XmlAttributeOverrides 传递给相应的XmlSerializer constructor。
注意 - 您必须在适当的哈希表中缓存和重用XmlSerializer,否则您将有巨大的资源泄漏。见here。
有关如何完成此操作的示例,请参见此处:Force XML serialization of XmlDefaultValue values。
在运行时发现列表中的所有派生类型,然后将其存储在实现IXmlSerializable 的自定义List<T> 子类中。
由于必须缓存XmlSerializer,我倾向于第二种方法。
发现所有派生类型:
public static class TypeExtensions
{
public static IEnumerable<Type> DerivedTypes(this IEnumerable<Type> 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<Assembly> 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<Assembly> 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<string>();
var stack = new Stack<Assembly>();
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<Assembly> 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<Assembly>();
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<Assembly> GetReferencingAssembliesAndSelf(this Assembly target)
{
return new[] { target }.Concat(target.GetReferencingAssemblies());
}
}
然后,发现自己的类型的多态列表:
public class XmlPolymorphicList<T> : List<T>, 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<object>");
}
}
internal sealed class DerivedTypeDictionary
{
Dictionary<Type, string> derivedTypeNames;
Dictionary<string, Type> derivedTypes;
DerivedTypeDictionary()
{
derivedTypeNames = typeof(T).DerivedTypes().ToDictionary(t => t, t => t.DefaultXmlElementName());
derivedTypes = derivedTypeNames.ToDictionary(p => p.Value, p => p.Key); // Will throw an exception if names are not unique
}
public static DerivedTypeDictionary Instance { get { return Singleton<DerivedTypeDictionary>.Instance; } }
public string GetName(Type type)
{
return derivedTypeNames[type];
}
public Type GetType(string name)
{
return derivedTypes[name];
}
}
public XmlPolymorphicList()
: base()
{
}
public XmlPolymorphicList(IEnumerable<T> items)
: base(items)
{
}
#region IXmlSerializable Members
XmlSchema IXmlSerializable.GetSchema()
{
return null;
}
void IXmlSerializable.ReadXml(XmlReader reader)
{
reader.ReadStartElement();
while (reader.NodeType == XmlNodeType.Element)
{
var name = reader.Name;
var type = DerivedTypeDictionary.Instance.GetType(name);
var item = (T)(new XmlSerializer(type).Deserialize(reader));
if (item != null)
Add(item);
}
}
void IXmlSerializable.WriteXml(XmlWriter writer)
{
foreach (var item in this)
{
new XmlSerializer(item.GetType()).Serialize(writer, item);
}
}
#endregion
}
public static class XmlSerializationHelper
{
public static string DefaultXmlElementName(this Type type)
{
var xmlType = type.GetCustomAttribute<XmlTypeAttribute>();
if (xmlType != null && !string.IsNullOrEmpty(xmlType.TypeName))
return xmlType.TypeName;
return type.Name;
}
}
public class Singleton<T> where T : class
{
// Explicit static constructor to tell C# compiler
// not to mark type as beforefieldinit
static Singleton()
{
}
/// <summary>
/// Private nested class which acts as singleton class instantiator. This class should not be accessible outside <see cref="Singleton<T>"/>
/// </summary>
class Nested
{
/// <summary>
/// Explicit static constructor to tell C# compiler not to mark type as beforefieldinit
/// </summary>
static Nested()
{
}
/// <summary>
/// Static instance variable
/// </summary>
internal static readonly T instance = (T)Activator.CreateInstance(typeof(T), true);
}
public static T Instance { get { return Nested.instance; } }
}