Могу ли я создать XmlAttributeOverrides, используя пользовательский атрибут? - PullRequest
0 голосов
/ 09 июня 2018

Я пытаюсь реализовать абстракцию для базовой сериализации в 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 или что-либо еще, я просто хочу, чтобы публичные свойства сериализировались / не сериализировались на основе наличия или отсутствия моего пользовательского свойства.

Заранее спасибо.

Ответы [ 2 ]

0 голосов
/ 10 июня 2018

У вас есть несколько проблем здесь:

  1. При добавлении переопределений для свойства с помощью XmlAttributeOverrides.Add (Type, String, XmlAttributes) переданное type должно быть объявляя тип для свойства, а не сериализуемый производный тип.

    Например, для подавления Test при сериализации SerializableObjectChildTwo type должно быть SerializableObjectChildOne.

  2. Свойство TypeIndicator не сериализовано, поскольку оно не имеет общедоступного установщика.Как объясняется в Почему свойства без установщика не сериализуются , , в большинстве случаев элемент должен быть доступен для чтения и записи для сериализации с XmlSerializer.

  3. При этом свойство сбора только для получения может быть сериализовано XmlSerializer.Это объясняется, хотя и неясно, в Введение в сериализацию XML :

    В сериализации XML не конвертируются методы, индексаторы, закрытые поля или readтолько свойства (кроме коллекций только для чтения) .Чтобы сериализовать все поля и свойства объекта, как публичные, так и частные, используйте DataContractSerializer вместо XML-сериализации.

    (Здесь коллекция только для чтения фактически означает read-только предварительно выделенное свойство коллекции .)

    Это объясняет, почему свойство List сериализуется, несмотря на то, что оно доступно только для получения.

  4. Вам следует кэшироватьСериализатор, чтобы избежать утечки памяти, как описано в Утечка памяти с использованием 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 fiddle здесь .

0 голосов
/ 10 июня 2018

Я нашел решение, которое работает должным образом.

Эта строка:

 overrides.Add(typeof(SerializableObjectChildTwo), property.Name, attrbs);

Должно быть:

 overrides.Add(property.DeclaringType, property.Name, attrbs);

Разница заключается в типе, указанном в качествеПервый параметрСпасибо @dbc за то, что указал мне правильное направление.

...