使用空格对标记的枚举进行反序列化会导致SerializationException



当对标记的枚举进行反序列化时,会引发SerializationException,该标记的枚举由值包含空格的EnumMemberAttribute修饰。值中的空格被视为分隔符。

有没有办法更改分隔符或将值放在引号中?或者还有更简单的解决方案吗

我已经在考虑的选项是:

  • 用此枚举类型的列表替换标记的枚举
  • 用下划线替换空格
  • 这是在WCF服务中使用的,我请注意,有些人认为数据契约中的枚举是一件坏事。因此,我也在考虑将枚举全部丢失

但我真的觉得这应该是可配置的,或者是其他人已经解决的。但我什么也找不到。

我把这个问题归结为一个简单的单元测试。下面的代码导致:

消息=无法将无效的枚举值"Test"反序列化为类型"UnitTests"。TestEnum"。如果类型具有DataContractAttribute属性,请确保存在必要的枚举值,并使用EnumMemberAttribute属性进行标记。来源=系统。运行时。序列化

using System;
using System.IO;
using System.Runtime.Serialization;
using System.Xml;
using FluentAssertions;
using Microsoft.VisualStudio.TestTools.UnitTesting;
namespace UnitTests
{
    [TestClass]
    public class EnumSerizalizationTests
    {
        [TestMethod]
        public void SerializingAndDesrializingAFlaggedEnumShouldResultInSameEnumValues()
        {
            //Arrange
            var orgObject = new TestClass { Value = TestEnum.TestValue1 | TestEnum.TestValue2 };
            //Act
            var temp = DataContractSerializeObject(orgObject);
            var newObject = DataContractDeSerializeObject<TestClass>(temp);
            //Assert
            newObject.ShouldBeEquivalentTo(orgObject, "Roundtripping serialization should result in same value");
        }
        public string DataContractSerializeObject<T>(T objectToSerialize)
        {
            using (var output = new StringWriter())
            {
                using (var writer = new XmlTextWriter(output) {Formatting = Formatting.Indented})
                {
                    new DataContractSerializer(typeof (T)).WriteObject(writer, objectToSerialize);
                    return output.GetStringBuilder().ToString();
                }
            }
        }
        public T DataContractDeSerializeObject<T>(string stringToDeSerialize)
        {
            DataContractSerializer ser = new DataContractSerializer(typeof(T));
            T result;
            using (StringReader stringReader = new StringReader(stringToDeSerialize))
            {
                using (XmlReader xmlReader = XmlReader.Create(stringReader))
                {
                    result = (T)ser.ReadObject(xmlReader);
                }
            }
            return result;
        }
    }
    [DataContract]
    [KnownType(typeof(TestEnum))]
    public class TestClass
    {
        [DataMember]
        public TestEnum Value { get; set; }
    }
    [Flags]
    [DataContract]
    public enum TestEnum
    {
        [EnumMember(Value = "Test value one")]
        TestValue1 = 1,
        [EnumMember(Value = "Test value two")]
        TestValue2 = 2,
        [EnumMember]
        TestValue3 = 4,
        [EnumMember]
        TestValue4 = 8,
    }

}

不能在值中使用空格,因为DataContractSerializer使用空格并且它是硬编码的。请参阅来源和文章。但是,如果你真的想在单词之间使用空格,那么就使用列出的解决方案之一:

第一种方式在值中使用其他空白字符,例如每个em三个空格。但你会遇到另一个问题——值之间没有视觉分隔符。

<TestClass xmlns:i="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://schemas.datacontract.org/2004/07/ConsoleApplication">
  <Value>Test value one Test value two</Value>
</TestClass>

第二种方法是使用IDataContractSurrogate。这种方式将生成下面列出的XML:

<TestClass xmlns:i="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://schemas.datacontract.org/2004/07/ConsoleApplication">
  <Value i:type="EnumValueOfTestEnum9cBcd6LT">Test value one, Test value two</Value>
</TestClass>

它是如何工作的?我们将在序列化过程中包装枚举,在反序列化的情况下打开包装。为了做到这一点,我们应该使用IDataContractSurrogate:

new DataContractSerializerSettings()
{
    DataContractSurrogate = new EnumSurrogate(),
    KnownTypes = new Type[] { typeof(EnumValue<TestEnum>) }
};
public class EnumSurrogate : IDataContractSurrogate
{
    #region IDataContractSurrogate Members
    public object GetCustomDataToExport(Type clrType, Type dataContractType)
    {
        return null;
    }
    public object GetCustomDataToExport(MemberInfo memberInfo, Type dataContractType)
    {
        return null;
    }
    public Type GetDataContractType(Type type)
    {
        return type;
    }
    public object GetDeserializedObject(object obj, Type targetType)
    {
        IEnumValue enumValue = obj as IEnumValue;
        if (enumValue!= null)
        { return enumValue.Value; }
        return obj;
    }
    public void GetKnownCustomDataTypes(Collection<Type> customDataTypes)
    {
    }
    public object GetObjectToSerialize(object obj, Type targetType)
    {
        if (obj != null)
        {
            Type type = obj.GetType();
            if (type.IsEnum && Attribute.IsDefined(type, typeof(FlagsAttribute)))
            { return Activator.CreateInstance(typeof(EnumValue<>).MakeGenericType(type), obj); }
        }
        return obj;
    }
    public Type GetReferencedTypeOnImport(string typeName, string typeNamespace, object customData)
    {
        return null;
    }
    public CodeTypeDeclaration ProcessImportedType(CodeTypeDeclaration typeDeclaration, CodeCompileUnit compileUnit)
    {
        return null;
    }
    #endregion
}
public interface IEnumValue : IXmlSerializable
{
    object Value { get; }
}
[Serializable]
public class EnumValue<T> : IEnumValue
    where T : struct
{
    #region Fields
    private Enum value;
    private static Type enumType;
    private static long[] values;
    private static string[] names;
    private static bool isULong;
    #endregion
    #region Constructors
    static EnumValue()
    {
        enumType = typeof(T);
        if (!enumType.IsEnum)
        { throw new InvalidOperationException(); }
        FieldInfo[] fieldInfos = enumType.GetFields(BindingFlags.Static | BindingFlags.Public);
        values = new long[fieldInfos.Length];
        names = new string[fieldInfos.Length];
        isULong = Enum.GetUnderlyingType(enumType) == typeof(ulong);
        for (int i = 0; i < fieldInfos.Length; i++)
        {
            FieldInfo fieldInfo = fieldInfos[i];
            EnumMemberAttribute enumMemberAttribute = (EnumMemberAttribute)fieldInfo
                .GetCustomAttributes(typeof(EnumMemberAttribute), false)
                .FirstOrDefault();
            IConvertible value = (IConvertible)fieldInfo.GetValue(null);
            values[i] = (isULong)
                ? (long)value.ToUInt64(null)
                : value.ToInt64(null);
            names[i] = (enumMemberAttribute == null || string.IsNullOrEmpty(enumMemberAttribute.Value))
                ? fieldInfo.Name
                : enumMemberAttribute.Value;
        }
    }
    public EnumValue()
    {
    }
    public EnumValue(Enum value)
    {
        this.value = value;
    }
    #endregion
    #region IXmlSerializable Members
    public XmlSchema GetSchema()
    {
        return null;
    }
    public void ReadXml(XmlReader reader)
    {
        string stringValue = reader.ReadElementContentAsString();
        long longValue = 0;
        int i = 0;
        // Skip initial spaces
        for (; i < stringValue.Length && stringValue[i] == ' '; i++) ;
        // Read comma-delimited values
        int startIndex = i;
        int nonSpaceIndex = i;
        int count = 0;
        for (; i < stringValue.Length; i++)
        {
            if (stringValue[i] == ',')
            {
                count = nonSpaceIndex - startIndex + 1;
                if (count > 1)
                { longValue |= ReadEnumValue(stringValue, startIndex, count); }
                nonSpaceIndex = ++i;
                // Skip spaces
                for (; i < stringValue.Length && stringValue[i] == ' '; i++) ;
                startIndex = i;
                if (i == stringValue.Length)
                { break; }
            }
            else
            {
                if (stringValue[i] != ' ')
                { nonSpaceIndex = i; }
            }
        }
        count = nonSpaceIndex - startIndex + 1;
        if (count > 1)
            longValue |= ReadEnumValue(stringValue, startIndex, count);
        value = (isULong)
            ? (Enum)Enum.ToObject(enumType, (ulong)longValue)
            : (Enum)Enum.ToObject(enumType, longValue);
    }
    public void WriteXml(XmlWriter writer)
    {
        long longValue = (isULong)
            ? (long)((IConvertible)value).ToUInt64(null)
            : ((IConvertible)value).ToInt64(null);
        int zeroIndex = -1;
        bool noneWritten = true;
        for (int i = 0; i < values.Length; i++)
        {
            long current = values[i];
            if (current == 0)
            {
                zeroIndex = i;
                continue;
            }
            if (longValue == 0)
            { break; }
            if ((current & longValue) == current)
            {
                if (noneWritten)
                { noneWritten = false; }
                else
                { writer.WriteString(","); }
                writer.WriteString(names[i]);
                longValue &= ~current;
            }
        }
        if (longValue != 0)
        { throw new InvalidOperationException(); }
        if (noneWritten && zeroIndex >= 0)
        { writer.WriteString(names[zeroIndex]); }
    }
    #endregion
    #region IEnumValue Members
    public object Value
    {
        get { return value; }
    }
    #endregion
    #region Private Methods
    private static long ReadEnumValue(string value, int index, int count)
    {
        for (int i = 0; i < names.Length; i++)
        {
            string name = names[i];
            if (count == name.Length && string.CompareOrdinal(value, index, name, 0, count) == 0)
            { return values[i]; }
        }
        throw new InvalidOperationException();
    }
    #endregion
}

第三种方法是动态发出类,如果基类已标记Enum属性,则用string属性替换它们,并使用生成类的实例作为代理。

最新更新