【问题标题】:XML serialization of list of class containing a System.Enum field causing crash包含 System.Enum 字段的类列表的 XML 序列化导致崩溃
【发布时间】:2015-10-06 20:25:20
【问题描述】:

我有一个包含两个枚举成员的自定义类型。我正在尝试创建此自定义类型的列表,然后将该列表序列化为 XML。当我尝试这样做时,Unity(我在 Unity 工作)崩溃了,这是我以前从未做过的。

我已经从我的序列化代码中将问题缩小到这个范围:

xmls.Serialize(stream, dataCollection);

因此,当 XMLSerializer 尝试序列化我的列表时,就会出现问题。我不知道为什么!所以任何帮助将不胜感激。

代码如下。

XML 序列化代码

public static void WriteGenericCollectionToXML<T>(T dataCollection, string filePath) where T : IEnumerable, ICollection
    {
        //Check to see if file already exists
        if(!File.Exists(filePath))
        {
            //if not, create it
            File.Create(filePath);
        }

        try
        {
            XmlSerializer xmls = new XmlSerializer(typeof(T));
            using(Stream stream = new FileStream(filePath, FileMode.Append))
            {
                xmls.Serialize(stream, dataCollection);
            }
        }
        catch(Exception e)
        {
            Debug.LogException(e);
        }
    }

创建自定义类型对象列表的代码

public  List<BlockType> t = new List<BlockType>();

        t.Add(new BlockType(true));
        t.Add(new BlockType(true));
        t.Add(new BlockType(true));

        SaveLoad.WriteGenericCollectionToXML(t, Application.dataPath + "/test.xml");

自定义类型

using UnityEngine;
using System.Collections;
using System.Xml;
using System.Xml.Serialization;

public enum BaseBlockType
{
    [XmlEnum(Name = "Animals")]Animals,
    [XmlEnum(Name = "Geometry")]Geometry,
    [XmlEnum(Name = "Letters")]Letters
}

public enum LetterBlockType
{
    [XmlEnum(Name = "A")]A,
    [XmlEnum(Name = "B")]B,
    [XmlEnum(Name = "C")]C,
    [XmlEnum(Name = "D")]Z,
    [XmlEnum(Name = "E")]X,
    [XmlEnum(Name = "F")]F,
    [XmlEnum(Name = "G")]G
}

public enum AnimalType
{
    [XmlEnum(Name = "Elephant")]Elephant,
    [XmlEnum(Name = "Giraffe")]Giraffe,
    [XmlEnum(Name = "Tiger")]Tiger,
    [XmlEnum(Name = "Sheep")]Sheep
}

public enum GeometaryType
{
    [XmlEnum(Name = "Square")]Square,
    [XmlEnum(Name = "Triangle")]Triangle,
    [XmlEnum(Name = "Circle")]Circle,
    [XmlEnum(Name = "Star")]Star
}

[XmlType]
public class BlockType
{
    [XmlAttribute]
    public BaseBlockType baseType;
    [XmlAttribute]
    public System.Enum subType;

    public BlockType(BaseBlockType baseT, System.Enum subT)
    {
        //Set base type
        baseType = baseT;

        //Set sub type
        subType = subT;
        //possibly need to perform checks that sub-type is acceptable
    }

    public BlockType(BaseBlockType baseT)
    {
        //Set base type
        baseType = baseT;

        //Set sub type
        subType = RandSubType(baseType);
    }

    public BlockType(bool random)
    {
        if(random)
        {
            //Set base type
            int enumLength = System.Enum.GetValues(typeof(BaseBlockType)).Length;
            int rand = Random.Range(0, enumLength);
            baseType = (BaseBlockType)rand;

            //Set sub type
            subType = RandSubType(baseType);
        }   
    }

    public BlockType()
    {}

    public System.Enum RandSubType(BaseBlockType baseType)
    {
        int subEnumLength;
        int subRand;

        switch(baseType)
        {
        case BaseBlockType.Animals:
            subEnumLength = System.Enum.GetValues(typeof(AnimalType)).Length;
            subRand = Random.Range(0, subEnumLength);
            return (AnimalType)subRand;
        case BaseBlockType.Geometry:
            subEnumLength = System.Enum.GetValues(typeof(GeometaryType)).Length;
            subRand = Random.Range(0, subEnumLength);
            return(GeometaryType)subRand;
        case BaseBlockType.Letters:
            subEnumLength = System.Enum.GetValues(typeof(LetterBlockType)).Length;
            subRand = Random.Range(0, subEnumLength);
            return (LetterBlockType)subRand;
        default:
            Debug.Log("Block Sub Type Selection not working");
            return null;
        }
    }

    public override string ToString ()
    {
        string result = baseType.ToString() + "." + subType.ToString();
        return result;
    }

    public override bool Equals (object t)
    {
        var test = t as BlockType;

        if(t == null)
            return false;

        return(test.baseType == this.baseType && test.subType == this.subType);
    }

    public override int GetHashCode ()
    {
        return this.GetHashCode ();
    }
}

【问题讨论】:

  • 围绕序列化的 try/catch - 它写入的异常是什么?如果没有异常,请在代码的入口点周围放置一个 try/catch 并查看它的内容
  • 刚刚尝试将问题行包含在 try catch 中,但它仍然使 Unity 崩溃!很烦人。
  • 试试 AppDomain.CurrentDomain.UnhandledException += (s,e)=>Console.WriteLine(e);
  • 我敢打赌 System.Enum subType; 会窒息,因为 System.Enum 是抽象的。
  • 我认为你是对的。我首先尝试将该成员标记为 {XmlIgnore],但这不起作用 - 仍然崩溃,但我随后将该成员从类中删除并且它起作用了。问题是,我需要那个成员!关于如何解决这个问题的任何想法?我想我可能不得不重新组织我的枚举方式以避免使用 System.Enum。

标签: c# xml serialization enums


【解决方案1】:

这里有两个不相关的问题:

  • 你有一个无限递归

    public override int GetHashCode()
    {
        return this.GetHashCode(); // Fix Me
    }
    

    显然XmlSerializer 使用了哈希码,所以你需要解决这个问题,这应该很容易。

  • 您正在尝试直接序列化 System.Enum subType; 类型的 System.Enum 对象。不幸的是,这种类型是抽象的,所以你不能直接序列化它。您将需要向 XML 添加更多信息,即正在序列化的 enum 类型。

我建议通过添加以下包装类来封装枚举类型来做到这一点:

public sealed class XmlEnumWrapper
{
    System.Enum value;
    Type type;

    [XmlAttribute("enumType")]
    public string XmlEnumType
    {
        get
        {
            if (Type == null)
                return null;
            return Type.AssemblyQualifiedName;
        }
        set
        {
            if (String.IsNullOrWhiteSpace(value))
            {
                Type = null;
            }
            else
            {
                Type = Type.GetType(value);
            }
        }
    }

    public abstract class EnumWraperBase
    {
        public abstract System.Enum BaseValue { get; }
    }

    [XmlRoot("Wrapper")]
    public sealed class InnerEnumWraper<T> : EnumWraperBase
    {
        public InnerEnumWraper() { }

        public InnerEnumWraper(T value) { this.Value = value; }

        public T Value { get; set; }

        public override Enum BaseValue { get { return (System.Enum)(object)Value; } }
    }

    [XmlText]
    public string XmlValue
    {
        get
        {
            if (Value == null)
                return null;
            var wrapper = Activator.CreateInstance(typeof(InnerEnumWraper<>).MakeGenericType(Type), new object[] { Value });
            // Handle [XmlEnum(Name = "XXX")] attributes applied to enum values by making a nested call to XmlSerializer
            return (string)wrapper.SerializeToXElement().Element("Value");
        }
        set
        {
            if (String.IsNullOrWhiteSpace(value))
            {
                Value = null;
            }
            else if (Type == null)
            {
                throw new InvalidOperationException("Type was not set");
            }
            else
            {
                var xelement = new XElement("Wrapper", new XElement("Value", value.Trim()));
                var wrapper = (EnumWraperBase)xelement.Deserialize(typeof(InnerEnumWraper<>).MakeGenericType(Type));
                Value = (wrapper == null ? null : wrapper.BaseValue);
            }
        }
    }

    [XmlIgnore]
    public System.Enum Value
    {
        get
        {
            return value;
        }
        set
        {
            this.value = value;
            if (value != null)
                type = value.GetType();
        }
    }

    [XmlIgnore]
    public Type Type
    {
        get
        {
            return type;
        }
        set
        {
            if (value != null)
            {
                if (!value.IsEnum || value.IsAbstract)
                    throw new ArgumentException();
            }
            this.type = value;
            if (this.value != null && this.type != this.value.GetType())
                this.value = null;
        }
    }

    public override string ToString()
    {
        return value == null ? "" : value.ToString();
    }
}

public static class XmlExtensions
{
    public static T LoadFromXML<T>(this string xmlString)
    {
        T returnValue = default(T);

        using (StringReader reader = new StringReader(xmlString))
        {
            object result = new XmlSerializer(typeof(T)).Deserialize(reader);
            if (result is T)
            {
                returnValue = (T)result;
            }
        }
        return returnValue;
    }

    public static string GetXml(this object obj)
    {
        using (var textWriter = new StringWriter())
        {
            var settings = new XmlWriterSettings() { Indent = true, IndentChars = "    " }; // For cosmetic purposes.
            using (var xmlWriter = XmlWriter.Create(textWriter, settings))
                new XmlSerializer(obj.GetType()).Serialize(xmlWriter, obj);
            return textWriter.ToString();
        }
    }

    public static object Deserialize(this XContainer element, Type type)
    {
        using (var reader = element.CreateReader())
        {
            return new XmlSerializer(type).Deserialize(reader);
        }
    }

    public static XElement SerializeToXElement<T>(this T obj)
    {
        var doc = new XDocument();
        using (var writer = doc.CreateWriter())
            new XmlSerializer(obj.GetType()).Serialize(writer, obj);
        var element = doc.Root;
        if (element != null)
            element.Remove();
        return element;
    }
}

然后,如下修改您的类,修复无限递归,忽略subType 字段,并将public XmlEnumWrapper XmlSubTypeWrapper 属性序列化为Element

[XmlType]
public class BlockType
{
    [XmlAttribute]
    public BaseBlockType baseType;

    [XmlIgnore]
    public System.Enum subType;

    [XmlElement("subType")]
    public XmlEnumWrapper XmlSubTypeWrapper
    {
        get
        {
            return (subType == null ? null : new XmlEnumWrapper { Value = subType });
        }
        set
        {
            subType = (value == null ? null : value.Value);
        }
    }

    public BlockType(BaseBlockType baseT, System.Enum subT)
    {
        //Set base type
        baseType = baseT;

        //Set sub type
        subType = subT;
        //possibly need to perform checks that sub-type is acceptable
    }

    public BlockType(BaseBlockType baseT)
    {
        //Set base type
        baseType = baseT;

        //Set sub type
        subType = RandSubType(baseType);
    }

    public BlockType(bool random)
    {
        if (random)
        {
            //Set base type
            int enumLength = System.Enum.GetValues(typeof(BaseBlockType)).Length;
            int rand = Random.Range(0, enumLength);
            baseType = (BaseBlockType)rand;

            //Set sub type
            subType = RandSubType(baseType);
        }
    }

    public BlockType()
    { }

    public System.Enum RandSubType(BaseBlockType baseType)
    {
        int subEnumLength;
        int subRand;

        switch (baseType)
        {
            case BaseBlockType.Animals:
                subEnumLength = System.Enum.GetValues(typeof(AnimalType)).Length;
                subRand = Random.Range(0, subEnumLength);
                return (AnimalType)subRand;
            case BaseBlockType.Geometry:
                subEnumLength = System.Enum.GetValues(typeof(GeometaryType)).Length;
                subRand = Random.Range(0, subEnumLength);
                return (GeometaryType)subRand;
            case BaseBlockType.Letters:
                subEnumLength = System.Enum.GetValues(typeof(LetterBlockType)).Length;
                subRand = Random.Range(0, subEnumLength);
                return (LetterBlockType)subRand;
            default:
                Debug.WriteLine("Block Sub Type Selection not working");
                return null;
        }
    }

    public override string ToString()
    {
        string result = baseType.ToString() + "." + subType.ToString();
        return result;
    }

    public override bool Equals(object t)
    {
        var test = t as BlockType;

        if (t == null)
            return false;

        return (test.baseType == this.baseType && test.subType == this.subType);
    }

    public override int GetHashCode()
    {
        var code1 = baseType.GetHashCode();
        var code2 = subType == null ? 0 : subType.GetHashCode();
        return unchecked(~code1 ^ (7 + code2 << 3));
    }
}

完成后,您的 XML 现在将如下所示:

<ArrayOfBlockType xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
    <BlockType baseType="Letters">
        <subType enumType="LetterBlockType, euj3revx, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null">LetterBlockTypeC</subType>
    </BlockType>
    <BlockType baseType="Geometry">
        <subType enumType="GeometaryType, euj3revx, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null">Triangle</subType>
    </BlockType>
    <BlockType baseType="Animals">
        <subType enumType="AnimalType, euj3revx, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null">Tiger</subType>
    </BlockType>
</ArrayOfBlockType>

原型fiddle.

【讨论】:

  • 谢谢。不敢相信我错过了 GetHashCode()。我在 element.Remove() 行的 SerializeToXElement 扩展方法中遇到错误:::: System.InvalidOperationException: 生成 XML 文档时出错。 ---> System.InvalidOperationException: Parent is missing
  • @MatthewSawrey - 我在 .Net 上没有收到该错误。您可以删除 if (element != null) element.Remove(); 逻辑,这是一个小的可选内存使用减少。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2019-10-08
  • 1970-01-01
  • 1970-01-01
  • 2013-09-08
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多