我可以使用自定义属性创建 XmlAttributeOverrides 吗?
Can I create XmlAttributeOverrides using a custom attribute?
我正在尝试在名为 GenericSerializable
的自定义属性后面用 C# 实现基本序列化的抽象。本质上,我希望这个属性在应用于 public 属性 时向某些序列化程序(无论是 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
属性添加到 SerializableObjectChildTwo
中的 List
属性,它的行为符合预期。问题是由于某种原因,尽管我添加了 attrbs.XmlIgnore = true
,但 Test
正在被序列化,而 TypeIndicator
没有被序列化,尽管我已将它显式添加到 [=] 21=].
我是否错误地使用了覆盖?我不需要任何花哨的 XML 模式或任何东西,我只希望 public 属性根据我的自定义 属性.[= 的存在与否进行序列化 serialized/not 22=]
提前致谢。
我找到了一个按预期工作的解决方案。
这一行:
overrides.Add(typeof(SerializableObjectChildTwo), property.Name, attrbs);
应该是:
overrides.Add(property.DeclaringType, property.Name, attrbs);
区别在于作为第一个参数提供的类型。感谢@dbc 为我指明了正确的方向。
你这里有几个问题:
使用 XmlAttributeOverrides.Add (Type, String, XmlAttributes)
为 属性 添加覆盖时,传入的 type
必须是 声明类型 对于 属性,不是被序列化的派生类型。
例如要在序列化 SerializableObjectChildTwo
时抑制 Test
,type
必须是 SerializableObjectChildOne
。
属性TypeIndicator
没有序列化,因为它没有publicsetter。如 Why are properties without a setter not serialized 中所述,在大多数情况下 成员必须 public 可读可写才能使用 XmlSerializer
.
也就是说,只获取集合 属性 可以 由 XmlSerializer
序列化。这在 Introducing XML Serialization:
中有解释,尽管不清楚
XML serialization does not convert methods, indexers, private fields, or read-only properties (except read-only collections). To serialize all an object's fields and properties, both public and private, use the DataContractSerializer instead of XML serialization.
(这里的read-only collection实际上是指read-only, pre-allocated collection 属性.)
这解释了为什么 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 fiddle here.
我正在尝试在名为 GenericSerializable
的自定义属性后面用 C# 实现基本序列化的抽象。本质上,我希望这个属性在应用于 public 属性 时向某些序列化程序(无论是 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
属性添加到 SerializableObjectChildTwo
中的 List
属性,它的行为符合预期。问题是由于某种原因,尽管我添加了 attrbs.XmlIgnore = true
,但 Test
正在被序列化,而 TypeIndicator
没有被序列化,尽管我已将它显式添加到 [=] 21=].
我是否错误地使用了覆盖?我不需要任何花哨的 XML 模式或任何东西,我只希望 public 属性根据我的自定义 属性.[= 的存在与否进行序列化 serialized/not 22=]
提前致谢。
我找到了一个按预期工作的解决方案。
这一行:
overrides.Add(typeof(SerializableObjectChildTwo), property.Name, attrbs);
应该是:
overrides.Add(property.DeclaringType, property.Name, attrbs);
区别在于作为第一个参数提供的类型。感谢@dbc 为我指明了正确的方向。
你这里有几个问题:
使用
XmlAttributeOverrides.Add (Type, String, XmlAttributes)
为 属性 添加覆盖时,传入的type
必须是 声明类型 对于 属性,不是被序列化的派生类型。例如要在序列化
SerializableObjectChildTwo
时抑制Test
,type
必须是SerializableObjectChildOne
。属性
TypeIndicator
没有序列化,因为它没有publicsetter。如 Why are properties without a setter not serialized 中所述,在大多数情况下 成员必须 public 可读可写才能使用XmlSerializer
.也就是说,只获取集合 属性 可以 由
中有解释,尽管不清楚XmlSerializer
序列化。这在 Introducing XML Serialization:XML serialization does not convert methods, indexers, private fields, or read-only properties (except read-only collections). To serialize all an object's fields and properties, both public and private, use the DataContractSerializer instead of XML serialization.
(这里的read-only collection实际上是指read-only, pre-allocated collection 属性.)
这解释了为什么
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 fiddle here.