我正在尝试在名为GenericSerializable
的自定义属性后面的C#中实现基本序列化的抽象。本质上,我希望这个属性在应用于公共属性时,向某些序列化程序(无论是XML,JSON,Protobuf等)指示该属性应该被序列化,如果它不存在则应该不被序列化。目前,我可以获得特定属性是否具有该属性的信息,但我正在努力实现XML序列化程序。这是我的测试继承结构:
public abstract class SerializableObjectBase
{
protected int _typeIndicator;
[GenericSerializable]
public int TypeIndicator
{
get
{
return _typeIndicator;
}
}
public SerializableObjectBase()
{
_typeIndicator = 0;
}
}
public class SerializableObjectChildOne : SerializableObjectBase
{
private int _test;
public int Test
{
get
{
return _test;
}
set
{
_test = value;
}
}
public SerializableObjectChildOne() : base()
{
_test = 1234;
_typeIndicator = 1;
}
}
public class SerializableObjectChildTwo : SerializableObjectChildOne
{
private List<int> _list;
public List<int> List
{
get
{
return _list;
}
}
public SerializableObjectChildTwo() : base()
{
_list = new List<int>();
_typeIndicator = 2;
}
}
我希望此示例的XML看起来像:
<?xml version="1.0" encoding="utf-8"?>
<SerializableObjectChildTwo xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<TypeIndicator>2</TypeIndicator>
</SerializableObjectChildTwo>
但它看起来像这样:
<?xml version="1.0" encoding="utf-8"?>
<SerializableObjectChildTwo xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<Test>1234</Test>
</SerializableObjectChildTwo>
这是序列化代码:
using (FileStream fs = new FileStream(".\\output.xml", FileMode.Create))
{
// object to serialize
SerializableObjectChildTwo s = new SerializableObjectChildTwo();
XmlAttributeOverrides overrides = new XmlAttributeOverrides();
// check whether each property has the custom attribute
foreach (PropertyInfo property in typeof(SerializableObjectChildTwo).GetProperties())
{
XmlAttributes attrbs = new XmlAttributes();
// if it has the attribute, tell the overrides to serialize this property
if (property.CustomAttributes.Any((attr) => attr.AttributeType.Equals(typeof(GenericSerializable))))
{
Console.WriteLine("Adding " + property.Name + "");
attrbs.XmlElements.Add(new XmlElementAttribute(property.Name));
}
else
{
// otherwise, ignore the property
Console.WriteLine("Ignoring " + property.Name + "");
attrbs.XmlIgnore = true;
}
// add this property to the list of overrides
overrides.Add(typeof(SerializableObjectChildTwo), property.Name, attrbs);
}
// create the serializer
XmlSerializer xml = new XmlSerializer(typeof(SerializableObjectChildTwo), overrides);
// serialize it
using (TextWriter tw = new StreamWriter(fs))
{
xml.Serialize(tw, s);
}
}
有趣的是,如果我将GenericSerializable
属性添加到List
中的SerializableObjectChildTwo
属性,则其行为与预期一致。问题在于,由于某些原因Test
已被序列化,尽管我添加了attrbs.XmlIgnore = true
,并且TypeIndicator
未被序列化,尽管我已将其明确添加到{XmlAttributeOverrides
1}}。
我是否错误地使用了覆盖?我不需要任何花哨的XML模式或任何东西,我只想根据我的自定义属性的存在与否来序列化/不序列化公共属性。
提前致谢。
答案 0 :(得分:1)
我找到了一个按预期工作的解决方案。
这一行:
overrides.Add(typeof(SerializableObjectChildTwo), property.Name, attrbs);
应该是:
overrides.Add(property.DeclaringType, property.Name, attrbs);
不同之处在于作为第一个参数提供的类型。感谢@dbc指出我正确的方向。
答案 1 :(得分:1)
这里有一些问题:
使用XmlAttributeOverrides.Add (Type, String, XmlAttributes)
为属性添加替换时,传入的type
必须是属性的声明类型,而不是序列化的派生类型。
E.g。在序列化Test
时要取消SerializableObjectChildTwo
,type
必须为SerializableObjectChildOne
。
属性TypeIndicator
未序列化,因为它没有公共设置器。如 Why are properties without a setter not serialized 中所述,在大多数情况下,成员必须是公开可读和可写的,才能使用XmlSerializer
序列化。
话虽如此,XmlSerializer
可以序列化只有get的集合属性 。在Introducing XML Serialization:
XML序列化不转换方法,索引器,私有字段或只读属性(只读集合除外)。要序列化所有对象的字段和属性(public和private),请使用DataContractSerializer而不是XML序列化。
(此处只读集合实际上是指只读,预先分配的集合属性。)
这解释了为什么List
属性被序列化,尽管只是get-only。
您应该缓存序列化程序以避免内存泄漏,如 Memory Leak using StreamReader and XmlSerializer 中所述。
将所有这些放在一起,您可以使用以下扩展方法为SerializableObjectChildTwo
构建序列化程序:
public static class SerializableObjectBaseExtensions
{
static readonly Dictionary<Type, XmlSerializer> serializers = new Dictionary<Type, XmlSerializer>();
static readonly object padlock = new object();
public static XmlSerializer GetSerializer<TSerializable>(TSerializable obj) where TSerializable : SerializableObjectBase, new()
{
return GetSerializer(obj == null ? typeof(TSerializable) : obj.GetType());
}
public static XmlSerializer GetSerializer<TSerializable>() where TSerializable : SerializableObjectBase, new()
{
return GetSerializer(typeof(TSerializable));
}
static XmlSerializer GetSerializer(Type serializableType)
{
lock (padlock)
{
XmlSerializer serializer;
if (!serializers.TryGetValue(serializableType, out serializer))
serializer = serializers[serializableType] = CreateSerializer(serializableType);
return serializer;
}
}
static XmlSerializer CreateSerializer(Type serializableType)
{
XmlAttributeOverrides overrides = new XmlAttributeOverrides();
for (var declaringType = serializableType; declaringType != null && declaringType != typeof(object); declaringType = declaringType.BaseType)
{
// check whether each property has the custom attribute
foreach (PropertyInfo property in declaringType.GetProperties(BindingFlags.Public | BindingFlags.DeclaredOnly | BindingFlags.Instance))
{
XmlAttributes attrbs = new XmlAttributes();
// if it has the attribute, tell the overrides to serialize this property
// property.IsDefined is faster than actually creating and returning the attribute
if (property.IsDefined(typeof(GenericSerializableAttribute), true))
{
Console.WriteLine("Adding " + property.Name + "");
attrbs.XmlElements.Add(new XmlElementAttribute(property.Name));
}
else
{
// otherwise, ignore the property
Console.WriteLine("Ignoring " + property.Name + "");
attrbs.XmlIgnore = true;
}
// add this property to the list of overrides
overrides.Add(declaringType, property.Name, attrbs);
}
}
// create the serializer
return new XmlSerializer(serializableType, overrides);
}
}
工作.Net小提琴here。