2009-02-26 12 views
10

Podstawowy projekt zawiera abstrakcyjną klasę podstawową Foo. W oddzielnych projektach klienckich istnieją klasy implementujące tę klasę podstawową.Serializowanie i odtwarzanie nieznanej klasy

Chciałbym serializacji i przywrócić wystąpienie konkretnej klasy przez wywołanie niektóre metody w klasie bazowej:

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

Można przypuszczać, że w momencie deserializacji, wszystkie potrzebne zajęcia są obecne . Jeśli to możliwe, serializacja powinna być wykonana w XML. Umożliwienie wykonania klasy podstawowej IXmlSerializable jest możliwe.

Trochę utknąłem tutaj. Jeśli moje rozumienie rzeczy jest poprawne, to jest to możliwe tylko przez dodanie [XmlInclude(typeof(UnknownClass))] do klasy bazowej dla każdej klasy implementacji - ale klasy implementacji są nieznane!

Czy istnieje sposób, aby to zrobić? Nie mam doświadczenia z refleksją, ale z zadowoleniem przyjmuję również odpowiedzi z nim związane.

Edycja: Problem polega na serializacji De. Tylko serializowanie byłoby łatwe. :-)

Odpowiedz

9

Można również zrobić to w momencie tworzenia XmlSerializer, poprzez dostarczenie dodatkowych szczegółów w konstruktorze. Zwróć uwagę, że nie używa on ponownie takich modeli, więc chcesz raz skonfigurować XmlSerializer (przy uruchamianiu aplikacji, w konfiguracji) i używać go wielokrotnie. Zauważ, że dzięki dostosowaniu XmlAttributeOverrides możliwe jest wiele dostosowań ...

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

Gdzieś głęboko w przestrzeni nazw XML znajduje się wspaniała klasa o nazwie XmlReflectionImporter.

Może to być pomocne w przypadku tworzenia schematu w czasie wykonywania.

1

Można to również zrobić, tworząc hasło XmlSerializer we wszystkich możliwych typach, do constructor. Ostrzegamy, że podczas korzystania z tego konstruktora xmlSerializer będzie kompilowany za każdym razem i spowoduje nieszczelność, jeśli będzie on ciągle odtwarzany. Będziesz chciał stworzyć pojedynczy serializator i ponownie użyć go w swojej aplikacji.

Następnie można załadować serializator i użyć wyglądu refleksji dla potomków foo.

2

Cóż, serializacja nie powinna stanowić problemu, konstruktor XmlSerializer bierze argument Type, nawet wywołanie metody GetType w instancji klasy pochodnej za pomocą metody na bazie abstrakcyjnej zwróci typy pochodne rzeczywistego typu. Więc w zasadzie tak długo, jak znasz właściwy typ przy deserializacji, wtedy serializacja właściwego typu jest banalna. Więc możesz zaimplementować metodę na bazie o nazwie serialize lub co ty przekazujesz this.GetType() do konstruktora XmlSerializer .. lub po prostu przekazuje bieżące referencje i pozwala, aby metoda serializacyjna zajęła się tym i powinieneś być w porządku.

Edycja: Aktualizacja dla PO Edit ..

Jeśli nie znasz typu na deserializacji to naprawdę nie ma się czym tablicy ciągów lub bajtów, bez jakiegoś identyfikatora gdzieś jesteś rodzajem Up zatoczka. Jest kilka rzeczy, które możesz zrobić, próbując deserializować jak każdy znany rodzaj pochodnej klasy podstawowej xx, nie polecałbym tego.

Twoją drugą opcją jest ręczne przesuwanie pliku XML i rekonstrukcja obiektu przez osadzenie tego typu jako własności lub co ty, może to jest to, co pierwotnie miałeś na myśli w tym artykule, ale w obecnej postaci nie sądzę, że to sposób na zbudowanie w serializacji, aby zająć się tym dla ciebie bez określania typu.

3

Nie musisz umieszczać funkcji serializacji w żadnej klasie bazowej, zamiast tego możesz dodać ją do swojej klasy Utility.

np. (Kod jest na przykład tylko, rootName jest opcjonalne)

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(); 
     } 
} 

Wystarczy zrobić wezwanie do

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

Nie tylko typy rodzin Foo może go używać, ale wszystkie inne serializacji obiektów.

EDIT

OK full service ... (rootName jest opcjonalne)

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

zrealizować ten jest bardzo stary, ale wygląda na to świetne rozwiązanie. Zastanawiające się, jak używać funkcji FromXml, ponieważ nie jest do końca jasne: "T src" nie jest używane i wymaga utworzenia instancji klasy. Usunięcie go powoduje, że kompilator musi się zastanowić, co to jest T. –

0

Oznaczenie klasy jako Serializable i korzystania Soap BinaryFormatter zamiast XmlSerializer automatycznie daje tej funkcji. Podczas serializowania informacji o typie instancji serializowanej zostanie zapisane w pliku XML, a BinaryFormatter może utworzyć instancje podklas podczas deserializacji.

+0

To jest oficjalnie przestarzałe: "Począwszy od .NET Framework w wersji 3.5, ta klasa jest przestarzała. Zamiast tego użyj BinaryFormatter."; http://msdn.microsoft.com/en-us/library/system.runtime.serialization.formatters.soap.soapformatter.aspx –

+0

Poprawiłem odpowiedź odpowiednio. Dzięki. –

+0

Niestety program BinaryFormatter nie generuje żadnych danych wyjściowych XML. : - \ – mafu

1

Linki te będą prawdopodobnie być pomocne dla Ciebie:

mam kompleksowy projekt remoting i chciał bardzo ścisłą kontrolę nad serializowanym XML. Serwer mógł odbierać obiekty, których nie miał pojęcia jak deserializować i odwrotnie, więc potrzebowałem sposobu, aby szybko je zidentyfikować.

Wszystkie rozwiązania .NET, które wypróbowałem, nie miały potrzebnej elastyczności dla mojego projektu.

Przechowuję atrybut int w bazie xml w celu zidentyfikowania typu obiektu.

Jeśli potrzebuję utworzyć nowy obiekt z xml, utworzę klasę fabryczną, która sprawdza atrybut type, a następnie tworzy odpowiednią klasę pochodną i przekazuje ją do xml.

zrobiłem coś takiego (ciągnięcie tego z pamięci, więc składnia może być trochę off):

(1) Utworzona istnieć interfejs

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

(2) Klasa bazowa

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) pochodne klasy

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) Fabryka Object

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

Metoda ta odczytuje element bazowy XML i sprawdza, czy obecny zespół wykonujący zawiera typ z taką nazwą. Jeśli tak, dokument XML jest deserializowany. Jeśli nie, zostanie zgłoszony błąd.

public static T FromXml<T>(string xmlString) 
{ 
    Type sourceType; 
    using (var stringReader = new StringReader(xmlString)) 
    { 
     var rootNodeName = XElement.Load(stringReader).Name.LocalName; 
     sourceType = 
       Assembly.GetExecutingAssembly().GetTypes() 
        .FirstOrDefault(t => t.IsSubclassOf(typeof(T)) 
            && t.Name == rootNodeName) 
       ?? 
       Assembly.GetAssembly(typeof(T)).GetTypes() 
        .FirstOrDefault(t => t.IsSubclassOf(typeof(T)) 
            && t.Name == rootNodeName); 

     if (sourceType == null) 
     { 
      throw new Exception(); 
     } 
    } 

    using (var stringReader = new StringReader(xmlString)) 
    { 
     if (sourceType.IsSubclassOf(typeof(T)) || sourceType == typeof(T)) 
     { 
      var ser = new XmlSerializer(sourceType); 

      using (var xmlReader = new XmlTextReader(stringReader)) 
      { 
       T obj; 
       obj = (T)ser.Deserialize(xmlReader); 
       xmlReader.Close(); 
       return obj; 
      } 
     } 
     else 
     { 
      throw new InvalidCastException(sourceType.FullName 
              + " cannot be cast to " 
              + typeof(T).FullName); 
     } 
    } 
} 
0

Użyłem atrybutu XmlType nieznanych (ale oczekiwanych) klas, aby określić Typ dla deserializacji. Typy, których należy się spodziewać, to ładunek podczas tworzenia klasy AbstractXmlSerializer i umieszczany w słowniku. Podczas deserializacji odczytywany jest element główny, a wraz z nim typ jest pobierany ze słownika. Po tym można go normalizować z postaci szeregowej.

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);   
} 
Powiązane problemy