2009-02-26 3 views
10

Базовый проект содержит абстрактный базовый класс Foo. В отдельных клиентских проектах существуют классы, реализующие этот базовый класс.Сериализация и восстановление неизвестного класса

Я хотел бы сериализации и восстановить экземпляр конкретного класса, вызвав некоторый метод в базовом классе:

// In the base project: 
public abstract class Foo 
{ 
    abstract void Save (string path); 
    abstract Foo Load (string path); 
} 

Можно предположить, что во время десериализации все необходимые классы присутствуют , Если возможно, каким-либо образом, сериализация должна выполняться в XML. Возможна реализация базового класса IXmlSerializable.

Я немного застрял здесь. Если мое понимание вещей верное, то это возможно только путем добавления [XmlInclude(typeof(UnknownClass))] к базовому классу для каждого класса реализации, но классы реализации неизвестны!

Есть ли способ сделать это? У меня нет опыта с размышлениями, но я также приветствую ответы, используя его.

Редактировать: Проблема De сериализация. Просто сериализация была бы легкой. :-)

ответ

9

Вы также можете сделать это в момент создания XmlSerializer, предоставляя дополнительные детали в конструкторе. Обратите внимание, что он не повторно использует такие модели, поэтому вам нужно настроить один раз XmlSerializer (при запуске приложения, из конфигурации) и повторно использовать его повторно ... обратите внимание, что при перегрузке XmlAttributeOverrides возможны многие другие настройки ...

using System; 
using System.Collections.Generic; 
using System.IO; 
using System.Xml.Serialization; 
static class Program 
{ 
    static readonly XmlSerializer ser; 
    static Program() 
    { 
     List<Type> extraTypes = new List<Type>(); 
     // TODO: read config, or use reflection to 
     // look at all assemblies 
     extraTypes.Add(typeof(Bar)); 
     ser = new XmlSerializer(typeof(Foo), extraTypes.ToArray()); 
    } 
    static void Main() 
    { 
     Foo foo = new Bar(); 
     MemoryStream ms = new MemoryStream(); 
     ser.Serialize(ms, foo); 
     ms.Position = 0; 
     Foo clone = (Foo)ser.Deserialize(ms); 
     Console.WriteLine(clone.GetType()); 
    } 
} 

public abstract class Foo { } 
public class Bar : Foo {} 
1

Где-то глубоко внутри пространств имен XML лежит замечательный класс под названием XmlReflectionImporter.

Это может быть полезно вам, если вам нужно создать схему во время выполнения.

1

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

После этого вы можете загружать сериализатор и использовать отражение для любых потомков foo.

2

Ну, сериализация не должна быть проблемой, конструктор XmlSerializer принимает аргумент типа, даже вызвав GetType на экземпляре производного класса с помощью метода на абстрактной базе, вернет производные типы actual Type. Таким образом, по существу, если вы знаете правильный тип при десериализации, сериализация соответствующего типа тривиальна. Таким образом, вы можете реализовать метод на базе, называемый serialize, или то, что вы передаете this.GetType() конструктору XmlSerializer .. или просто передает текущую ссылку и позволяет метод serialize позаботиться об этом, и все должно быть хорошо.

Edit: Обновление для OP Edit ..

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

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

3

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

например. (Код только для примера, rootName не является обязательным)

public static class Utility 
{ 
     public static void ToXml<T>(T src, string rootName, string fileName) where T : class, new() 
     { 
      XmlSerializer serializer = new XmlSerializer(typeof(T), new XmlRootAttribute(rootName)); 
      XmlTextWriter writer = new XmlTextWriter(fileName, Encoding.UTF8); 
      serializer.Serialize(writer, src); 
      writer.Flush(); 
      writer.Close(); 
     } 
} 

Просто сделать вызов

Utility.ToXml(fooObj, "Foo", @"c:\foo.xml"); 

не только типы семьи Foo могут использовать его, но и все другие сериализуемые объекты.

EDIT

OK полный спектр услуг, ... (rootName необязателен)

public static T FromXml<T>(T src, string rootName, string fileName) where T : class, new() 
{ 
    XmlSerializer serializer = new XmlSerializer(typeof(T), new XmlRootAttribute(rootName)); 
    TextReader reader = new StreamReader(fileName); 
    return serializer.Deserialize(reader) as T; 
} 
+1

Реализовать это действительно старый, но выглядит как отличное решение. Просто интересно, как использовать функцию FromXml, поскольку она не совсем ясна: «T src» не используется и требует создания экземпляра класса. Устранение этого приводит к тому, что компилятор пытается понять, что такое T. –

0

Маркировка классов, как Сериализуемый и используя мыло BinaryFormatter вместо XmlSerializer даст вам эту функцию автоматически. Когда сериализация информации о типе сериализуемого экземпляра будет записана в XML, и Soap BinaryFormatter может создавать подклассы при десериализации.

+0

Это официально устарело: «Начиная с .NET Framework версии 3.5 этот класс устарел. Вместо этого используйте BinaryFormatter.»; http://msdn.microsoft.com/en-us/library/system.runtime.serialization.formatters.soap.soapformatter.aspx –

+0

Я изменил ответ соответствующим образом. Благодарю. –

+0

Я боюсь, что BinaryFormatter не производит XML-вывод. : - \ – mafu

1

Эти ссылки, вероятно, будет полезно для вас:

У меня сложный удаленного взаимодействия проекта и хотел очень жесткий контроль над сериализованного XML. Сервер мог получать объекты, которые не имели представления о десериализации и наоборот, поэтому мне нужен способ быстро их идентифицировать.

Все .NET-решения, которые я пробовал, не обладали необходимой гибкостью для моего проекта.

Я сохраняю атрибут int в базовом xml, чтобы идентифицировать тип объекта.

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

я сделал что-то вроде этого (вытягивать это из памяти, так что синтаксис может быть немного выключен):

(1) создали интерфейс

interface ISerialize 
{ 
    string ToXml(); 
    void FromXml(string xml);  
}; 

(2) Базовый класс

public class Base : ISerialize 
{ 
    public enum Type 
    { 
     Base, 
     Derived 
    }; 

    public Type m_type; 

    public Base() 
    { 
     m_type = Type.Base; 
    } 

    public virtual string ToXml() 
    { 
     string xml; 
     // Serialize class Base to XML 
     return string; 
    } 

    public virtual void FromXml(string xml) 
    { 
     // Update object Base from xml 
    } 
}; 

(3) производный класс

public class Derived : Base, ISerialize 
{ 
    public Derived() 
    { 
     m_type = Type.Derived; 
    } 

    public override virtual string ToXml() 
    { 
     string xml; 
     // Serialize class Base to XML 
     xml = base.ToXml(); 
     // Now serialize Derived to XML 
     return string; 
    } 
    public override virtual void FromXml(string xml) 
    { 
     // Update object Base from xml 
     base.FromXml(xml); 
     // Update Derived from xml 
    } 
}; 

(4) Объект завод

public ObjectFactory 
{ 
    public static Base Create(string xml) 
    { 
     Base o = null; 

     Base.Type t; 

     // Extract Base.Type from xml 

     switch(t) 
     { 
      case Base.Type.Derived: 
       o = new Derived(); 
       o.FromXml(xml); 
      break; 
     } 

     return o; 
    } 
}; 
0

Этот метод считывает корневой элемент XML и проверяет, если текущее выполнение сборки содержит тип с таким именем. Если это так, XML-документ десериализуется. Если нет, возникает ошибка.

0

Я использовал атрибут XmlType неизвестных (но ожидаемых) классов для определения типа десериализации. Ожидаемые типы - это загрузка во время создания класса AbstractXmlSerializer и размещение в словаре. Во время десериализации считывается корневой элемент, и при этом тип извлекается из словаря. После этого его можно десериализовать нормально.

XmlMessage.class:

public abstract class XmlMessage 
{ 
} 

IdleMessage.class:

[XmlType("idle")] 
public class IdleMessage : XmlMessage 
{ 
    [XmlElement(ElementName = "id", IsNullable = true)] 
    public string MessageId 
    { 
     get; 
     set; 
    } 
} 

AbstractXmlSerializer.class:

public class AbstractXmlSerializer<AbstractType> where AbstractType : class 
{ 
    private Dictionary<String, Type> typeMap; 

    public AbstractXmlSerializer(List<Type> types) 
    {    
     typeMap = new Dictionary<string, Type>(); 

     foreach (Type type in types) 
     { 
      if (type.IsSubclassOf(typeof(AbstractType))) { 
       object[] attributes = type.GetCustomAttributes(typeof(XmlTypeAttribute), false); 

       if (attributes != null && attributes.Count() > 0) 
       { 
        XmlTypeAttribute attribute = attributes[0] as XmlTypeAttribute; 
        typeMap[attribute.TypeName] = type; 
       } 
      } 
     } 
    } 

    public AbstractType Deserialize(String xmlData) 
    { 
     if (string.IsNullOrEmpty(xmlData)) 
     { 
      throw new ArgumentException("xmlData parameter must contain xml"); 
     }    

     // Read the Data, Deserializing based on the (now known) concrete type. 
     using (StringReader stringReader = new StringReader(xmlData)) 
     { 
      using (XmlReader xmlReader = XmlReader.Create(stringReader)) 
      { 
       String targetType = GetRootElementName(xmlReader); 

       if (targetType == null) 
       { 
        throw new InvalidOperationException("XML root element was not found"); 
       }       

       AbstractType result = (AbstractType)new 
        XmlSerializer(typeMap[targetType]).Deserialize(xmlReader); 
       return result; 
      } 
     } 
    } 

    private static string GetRootElementName(XmlReader xmlReader) 
    {    
     if (xmlReader.IsStartElement()) 
     { 
      return xmlReader.Name; 
     } 

     return null; 
    } 
} 

UnitTest:

[TestMethod] 
public void TestMethod1() 
{ 
    List<Type> extraTypes = new List<Type>(); 
    extraTypes.Add(typeof(IdleMessage)); 
    AbstractXmlSerializer<XmlMessage> ser = new AbstractXmlSerializer<XmlMessage>(extraTypes); 

    String xmlMsg = "<idle></idle>"; 

    MutcMessage result = ser.Deserialize(xmlMsg); 
    Assert.IsTrue(result is IdleMessage);   
} 
Смежные вопросы