【问题标题】:Serializing an array of multiple types using XmlSerializer使用 XmlSerializer 序列化多个类型的数组
【发布时间】:2015-02-11 19:07:03
【问题描述】:

我正在尝试使用 XMLSerializer 生成如下 XML,其中 <create> 的内容是一个数组,但元素可以是不同的类型(在本例中为 <vendor><customer><asset>)。这可能吗?

...
    <create>
        <vendor> 
            <vendorid>Unit - A-1212</vendorid>
            <name>this is the name8</name>
            <vcf_bill_siteid3>FOOBAR8</vcf_bill_siteid3>
        </vendor>             
        <customer>
            <CUSTOMERID>XML121</CUSTOMERID>
            <NAME>XML Customer 111</NAME>
        </customer>             
        <asset>  
            <createdAt>San Jose</createdAt>
            <createdBy>Kevin</createdBy>
            <serial_number>123456789</serial_number>
        </asset> 
    </create>
....

【问题讨论】:

标签: c# xmlserializer


【解决方案1】:

假设数组中所有可能的类型在编译时都是已知的,您可以为数组中可能出现的每个已知类型应用多个[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 中以完成实际工作。不会为无法静态发现的类型创建代码,因此(反)序列化将失败。

您有两个选项可以解决此限制:

  1. 在运行时发现列表中的所有派生类型,然后为多态数组属性构造XmlAttributeOverridesadd XmlAttributes,然后用发现的子类型为数组属性填写XmlArrayItems。然后将XmlAttributeOverrides 传递给相应的XmlSerializer constructor

    注意 - 您必须在适当的哈希表中缓存和重用XmlSerializer,否则您将有巨大的资源泄漏。见here

    有关如何完成此操作的示例,请参见此处:Force XML serialization of XmlDefaultValue values

  2. 在运行时发现列表中的所有派生类型,然后将其存储在实现IXmlSerializable 的自定义List&lt;T&gt; 子类中。

由于必须缓存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; } }
}

【讨论】:

  • 这可能是我唯一的选择。我希望有一种更方便的方法,因为有很多可能的类型。我特别尝试生成 xml 以与此处记录的 API 对话:developer.intacct.com/wiki/create。有效的对象类型在这里:developer.intacct.com/wiki/functions-object - 如您所见,有数百个。
  • 如果这是必要的,我可能会使用反射编写自己的序列化程序。到目前为止,我对 XmlSerializer 很不满意。不过,我会将您的回答标记为已接受。
【解决方案2】:

在你的 c# 类中,只要确保你总是返回一个可能返回的任何可能类型的空数组:

    [Serializable]
public class create
{
    public create()
    {
        vendor = new Vendor[0];
        customer = new Customer[0];
        asset = new Asset[0];
    }
    Vendor[] vendor { get; set; }
    Customer[] customer { get; set; }
    Asset[] asset { get; set; }
}
[Serializable]
public class Vendor
{
    public string vendorid { get; set; }
    public string name { get; set; }
    public string vcf_bill_siteid3 { get; set; }
}
[Serializable]
public class Customer
{
    public string CUSTOMERID { get; set; }
    public string NAME { get; set; }
}
[Serializable]
public class Asset
{
    public string createdAt { get; set; }
    public string createdBy { get; set; }
    public string serial_number { get; set; }
}

【讨论】:

  • 正如我在下面的评论中所说,有很多可能的类型,所以这个解决方案虽然有效,但并不理想。
【解决方案3】:

我最终放弃了 XmlSerializer 并使用 Json.NET 将对象序列化为 json,然后再转换为 XML。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2020-12-09
    • 1970-01-01
    • 1970-01-01
    • 2012-01-02
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多