Во-первых, 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 здесь .