Могу ли я создать XmlAttributeOverrides с помощью настраиваемого атрибута?

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

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

Свойство TypeIndicator не будет сериализовано, потому что у него нет установщика, а также получателя, как объяснено в Почему свойства без сеттера не сериализуются. Но List сериализуется, потому что это предварительно выделенная коллекция, и это ограничение не существует для свойств коллекции только для получения, как объяснено в этот ответ - Для реализации IXmlSerializable требуется свойство коллекции иметь сеттер.

dbc 09.06.2018 21:26

Между прочим, вам нужно кэшировать и повторно использовать сериализатор, как описано в Утечка памяти с использованием StreamReader и XmlSerializer.

dbc 09.06.2018 21:32

Я действительно знаю о необходимости кеширования, я просто не включил это в этот пример. Сеттер необходим, так что спасибо, что поймали это. Однако проблема с XmlAttributeOverrides все еще существует: несмотря на то, что я установил attrbs.XmlIgnore = true, свойство все еще сериализуется.

ms5991 09.06.2018 22:35
Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
1
3
405
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Я нашел решение, которое работает как положено.

Эта строка:

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

Должно быть:

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

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

Ответ принят как подходящий

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

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

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

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

  3. При этом свойство коллекции только для получения может сериализуется с помощью 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 сериализуется, несмотря на то, что оно доступно только для получения.

  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 рабочий пример здесь.

Другие вопросы по теме