当反序列化使用包含空格的值的EnumMemberAttribute修饰的标记枚举时,将抛出SerializationException。值中的空格被视为分隔符。
有没有办法更改分隔符或将值放在引号中?或者是否有更简单的解决方案?
我正在考虑的选项是:
但我真的觉得这应该是可配置的东西或其他人已经解决的东西。但我找不到任何东西。
我把问题归结为一个简单的单元测试。以下代码导致:
消息=无效的枚举值“测试”无法反序列化为“UnitTests.TestEnum”类型。确保存在必要的枚举值,并且如果类型具有DataContractAttribute属性,则使用EnumMemberAttribute属性标记。 源= System.Runtime.Serialization
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,
}
}
答案 0 :(得分:5)
您不能在值中使用空格,因为DataContractSerializer
使用它并且它是硬编码的。请参阅source和post。但是如果你真的想在单词之间使用空格,那么使用列出的解决方案之一:
第一种方式。在值中使用其他空格字符,例如每个空间三个空格。但是你会遇到另一个问题 - 值之间没有可视分隔符。
<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
属性替换它们,并使用生成的类的实例作为代理