对我来说,这些解决方案并不令人满意。我喜欢通用的解决方案,不需要在调整枚举值时进行调整。因此,我创建了以下解决方案,使用XmlAttributeOverrides
。
解决方案1
public static XmlAttributeOverrides ReflectionAddXmlEnumAttributes(Type baseType, XmlAttributeOverrides overrides = null)
{
if (overrides == null) overrides = new XmlAttributeOverrides();
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)
{
ReflectionAddXmlEnumAttributes(memberType, overrides);
if (!memberType.IsEnum) continue;
var enumFields = memberType.GetFields();
foreach (var enumFieldInfo in enumFields)
{
if (!enumFieldInfo.IsLiteral) continue;
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();
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;
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)
{
var attributeIgnore = new XmlEnumConvertAttribute
{
XmlIgnore = true,
};
overrides.Add(baseType, name, attributeIgnore);
}
else if (serializeEnumsAsNumber)
{
var enumFields = deepEnumType.GetFields();
foreach (var enumFieldInfo in enumFields)
{
if (!enumFieldInfo.IsLiteral) continue;
var literalName = enumFieldInfo.Name;
if (overrides[memberType, literalName] != null) continue;
var integer = enumFieldInfo.GetRawConstantValue();
var attribute = new XmlAttributes
{
XmlEnum = new XmlEnumAttribute(integer.ToString())
};
overrides.Add(memberType, literalName, attribute);
}
}
}
outOverrides = overrides;
return overrides;
}
private void ConvertEnumEvent(object sender, XmlElementEventArgs e)
{
var memberName = e.Element.Name;
var targetObject = e.ObjectBeingDeserialized;
var baseType = targetObject.GetType();
if (!(Overrides[baseType, memberName] is XmlEnumConvertAttribute)) return;
var text = e.Element.InnerText;
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);
property?.SetValue(targetObject, newValue);
member?.SetValue(targetObject, newValue);
}
private class XmlEnumConvertAttribute : XmlAttributes { }
}
public enum SerializerDirection
{
Serialize,
Deserialize
}
}
使用类似的调用
var serializer = new XmlExtendedSerializer(SerializerDirection.Serialize, typeof(T));
using (var writer = new StreamWriter(path))
并且
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;
}
缺点包括:
- 可空枚举始终序列化为字符串
- 创建一个序列化器实例只能用于一种模式的序列化或反序列化,不能同时使用
- 并非所有
Deserialize
和Serialize
的重载都被覆盖
- 自定义类
Serializable
属性,因为它对于 Xml 序列化并不必要。 - julealgon