如何将枚举值序列化为整数?

84
我想把枚举值序列化为整数,但我只得到了名称。
这是我的(示例)类和枚举:
public class Request {
    public RequestType request;
}

public enum RequestType
{
    Booking = 1,
    Confirmation = 2,
    PreBooking = 4,
    PreBookingConfirmation = 5,
    BookingStatus = 6
}

以下是代码(只是为了确保我没有做错)

Request req = new Request();
req.request = RequestType.Confirmation;
XmlSerializer xml = new XmlSerializer(req.GetType());
StringWriter writer = new StringWriter();
xml.Serialize(writer, req);
textBox1.Text = writer.ToString();
这个答案(对另一个问题的回答)似乎表明枚举默认应该序列化为整数,但是它并没有这样做。这是我的输出结果:
<?xml version="1.0" encoding="utf-16"?>
<Request xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
  <request>Confirmation</request>
</Request>

我已经能够通过在每个值上放置"[XmlEnum("X")]"属性来将其序列化为值,但这似乎不太对。

7个回答

154

最简单的方法是使用 [XmlEnum] 属性,如下所示:

[Serializable]
public enum EnumToSerialize
{
    [XmlEnum("1")]
    One = 1,
    [XmlEnum("2")]
    Two = 2
}

这将序列化成XML格式(假设父类为CustomClass):

<CustomClass>
  <EnumValue>2</EnumValue>
</CustomClass>

5
这是否比被接受的答案更省工作还有待商榷。枚举值越多,维护工作就越多。 - youwhut
6
这也是我的首选方法——为什么要创建一个不必要的委托属性呢?只需添加几个额外的指令就可以使枚举正确地进行(反)序列化。 - CJM
1
除非您明确定义所有组合,否则不会发生。对于小标志枚举,这是一个选项。 - xr280xr
3
个人认为,相比被接受的答案,这种方式更加优雅。这样做还可以更容易地序列化使用枚举值作为键的字典。 - Alex
1
很棒的东西。我建议放弃 Serializable 属性,因为它对于 Xml 序列化并不必要。 - julealgon
显示剩余2条评论

75

很多时候人们需要的是名称而不是整数。你可以添加一个用于此目的的shim属性吗?

[XmlIgnore]
public MyEnum Foo {get;set;}

[XmlElement("Foo")]
[EditorBrowsable(EditorBrowsableState.Never), Browsable(false)]
public int FooInt32 {
    get {return (int)Foo;}
    set {Foo = (MyEnum)value;}
}

或者您可以使用 IXmlSerializable,但这需要做很多工作。


只是为了明确 - 这个代码片段告诉 XmlSerializer - 忽略 MyEnum 属性。并且序列化 FooInt32 属性,它只是将 MyEnum 属性强制转换为 Int32 值。这对您来说将完美地工作。 - Cheeso
当引用第三方库中的枚举时,这是一个不错的解决方案。 - CtrlDot
还需注意的是,FooInt32属性被序列化为名称Foo。我喜欢这个答案,因为如果您的枚举中有数十个值,则只需执行一次此操作。 - intrepidis
我也成功地在WebHTTP(JSON)服务中使用了它,唯一的变化是将[XmlElement("Foo")]属性替换为[DataMember(Name="Foo")]。 - Dan Parsonson

14
请查看下面的完整示例Console应用程序,以了解使用DataContractSerializer实现您要查找的内容的有趣方法:
using System;
using System.IO;
using System.Runtime.Serialization;

namespace ConsoleApplication1
{
    [DataContract(Namespace="petermcg.wordpress.com")]
    public class Request
    {
        [DataMember(EmitDefaultValue = false)]
        public RequestType request;
    }

    [DataContract(Namespace = "petermcg.wordpress.com")]
    public enum RequestType
    {
        [EnumMember(Value = "1")]
        Booking = 1,
        [EnumMember(Value = "2")]
        Confirmation = 2,
        [EnumMember(Value = "4")]
        PreBooking = 4,
        [EnumMember(Value = "5")]
        PreBookingConfirmation = 5,
        [EnumMember(Value = "6")]
        BookingStatus = 6
    }

    class Program
    {
        static void Main(string[] args)
        {
            DataContractSerializer serializer = new DataContractSerializer(typeof(Request));

            // Create Request object
            Request req = new Request();
            req.request = RequestType.Confirmation;

            // Serialize to File
            using (FileStream fileStream = new FileStream("request.txt", FileMode.Create))
            {
                serializer.WriteObject(fileStream, req);
            }

            // Reset for testing
            req = null;

            // Deserialize from File
            using (FileStream fileStream = new FileStream("request.txt", FileMode.Open))
            {
                req = serializer.ReadObject(fileStream) as Request;
            }

            // Writes True
            Console.WriteLine(req.request == RequestType.Confirmation);
        }
    }
}
调用WriteObject后,request.txt的内容如下:
<Request xmlns="petermcg.wordpress.com" xmlns:i="http://www.w3.org/2001/XMLSchema-instance">
    <request>2</request>
</Request>

你需要引用 System.Runtime.Serialization.dll 程序集才能使用 DataContractSerializer。


问题已经注明了[XmlEnum("...")]的用法,这是与[EnumMember("...")]相当的XmlSerializer等效方法 - 所以我不确定这是否会增加OP不知道的任何内容。 - Marc Gravell
1
此外,由于DataContractSerializer支持私有成员,更简单的方法是使用私有shim属性在int和枚举类型之间进行转换。 - Marc Gravell
是的,我注意到了关于XmlEnum的问题,谢谢。但正如我所说,我认为这是一个有趣的解决方案。哪个解决方案更“简单”是主观的,最终取决于提问者。同意:使用“shim”方法,DataContractSerializer及其对私有成员的支持是正确的选择。 - Peter McG
1
@MarcGravell, @Peter McGrattan:在这里使用[EnumMember(Value = "1")]有什么问题吗?或者我们应该像Marc G建议的那样始终使用“shim”属性? - VoodooChild

2
using System.Xml.Serialization;

public class Request
{    
    [XmlIgnore()]
    public RequestType request;

    public int RequestTypeValue
    {
      get 
      {
        return (int)request;
      } 
      set
      {
        request=(RequestType)value; 
      }
    }
}

public enum RequestType
{
    Booking = 1,
    Confirmation = 2,
    PreBooking = 4,
    PreBookingConfirmation = 5,
    BookingStatus = 6
}

上述方法对我有效。

1

对我来说,这些解决方案并不令人满意。我喜欢通用的解决方案,不需要在调整枚举值时进行调整。因此,我创建了以下解决方案,使用XmlAttributeOverrides

解决方案1

    public static XmlAttributeOverrides ReflectionAddXmlEnumAttributes(Type baseType, XmlAttributeOverrides overrides = null)
    {
        if (overrides == null) overrides = new XmlAttributeOverrides();
        // traversing all serializable members
        var filteredFields = baseType.GetFields()
            .Where(f =>
                (f.Attributes.HasFlag(FieldAttributes.Public) &&
                 !f.Attributes.HasFlag(FieldAttributes.Static) &&
                 !f.CustomAttributes.Any(
                     a => a.AttributeType.IsAssignableFrom(typeof(XmlIgnoreAttribute)))));
        var filteredProperties = baseType.GetProperties()
            .Where(f =>
                (f.GetMethod?.Attributes.HasFlag(MethodAttributes.Public) ?? false) &&
                !f.GetMethod.Attributes.HasFlag(MethodAttributes.Static) &&
                (f.SetMethod?.Attributes.HasFlag(MethodAttributes.Public) ?? false) &&
                !f.SetMethod.Attributes.HasFlag(MethodAttributes.Static) &&
                !f.CustomAttributes.Any(
                    a => a.AttributeType.IsAssignableFrom(typeof(XmlIgnoreAttribute))));

        var classMemberTypes = filteredFields.Select(f => f.FieldType)
            .Concat(filteredProperties.Select(p => p.PropertyType));

        foreach (var memberType in classMemberTypes)
        {
            // proceed the same way for sub members
            ReflectionAddXmlEnumAttributes(memberType, overrides);
            if (!memberType.IsEnum) continue;
            var enumFields = memberType.GetFields();
            foreach (var enumFieldInfo in enumFields)
            {
                if (!enumFieldInfo.IsLiteral) continue;
                // add attribute-overrides for every enum-literal
                var name = enumFieldInfo.Name;
                if (overrides[memberType, name] != null) continue;
                var integer = enumFieldInfo.GetRawConstantValue();
                var attribute = new XmlAttributes
                {
                    XmlEnum = new XmlEnumAttribute(integer.ToString()),
                };
                overrides.Add(memberType, name, attribute);
            }
        }
        return overrides;
    }

    public static T MyDeserialize<T>(string filePath)
    {
        var overrides = ReflectionAddXmlEnumAttributes(typeof(T));

        var serializer = new XmlSerializer(typeof(T), overrides);
        using (var fileStream = new FileStream(filePath, FileMode.Open, System.IO.FileAccess.Read))
        {
            var deserialized = serializer.Deserialize(fileStream);
            fileStream.Close();
            return (T) deserialized;
        }
    }

    public static void MySerialization<T>(T serializeObject, string filePath)
    {
        var overrides = ReflectionAddXmlEnumAttributes(typeof(T));
        var serializer = new XmlSerializer(typeof(T), overrides);
        using (var writer = new StreamWriter(filePath))
        {
            serializer.Serialize(writer, serializeObject);
            writer.Close();
        }
    }

对我而言,这种解决方案的缺点是它需要相当多的代码,并且只能处理数值类型。我正在寻找一种解决方案,可以处理一个枚举字面量的不同字符串,以使得可以接受数字表示和枚举名称。

解决方案2

由于我的第一个解决方案还不够长,我创建了另一个解决方案,因此我有了自己的序列化器类,可以用于接受各种序列化的枚举(包括字符串和数字解释)。

using System;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Security.Policy;
using System.Xml.Serialization;

namespace MyApp.Serializer
{
    public class XmlExtendedSerializer : XmlSerializer
    {
        public SerializerDirection Mode { get; }
        public XmlAttributeOverrides Overrides { get; }
        public Type BaseType { get; }
        public XmlExtendedSerializer(SerializerDirection mode, Type type, XmlAttributeOverrides overrides, Type[] extraTypes, XmlRootAttribute root, string defaultNamespace, bool serializeEnumsAsNumber = true) : base(type, AddXmlEnumAttributes(mode, type, out _, serializeEnumsAsNumber, overrides), extraTypes, root, defaultNamespace)
        {
            BaseType = type;
            Mode = mode;
            Overrides = overrides;
        }

        public XmlExtendedSerializer(SerializerDirection mode, Type type, XmlRootAttribute root, bool serializeEnumsAsNumber = true) : base(type, AddXmlEnumAttributes(mode, type, out var overrideCreated, serializeEnumsAsNumber), null, root, null)
        {
            BaseType = type;
            Mode = mode;
            Overrides = overrideCreated;
        }

        public XmlExtendedSerializer(SerializerDirection mode, Type type, Type[] extraTypes, bool serializeEnumsAsNumber = true) : base(type, AddXmlEnumAttributes(mode, type, out var overrideCreated, serializeEnumsAsNumber), extraTypes, null, null)
        {
            BaseType = type;
            Mode = mode;
            Overrides = overrideCreated;
        }

        public XmlExtendedSerializer(SerializerDirection mode, Type type, XmlAttributeOverrides overrides, bool serializeEnumsAsNumber = true) : base(type, AddXmlEnumAttributes(mode, type, out var overrideCreated, serializeEnumsAsNumber, overrides))
        {
            BaseType = type;
            Mode = mode;
            Overrides = overrideCreated;
        }

        public XmlExtendedSerializer(XmlTypeMapping xmlTypeMapping)
        {
            throw new NotImplementedException("This method is not supported by this wrapper.");
        }

        public XmlExtendedSerializer(SerializerDirection mode, Type type, bool serializeEnumsAsNumber = true) : base(type, AddXmlEnumAttributes(mode, type, out var overrideCreated, serializeEnumsAsNumber))
        {
            BaseType = type;
            Mode = mode;
            Overrides = overrideCreated;
        }

        public XmlExtendedSerializer(SerializerDirection mode, Type type, string defaultNamespace, bool serializeEnumsAsNumber = true) : base(type, AddXmlEnumAttributes(mode, type, out var overrideCreated, serializeEnumsAsNumber), null, null, defaultNamespace)
        {
            BaseType = type;
            Mode = mode;
            Overrides = overrideCreated;
        }

        public XmlExtendedSerializer(SerializerDirection mode, Type type, XmlAttributeOverrides overrides, Type[] extraTypes, XmlRootAttribute root, string defaultNamespace, string location, bool serializeEnumsAsNumber = true) : base(type, AddXmlEnumAttributes(mode, type, out var overrideCreated, serializeEnumsAsNumber, overrides), extraTypes, root, defaultNamespace, location)
        {
            BaseType = type;
            Mode = mode;
            Overrides = overrideCreated;
        }

        public XmlExtendedSerializer(SerializerDirection mode, Type type, XmlAttributeOverrides overrides, Type[] extraTypes, XmlRootAttribute root, string defaultNamespace, string location, Evidence evidence, bool serializeEnumsAsNumber = true) : base(type, AddXmlEnumAttributes(mode, type, out var overrideCreated, serializeEnumsAsNumber, overrides), extraTypes, root, defaultNamespace, location, evidence)
        {
            BaseType = type;
            Mode = mode;
            Overrides = overrideCreated;
        }

        public new object Deserialize(Stream stream)
        {
            if (Mode != SerializerDirection.Deserialize) throw new NotSupportedException("Wrong mode.");
            UnknownElement += ConvertEnumEvent;
            return base.Deserialize(stream);
        }

        public new void Serialize(TextWriter writer, object o)
        {
            if (Mode != SerializerDirection.Serialize) throw new NotSupportedException("Wrong mode.");
            base.Serialize(writer, o);
        }
        private static XmlAttributeOverrides AddXmlEnumAttributes(SerializerDirection mode, Type baseType, out XmlAttributeOverrides outOverrides, bool serializeEnumsAsNumber = true, XmlAttributeOverrides overrides = null)
        {
            if (overrides == null) overrides = new XmlAttributeOverrides();
            // traversing all serializable members
            var filteredFields = baseType.GetFields()
                .Where(f =>
                    (f.Attributes.HasFlag(FieldAttributes.Public) &&
                     !f.Attributes.HasFlag(FieldAttributes.Static) &&
                     !f.CustomAttributes.Any(
                         a => a.AttributeType.IsAssignableFrom(typeof(XmlIgnoreAttribute)))));
            var filteredProperties = baseType.GetProperties()
                .Where(f =>
                    (f.GetMethod?.Attributes.HasFlag(MethodAttributes.Public) ?? false) &&
                    !f.GetMethod.Attributes.HasFlag(MethodAttributes.Static) &&
                    (f.SetMethod?.Attributes.HasFlag(MethodAttributes.Public) ?? false) &&
                    !f.SetMethod.Attributes.HasFlag(MethodAttributes.Static) &&
                    !f.CustomAttributes.Any(
                        a => a.AttributeType.IsAssignableFrom(typeof(XmlIgnoreAttribute))));

            foreach (var member in filteredFields.Cast<object>().Concat(filteredProperties))
            {
                var memberType = (member as FieldInfo)?.FieldType ?? ((PropertyInfo) member).PropertyType;
                var name = (member as FieldInfo)?.Name ?? ((PropertyInfo)member).Name;

                // proceed the same way for sub members
                AddXmlEnumAttributes(mode, memberType, out _ , serializeEnumsAsNumber, overrides);
                var deepEnumType = Nullable.GetUnderlyingType(memberType);
                var isNullable = deepEnumType != null;
                if (!isNullable) deepEnumType = memberType;

                if (!deepEnumType.IsEnum) continue;
                if (mode == SerializerDirection.Deserialize) // set ignore for enums and mark them for our own deserializer
                {
                    var attributeIgnore = new XmlEnumConvertAttribute // with custom type to detect
                    {
                        XmlIgnore = true, // ignore all enums
                    };
                    overrides.Add(baseType, name, attributeIgnore);
                }
                else if (serializeEnumsAsNumber) // serialize as number
                {
                    var enumFields = deepEnumType.GetFields();
                    foreach (var enumFieldInfo in enumFields)
                    {
                        if (!enumFieldInfo.IsLiteral) continue;
                        // add attribute-overrides for every enum-literal
                        var literalName = enumFieldInfo.Name;
                        if (overrides[memberType, literalName] != null) continue;
                        var integer = enumFieldInfo.GetRawConstantValue();
                        var attribute = new XmlAttributes
                        {
                            XmlEnum = new XmlEnumAttribute(integer.ToString()) // sets the number as output value
                        };
                        overrides.Add(memberType, literalName, attribute);
                    }
                }
            }

            outOverrides = overrides;
            return overrides;
        }

        // will be triggered on unknown xml elements are detected (enums are now marked as not serializable so they are unknown)
        private void ConvertEnumEvent(object sender, XmlElementEventArgs e)
        {
            var memberName = e.Element.Name; // enum property/field name
            var targetObject = e.ObjectBeingDeserialized;
            var baseType = targetObject.GetType(); // type of including class
            if (!(Overrides[baseType, memberName] is XmlEnumConvertAttribute)) return; // tag is really unknown
            var text = e.Element.InnerText; // the value text from xml
            var member = baseType.GetField(memberName);
            var property = baseType.GetProperty(memberName);
            var enumType = member?.FieldType ?? property.PropertyType;
            enumType = Nullable.GetUnderlyingType(enumType) ?? enumType;
            var newValue = string.IsNullOrEmpty(text) ?
                null :
                Enum.Parse(enumType, text);  // enum parser accepts also string type and number-type
            property?.SetValue(targetObject, newValue);
            member?.SetValue(targetObject, newValue);
        }

        // custom type to detect on event, that this property was not ignored intentionally
        private class XmlEnumConvertAttribute : XmlAttributes { }
    }

    public enum SerializerDirection
    {
        Serialize,
        Deserialize
    }
}

使用类似的调用

var serializer = new XmlExtendedSerializer(SerializerDirection.Serialize, typeof(T));
using (var writer = new StreamWriter(path))
{
    serializer.Serialize(writer, objectToSerialize);
    writer.Close();
}

并且

var serializer = new XmlExtendedSerializer(SerializerDirection.Deserialize, typeof(T));
using (var fileStream = new FileStream(path, FileMode.Open, System.IO.FileAccess.Read))
{
    var tmpObj = serializer.Deserialize(fileStream);
    fileStream.Close();
    var deserializedObject = (T) tmpObj;
}

缺点包括:

  • 可空枚举始终序列化为字符串
  • 创建一个序列化器实例只能用于一种模式的序列化或反序列化,不能同时使用
  • 并非所有DeserializeSerialize的重载都被覆盖
  • 自定义类

0

看一下 System.Enum 类。Parse 方法将字符串或整数表示转换为 Enum 对象,ToString 方法将 Enum 对象转换为可序列化的字符串。


3
虽然这是正确的,但这并没有解决如何在序列化过程中透明地使用它,这才是真正的问题。 - Marc Gravell

0

由于你正在为枚举选项分配显式的非连续值,我假设你想一次指定多个值(二进制标志),那么被接受的答案是你唯一的选择。传递PreBooking|PreBookingConfirmation将具有整数值9,并且序列化程序无法反序列化它,但使用一个代理属性进行强制转换将可以很好地解决问题。或者也许你只是错过了第3个值:)


2
我刚刚发现了一个方便的枚举类型 [Flags],它允许您使用位标志,并将它们序列化为正确的选项... - Adriaan Davel

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接