包含System.Enum字段的类列表的XML序列化导致崩溃 [英] XML serialization of list of class containing a System.Enum field causing crash

查看:74
本文介绍了包含System.Enum字段的类列表的XML序列化导致崩溃的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

I have a custom type with two enum members. I'm trying to create a list of this custom type, then serialize that list to XML. When I try and do this, Unity (I'm working in Unity) crashes, which it's never done on me before.

我已将问题缩小到序列化代码中:

I've narrowed the issue down to this from my serialization code:

xmls.Serialize(stream, dataCollection);

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

So the issue poccurs when the XMLSerializer attempts to serialize my List. I have no idea why though! So any help would be appreciated.

下面的代码。

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 ();
    }
}


推荐答案

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

You have two unrelated problems here:


  • 您在

  • You have an infinite recursion in

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

显然 XmlSerializer 使用哈希码处理某些事情,因此您需要解决此问题,这应该很容易。

Apparently XmlSerializer uses the hash code for something, so you need to fix this, which should be easy.

您正尝试直接序列化对象 System.Enum子类型; 类型为 System.Enum 。不幸的是,这种类型是抽象的,因此您不能直接对其进行序列化。您将需要向XML添加更多信息,即要序列化的 enum 类型。

You are trying to directly serialize an object System.Enum subType; of type System.Enum. Unfortunately, this type is abstract, so you can't serialize this directly. You're going to need to add more information to your XML, namely the enum type being serialized.

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

I would suggest doing this by adding the following wrapper class to encapsulate the enum type:

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

Then, modify your class as follows, fixing the infinite recursion, ignoring the subType field, and serializing instead a public XmlEnumWrapper XmlSubTypeWrapper property as an 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现在看起来像:

Having done so, your XML will now look like:


<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>


原型小提琴

这篇关于包含System.Enum字段的类列表的XML序列化导致崩溃的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆