2015-07-23 5 views
0

У меня есть класс, который мне нужно для сериализации/десериализации, и я на полпути - у меня есть функционал сериализации, ниже XML. Однако, так как я уверен, реализации IXmlSerializable себя, я не зная, что реализация ReadXml должна выглядеть, учитывая, что SomeGenericClass<T> был сериализовать с использованием атрибутов на основе маркировки, а не явной реализации, если IXmlSerializableКак реализовать ReadXml для SomeClass: IList <IFoo>, где все экземпляры IFoo являются IFoo <T> переменных T

<?xml version="1.0" encoding="utf-16"?> 
<FooContainer FooName="DoSomething"> 
    <SomeGenericClassOfString xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" Value="Foobar" Name="firstParam" Description="First Paramater Serialized" /> 
    <SomeGenericClassOfInt32 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" Value="10000" Name="nextParam" Description="Second Serialized parameter" /> 
</FooContainer> 

Что я хочу сериализовать обратно в экземпляр:

public class FooContainer : IList<ISomeGenericClassBase>, IXmlSerializable 
{ 
    public string FooName {get;set;} 

    void IXmlSerializable.WriteXml(XmlWriter writer) { 
     var serializer = XmlSerializer.FromTypes(new Type[]{SomeGenericBaseClass})[0]; 
     this 
      .Select(item=>SomeGenericClassBase.ConvertToMe(item)) 
      .ToList() 
      .ForEach(item=>serializer.Serialize(writer, item)); 
    } 

    // IList Implementation omitted - wraps a private List<ISomeGenericClassBase> 
} 

Если список будет содержать экземпляры вдоль этих линий:

public interface ISomeGenericClassBase 
{ 
} 

public interface ISomeGenericBaseClass<T> : ISomeGenericBaseClass 
{ 
} 

public class SomeGenericClassBase : ISomeGenericClassBase 
{ 
    public static SomeGenericClassBase ConvertToMe(ISomeGenericClassBase target) { 
     return new SomeGenericClassBase() {Property1 = target.Property1; Property2 = target.Property2} 
    } 

    public static ISomeGenericBaseClass ExpantToTyped(SomeGenericClassBase target) { 
     // Implementation omitted - converts a base class instance to a generic instance by working out the generic type from saved data and reconstructing 
    } 
} 

public class SomeGenericClass<T> : SomeGenericClassBase, ISomeGenericBaseClass<T> 
{ 
    [XmlAttribute] 
    public string Name {get;set;} 

    [XmlAttribute] 
    public string Description{get;set;} 

    [XmlAttribute] 
    public T Value {get;set;} 

    [XmlElement("")] 
    public T[] ValidOptions {get;set;} 

} 

EDIT: Expanded реализации - понял, как это было, это не иллюстрировать проблему правильно

Основной проблемой является то, что я хочу, чтобы иметь возможность сериализовать элементы, которые только реализуют интерфейс, даже если я только вернусь SomeGenericClassBase экземпляров. На основе подхода, используемого в методе ExpandToTyped, я ожидаю, что потребители этого класса будут сохранять достаточные данные в их реализациями, которые позволят результирующим классам быть преобразованными обратно в исходную форму по мере необходимости. Так что да, есть потеря верности, но я могу жить в обмен на гибкость использования списка интерфейсов вместо списка базовых классов.

+0

Зачем использовать атрибуты xml для сериализации, но не для десериализации? –

+0

Извините за это - просто перечитайте то, что я спросил, и я понимаю, почему у вас возник вопрос. Я расширил этот вопрос, чтобы быть более наглядным примером того, что я пытаюсь сделать. Сводная причина состоит в том, что список состоит из интерфейсов, а не базовых классов. – tobriand

+0

Спасибо за комментарий, хотя - на самом деле указал мне в правильном направлении! Решение заключалось в том, чтобы сменить IXmlSerializable и использовать сериализуемый класс преобразователей - будет вводить его немного. – tobriand

ответ

0

Одним из решений является устранение проблемы (IXmlSerializable.ReadXml выглядит в любом случае весьма болезненным, например, для коллекций). То, что я в конечном итоге сделал, это сменить IXmlSerializable и вместо этого создать класс по строкам ниже.

Обратите внимание, что, хотя этот подход работает, в настоящее время довольно подвержен ошибкам, если сериализуемый экземпляр используется для чего угодно, кроме сериализации. Синхронизация поддерживается ТОЛЬКО при установке или извлечении SerializationTarget. Когда он установлен, мы преобразуем существующие параметры в соответствующие экземпляры и добавим их в сериализуемый список. Когда он будет восстановлен, если он равен нулю, мы раздуем все, что было в текущем значении.

Однако, если FooContainer изменится после создания этого объекта, он не будет поддерживать, что синхронизация и то, что будет сериализовано, будет устаревшей. Это во многом потому, что я ленив и не хочу снова использовать IList<SomeGenericClassBase>, чтобы переопределить методы Add и Remove (хотя это был бы более надежный подход).

public class FooContainerSerializable 
{ 
    public FooContainerSerializable() {} 
    public FooContainerSerializable(FooContainer serializationTarget) 
    { 
     this.SerializationTarget = serializationTarget; 
    } 

    [XmlIgnore] 
    public FooContainer SerializationTarget 
    { 
     get { 
      if (_SerializationTarget == null) 
      { 
       _SerializationTarget = new FooContainer(); 

       // Copy across extant collection properties here 
       this.Parameters.ForEach(item=>_SerializationTarget.Add(item)); 
      } 
      return _SerializationTarget; 
     } 
     set { 
      // Synchronize this entity's entries here 
      _SerializationTarget = value; 
      _SerializationTarget.ForEach(item=>this.Parameters.Add(item.Deflate())); 
     } 
    } 
    private FooContainer _SerializationTarget; 

    [XmlElement] 
    public string FooName { 
     get {return this.SerializationTarget.FooName;} 
     set {this.SerializationTarget.FooName = value;} 
    } 

    [XmlElement] 
    public List<SomeGenericClassBase> Parameters { 
     get {return _Parameters ?? (_Parameters = new List<SomeGenericClassBase>());} 
     set {_Parameters = value;} 
    } 
} 
0

Вот еще один вариант, если вы хотите использовать абстрактный класс вместо интерфейса в определении вашей коллекции. Вам также необходимо объявить все производные типы SomeGenericClassBase с использованием атрибутов XmlInclude. Я думаю, что это будет не так уж плохо, если будет только несколько типов, которые вы будете использовать с этим классом.

[XmlRoot(ElementName = "FooContainer")] 
public class FooContainer : List<SomeGenericClassBase> 
{ 
    [XmlAttribute] 
    public string FooName { get; set; } 
}  

[XmlInclude(typeof(SomeGenericClass<string>))] 
[XmlInclude(typeof(SomeGenericClass<int>))] 
public abstract class SomeGenericClassBase 
{ 
    [XmlAttribute] 
    public string Name { get; set; } 

    [XmlAttribute] 
    public string Description { get; set; } 
} 

public class SomeGenericClass<T> : SomeGenericClassBase 
{ 
    [XmlAttribute] 
    public T Value { get; set; } 

    [XmlElement] 
    public T[] ValidOptions { get; set; } 
} 

class Class1 
{ 
    public static void Run() 
    { 
     var f = new FooContainer() 
     { 
      new SomeGenericClass<string> { Name = "firstParam", Description = "First Paramater Serialized", Value = "Foobar"}, 
      new SomeGenericClass<int> { Name = "nextParam", Description = "Second Serialized parameter", Value = 10000 } 
     }; 

     f.FooName = "DoSomething"; 

     XmlSerializer serializer = new XmlSerializer(f.GetType());    
     StringBuilder sb = new StringBuilder(); 

     // Serialize 
     using (StringWriter writer = new StringWriter(sb)) 
     { 
      serializer.Serialize(writer, f); 
     } 

     Console.WriteLine(sb); 

     // Deserialize 
     using(StringReader reader = new StringReader(sb.ToString())) 
     { 
      FooContainer f2 = (FooContainer)serializer.Deserialize(reader); 
     } 
    } 
} 

Это сериализации к следующему XML:

<?xml version="1.0" encoding="utf-16"?> 
<FooContainer xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> 
    <SomeGenericClassBase xsi:type="SomeGenericClassOfString" Name="firstParam" Description="First Paramater Serialized" Value="Foobar" /> 
    <SomeGenericClassBase xsi:type="SomeGenericClassOfInt32" Name="nextParam" Description="Second Serialized parameter" Value="10000" /> 
</FooContainer> 

Десериализация сохраняет полную верность.

+0

Интересный подход - и, как мне кажется, немного чище, чем у меня. Считая, что с помощью статического метода, который обрабатывает неизвестные типы под '[XmlInclude (SomeGenericClass )], он мог бы работать так же хорошо (плюс соответствующий метод' Expand() ', чтобы попытаться преобразовать обратно в T). Дайте ему шанс и посмотрим, как это работает. К сожалению, пока я ожидаю довольно ограниченный набор типов, я не могу гарантировать, что потребители этого класса не будут пытаться использовать другие, и я бы хотел иметь возможность обращаться с ними, если это возможно! – tobriand