Я пытаюсь реализовать абстракцию для базовой сериализации на C# за настраиваемым атрибутом, называемым GenericSerializable. По сути, я хочу, чтобы этот атрибут при применении к общедоступному свойству указывал некоторому сериализатору (будь то 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.
Я неправильно использую переопределения? Мне не нужны какие-либо причудливые схемы XML или что-то еще, я просто хочу, чтобы общедоступные свойства были сериализованы / не сериализованы в зависимости от наличия или отсутствия моего настраиваемого свойства.
Заранее спасибо.
Между прочим, вам нужно кэшировать и повторно использовать сериализатор, как описано в Утечка памяти с использованием StreamReader и XmlSerializer.
Я действительно знаю о необходимости кеширования, я просто не включил это в этот пример. Сеттер необходим, так что спасибо, что поймали это. Однако проблема с XmlAttributeOverrides все еще существует: несмотря на то, что я установил attrbs.XmlIgnore = true, свойство все еще сериализуется.





Я нашел решение, которое работает как положено.
Эта строка:
overrides.Add(typeof(SerializableObjectChildTwo), property.Name, attrbs);
Должно быть:
overrides.Add(property.DeclaringType, property.Name, attrbs);
Разница в том, что тип указан в качестве первого параметра. Спасибо @dbc за то, что указал мне в правильном направлении.
Здесь у вас есть несколько проблем:
При добавлении переопределений для свойства с использованием XmlAttributeOverrides.Add (Type, String, XmlAttributes) переданный type должен быть объявление типа для свойства, а не сериализуемым производным типом.
Например. чтобы подавить Test при сериализации SerializableObjectChildTwo, type должен быть SerializableObjectChildOne.
Свойство TypeIndicator не сериализуется, поскольку у него нет общедоступного установщика. Как объяснено в Почему свойства без сеттера не сериализуются, в большинстве случаев, член должен быть доступен для чтения и записи, чтобы его можно было сериализовать с помощью XmlSerializer.
При этом свойство коллекции только для получения может сериализуется с помощью XmlSerializer. Это объясняется, хотя и неясно, в Введение в сериализацию XML:
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.
(Здесь коллекция только для чтения фактически означает доступное только для чтения, предварительно выделенное свойство коллекции.)
Это объясняет, почему свойство List сериализуется, несмотря на то, что оно доступно только для получения.
Вы должны кэшировать сериализатор, чтобы избежать утечки памяти, как описано в Утечка памяти с использованием StreamReader и 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 рабочий пример здесь.
Свойство
TypeIndicatorне будет сериализовано, потому что у него нет установщика, а также получателя, как объяснено в Почему свойства без сеттера не сериализуются. НоListсериализуется, потому что это предварительно выделенная коллекция, и это ограничение не существует для свойств коллекции только для получения, как объяснено в этот ответ - Для реализации IXmlSerializable требуется свойство коллекции иметь сеттер.