Сериализует список объектов, наследуемых от класса A до xml, поэтому имена элементов в xml - B, C, D - PullRequest
0 голосов
/ 22 мая 2018

Использование DataContractSerializer Я хочу сериализовать список объектов, которые наследуются от класса А. Эти объекты находятся в другой сборке, и, скажем, они имеют классы B, C и D. Я добавил B, C и D визвестные типы данных контракта сериализатора.Мне удалось сериализовать список, но результат сериализации выглядит следующим образом:

<SerializedListObjects>
   <A i:type="B">
   <A i:type="C">
</SerializedListObjects>

То, что я хочу, это:

<SerializedListObjects>
   <B>
   <C>
</SerializedListObjects>

Возможно, в B может быть какой-то атрибутC с информацией, которую они наследуют от A.

Это мой базовый класс:

  [Serializable]
  [DataContract(Name = "A")]
  public abstract class A
  {
  }

И это пример определения производного класса.

  [Serializable]
  [DataContract(Name = "B")]
  public class B : A
  {
  }

Поскольку производные классы находятся в другой сборке, я не могу поместить какой-либо атрибут в их базовый класс или сериализованный класс, который будет содержать имя производного класса (например, [XmlElement("B", Type = typeof(ChildB))]) - у меня нет доступа к производномузанятия там.

Возможно ли это?

Хотя в настоящее время я использую DataContractSerializer, я готов переключиться на другой XML-сериализатор, такой как XmlSerializer, если необходимо.

1 Ответ

0 голосов
/ 25 мая 2018

Во-первых, DataContractSerializer не имеет механизма поддержки полиморфизма элементов коллекции путем изменения имен элементов коллекции.Он поддерживает только механизм известного типа , в котором используется атрибут i:type, который, как вы указываете, недопустим.

, поскольку вы готовы перейти на XmlSerializerВы можете использовать атрибут XmlArrayItemAttribute.Type, чтобы указать имена элементов для полиморфных типов в списках:

public class AListObject
{
    [XmlArrayItem(typeof(B))]
    [XmlArrayItem(typeof(C))]
    public List<A> SerializedListObjects { get; set; }
}

Однако вы также указываете, что полиморфные подтипы не могут быть объявлены статически при типе компиляциипотому что они существуют в какой-то другой сборке.

В результате вам нужно будет использовать механизм XmlAttributeOverrides, чтобы указать все возможные производные типы для всех свойств List<A> во время выполнения, и вручную создать XmlSerializer с использованием этих переопределений.

Вот прототип решения.Во-первых, давайте предположим, что у вас есть корневой объект, который ссылается на объект, содержащий List<A>, например:

public class RootObject
{
    public AListObject AList { get; set; }
}

public class AListObject
{
    public List<A> SerializedListObjects { get; set; }
}

(Корневой объект может быть объектом со свойством List<A>, но недолжно быть.) Давайте также предположим, что вы знаете все такие объекты, как AListObject, которые могут содержать List<A> свойств.

С этими допущениями можно использовать следующую фабрику сериализатора для генерации XmlSerializer для любого корневого объекта, который может ссылаться на любые экземпляры известных типов, содержащие свойство List<A>:

public interface IXmlSerializerFactory
{
    XmlSerializer CreateSerializer(Type rootType);
}

public static class AListSerializerFactory
{
    static readonly XmlArrayItemTypeOverrideSerializerFactory instance;

    static AListSerializerFactory()
    {
        // Include here a list of all types that have a List<A> property.
        // You could use reflection to find all such public types in your assemblies.
        var declaringTypeList = new []
        {
            typeof(AListObject),
        };

        // Include here a list of all base types with a corresponding mapping 
        // to find all derived types in runtime.   Here you could use reflection
        // to find all such types in your assemblies, as shown in 
        // /818101/poluchit-vse-proizvodnye-tipy-tipa
        var derivedTypesList = new Dictionary<Type,  Func<IEnumerable<Type>>>
        {
            { typeof(A), () => new [] { typeof(B), typeof(C) } },
        };
        instance = new XmlArrayItemTypeOverrideSerializerFactory(declaringTypeList, derivedTypesList);
    }

    public static IXmlSerializerFactory Instance { get { return instance; } }
}

public class XmlArrayItemTypeOverrideSerializerFactory : IXmlSerializerFactory
{
    // To avoid a memory & resource leak, the serializers must be cached as explained in
    // https://stackoverflow.com/questions/23897145/memory-leak-using-streamreader-and-xmlserializer

    readonly object padlock = new object();
    readonly Dictionary<Type, XmlSerializer> serializers = new Dictionary<Type, XmlSerializer>();
    readonly XmlAttributeOverrides overrides;

    public XmlArrayItemTypeOverrideSerializerFactory(IEnumerable<Type> declaringTypeList, IEnumerable<KeyValuePair<Type, Func<IEnumerable<Type>>>> derivedTypesList)
    {
        var completed = new HashSet<Type>();
        overrides = declaringTypeList
            .SelectMany(d => derivedTypesList.Select(p => new { declaringType = d, itemType = p.Key, derivedTypes = p.Value() }))
            .Aggregate(new XmlAttributeOverrides(), (a, d) => a.AddXmlArrayItemTypes(d.declaringType, d.itemType, d.derivedTypes, completed));
    }

    public XmlSerializer CreateSerializer(Type rootType)
    {
        lock (padlock)
        {
            XmlSerializer serializer;
            if (!serializers.TryGetValue(rootType, out serializer))
                serializers[rootType] = serializer = new XmlSerializer(rootType, overrides);
            return serializer;
        }
    }
}

public static partial class XmlAttributeOverridesExtensions
{
    public static XmlAttributeOverrides AddXmlArrayItemTypes(this XmlAttributeOverrides overrides, Type declaringType, Type itemType, IEnumerable<Type> derivedTypes)
    {
        return overrides.AddXmlArrayItemTypes(declaringType, itemType, derivedTypes, new HashSet<Type>());
    }

    public static XmlAttributeOverrides AddXmlArrayItemTypes(this XmlAttributeOverrides overrides, Type declaringType, Type itemType, IEnumerable<Type> derivedTypes, HashSet<Type> completedTypes)
    {
        if (overrides == null || declaringType == null || itemType == null || derivedTypes == null || completedTypes == null)
            throw new ArgumentNullException();
        XmlAttributes attributes = null;
        for (; declaringType != null && declaringType != typeof(object); declaringType = declaringType.BaseType)
        {
            // Avoid duplicate overrides.
            if (!completedTypes.Add(declaringType))
                break;
            foreach (var property in declaringType.GetProperties(BindingFlags.Public | BindingFlags.DeclaredOnly | BindingFlags.Instance))
            {
                // Skip the property if already ignored
                if (property.IsDefined(typeof(XmlIgnoreAttribute), false))
                    continue;

                // See if it is a list property, and if so, get its type.
                var propertyItemType = property.PropertyType.GetListType();
                if (propertyItemType == null)
                    continue;

                // OK, its a List<itemType>.  Add all the necessary XmlElementAttribute declarations.
                if (propertyItemType == itemType)
                {
                    if (attributes == null)
                    {
                        attributes = new XmlAttributes();
                        foreach (var derivedType in derivedTypes)
                            // Here we are assuming all the derived types have unique XML type names.
                            attributes.XmlArrayItems.Add(new XmlArrayItemAttribute { Type = derivedType });
                        if (itemType.IsConcreteType())
                            attributes.XmlArrayItems.Add(new XmlArrayItemAttribute { Type = itemType });
                    }
                    overrides.Add(declaringType, property.Name, attributes);
                }
            }
        }
        return overrides;
    }
}

public static class TypeExtensions
{
    public static bool IsConcreteType(this Type type)
    {
        return !type.IsAbstract && !type.IsInterface;
    }

    public static Type GetListType(this Type type)
    {
        while (type != null)
        {
            if (type.IsGenericType)
            {
                var genType = type.GetGenericTypeDefinition();
                if (genType == typeof(List<>))
                    return type.GetGenericArguments()[0];
            }
            type = type.BaseType;
        }
        return null;
    }
}

Затем вы можете сериализовать и десериализовать экземпляры RootObject в и из XML следующим образом:

var root = new RootObject
{
    AList = new AListObject
    {
        SerializedListObjects = new List<A> { new B(), new C() },
    },
};

var serializer = AListSerializerFactory.Instance.CreateSerializer(root.GetType());

var xml = root.GetXml(serializer);
var root2 = xml.LoadFromXml<RootObject>(serializer);

Используя методы расширения:

public static class XmlSerializationHelper
{
    public static T LoadFromXml<T>(this string xmlString, XmlSerializer serial = null)
    {
        serial = serial ?? new XmlSerializer(typeof(T));
        using (var reader = new StringReader(xmlString))
        {
            return (T)serial.Deserialize(reader);
        }
    }

    public static string GetXml<T>(this T obj, XmlSerializer serializer = null)
    {
        using (var textWriter = new StringWriter())
        {
            var settings = new XmlWriterSettings() { Indent = true }; // For cosmetic purposes.
            using (var xmlWriter = XmlWriter.Create(textWriter, settings))
                (serializer ?? new XmlSerializer(obj.GetType())).Serialize(xmlWriter, obj);
            return textWriter.ToString();
        }
    }
}

И в результате:

<RootObject xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
  <AList>
    <SerializedListObjects>
      <B />
      <C />
    </SerializedListObjects>
  </AList>
</RootObject>

Примечания:

  • Как объяснено в Утечка памяти при использовании StreamReader и XmlSerializer , вы должны статически кешировать любыеXmlSerializer построен с XmlAttributeOverrides, чтобы избежать серьезной утечки памяти.Документация предлагает использовать Hashtable, однако XmlAttributeOverrides не переопределяет Equals() или GetHashCode() и не предоставляет достаточный доступ к его внутренним данным для того, чтобы разработчики приложений могли писать свои собственные.Таким образом, необходимо использовать некую статическую схему кэширования всякий раз, когда используется XmlAttributeOverrides.

  • Учитывая сложность поиска и переопределения атрибутов XmlArrayItem всех свойств List<A>Вы можете придерживаться существующего механизма i:type.Это просто, хорошо работает, поддерживается как DataContractSerializer и XmlSerializer, так и стандарт .

  • Я написал классXmlArrayItemTypeOverrideSerializerFactory в общем виде, что добавляет очевидной сложности.

Рабочий образец .Net fiddle здесь .

...