2011-01-26 4 views
19

C#에서는 XmlSerializer을 사용하여 기본 클래스이거나 미리 파생 클래스를 모르는 개체를 deserialize하는 방법은 무엇입니까?XmlSerializer를 사용하여 형식을 미리 모른 채 기본 또는 파생 클래스 일 수있는 개체를 deserialize하려면 어떻게해야합니까?

파생 클래스는 모두 데이터 멤버를 추가합니다. 클래스 객체를 직렬화 및 비 직렬화 할 수있는 간단한 GUI를 만들었습니다. 사용자가 채우려는 필드를 기반으로 상속 된 클래스 (또는 기본 클래스)가 적절하기 때문에 객체를 직렬화합니다.

나는 직렬화에 아무런 문제가 없다. 문제는 비 직렬화입니다. XmlSerializer 클래스를 미리 알지 못해서 올바른 파생 클래스에 데이터를 직렬화하지 못하게하려면 어떻게해야합니까? 나는 현재 XmlReader을 XML 파일의 첫 번째 노드를 읽고 그 클래스를 결정하기 위해 만들고, 그것은 내 목적을 위해 작동하는 것처럼 보입니다. 그러나 이것은 매우 비 숙련 된 솔루션처럼 보입니다.

아래에 몇 가지 샘플 코드를 게시했습니다. 어떤 제안?

BaseType objectOfConcern = new BaseType(); 
XmlSerializer xserializer; 
XmlTextReader xtextreader = new XmlTextReader(DEFAULT_FILENAME); 

do { xtextreader.Read(); } while (xtextreader.NodeType != XmlNodeType.Element); 

string objectType = xtextreader.Name; 
xtextreader.Close(); 

FileStream fstream = new FileStream(DEFAULT_FILENAME, FileMode.Open); 

switch (objectType) 
    { 
case "type1": 
    xserializer = new XmlSerializer(typeof(DerivedType)); 

    objectOfConcern = (DerivedType)xserializer.Deserialize(fstream); 

    //Load fields specific to that derived type here 
    whatever = (objectOfConcern as DerivedType).NoOfstreamubordinates.ToString(); 

    case "xxx_1": 
     //code here 

    case "xxx_2": 
     //code here 

    case "xxx_n": 
     //code here 

     //and so forth 

    case "BaseType": 
    xserializer = new XmlSerializer(typeof(BaseType)); 
    AssignEventHandler(xserializer); 
    objectOfConcern = (BaseType)xserializer.Deserialize(fstream); 
} 

//Assign all deserialized values from base class common to all derived classes here 

//Close the FileStream 
fstream.Close(); 

답변

17

파생 된 유형이 포함 된 루트 클래스/태그가 있습니까? 그래, 당신은 매핑 XmlElementAttribute를 사용할 수있는 경우 태그의 이름을 입력합니다 :

당신이이 XmlSerializer 대신 KnownType 속성으로 DataContractSerializer을 사용할 수 있습니다 사용에 설정하지 않는 경우
public class RootElementClass 
{ 
    [XmlElement(ElementName = "Derived1", Type = typeof(Derived1BaseType))] 
    [XmlElement(ElementName = "Derived2", Type = typeof(Derived2BaseType))] 
    [XmlElement(ElementName = "Derived3", Type = typeof(Derived3BaseType))] 
    public BaseType MyProperty { get; set; } 
} 

public class BaseType { } 
public class Derived1BaseType : BaseType { } 
public class Derived2BaseType : BaseType { } 
public class Derived3BaseType : BaseType { } 
+0

아주 멋진, 감사합니다! 좋은 예를 들면 –

4

.

각 하위 클래스의 부모 클래스에 KnownType 속성을 추가하면 나머지는 DataContractSerializer이 수행하면됩니다.

DataContractSerializer은 XML에 직렬화 할 때 유형 정보를 추가하고 올바른 형식을 생성하기 위해 직렬화 해제 할 때 해당 유형 정보를 사용합니다. 예를 들어

다음 코드를

[KnownType(typeof(C2))] 
[KnownType(typeof(C3))] 
public class C1 {public string P1 {get;set;}} 
public class C2 :C1 {public string P2 {get;set;}} 
public class C3 :C1 {public string P3 {get;set;}} 

class Program 
{ 
    static void Main(string[] args) 
    { 
    var c1 = new C1{ P1="c1"}; 
    var c2 = new C2{ P1="c1", P2="c2"}; 
    var c3 = new C3{ P1="c1", P3="c3"}; 

    var s = new DataContractSerializer(typeof(C1)); 
    Test(c1, s); 
    Test(c2, s); 
    Test(c3, s); 
    } 

    static void Test(C1 objectToSerialize, DataContractSerializer serializer) 
    { 
    using (var stream = new MemoryStream()) 
    { 
     serializer.WriteObject(stream, objectToSerialize); 
     stream.WriteTo(Console.OpenStandardOutput()); 
     stream.Position = 0; 
     var deserialized = serializer.ReadObject(stream); 
     Console.WriteLine(Environment.NewLine + "Deserialized Type: " + deserialized.GetType().FullName);    
    } 
    } 
} 

출력 : 당신이 C2와 C3의 XML을 알 수 있습니다

<C1 xmlns="http://schemas.datacontract.org/2004/07/ConsoleApplication1" xmlns:i="http://www.w3.org/2001/XMLSchema-instance"> 
<P1>c1</P1></C1> 

Deserialized Type: ConsoleApplication1.C1 

<C1 i:type="C2" xmlns="http://schemas.datacontract.org/2004/07/ConsoleApplication1" xmlns:i="http://www.w3.org/2001/XMLSchema-instance"> 
<P1>c1</P1><P2>c2</P2></C1> 

Deserialized Type: ConsoleApplication1.C2 

<C1 i:type="C3" xmlns="http://schemas.datacontract.org/2004/07/ConsoleApplication1" xmlns:i="http://www.w3.org/2001/XMLSchema-instance"> 
<P1>c1</P1><P3>c3</P3></C1> 

Deserialized Type: ConsoleApplication1.C3 

출력에서 ​​생성하는 DataContractSerializer.ReadObject을 허용 별도의 유형 정보를 포함 올바른 유형.

+0

+1. – Beyers

4

나는 최근에이 기본 serializer \ deserializer를 기본 클래스 T와 T의 파생 클래스로 작성했습니다. 지금까지 작동하는 것으로 보입니다.

Type [] 배열은 T와 T 자체의 모든 파생 된 유형을 저장합니다. 디시리얼라이저는 각각을 시도하고 올바른 것을 찾으면 리턴합니다.

/// <summary> 
/// A generic serializer\deserializer 
/// </summary> 
/// <typeparam name="T"></typeparam> 
public static class Serializer<T> 
{ 
    /// <summary> 
    /// serialize an instance to xml 
    /// </summary> 
    /// <param name="instance"> instance to serialize </param> 
    /// <returns> instance as xml string </returns> 
    public static string Serialize(T instance) 
    { 
     StringBuilder sb = new StringBuilder(); 
     XmlWriterSettings settings = new XmlWriterSettings(); 

     using (XmlWriter writer = XmlWriter.Create(sb, settings)) 
     { 
      XmlSerializer serializer = new XmlSerializer(instance.GetType()); 
      serializer.Serialize(writer, instance); 
     } 

     return sb.ToString(); 
    } 

    /// <summary> 
    /// deserialize an xml into an instance 
    /// </summary> 
    /// <param name="xml"> xml string </param> 
    /// <returns> instance </returns> 
    public static T Deserialize(string xml) 
    { 
     using (XmlReader reader = XmlReader.Create(new StringReader(xml))) 
     { 
      foreach (Type t in types) 
      { 
       XmlSerializer serializer = new XmlSerializer(t); 
       if (serializer.CanDeserialize(reader)) 
        return (T)serializer.Deserialize(reader); 
      } 
     } 

     return default(T); 
    } 

    /// <summary> 
    /// store all derived types of T: 
    /// is used in deserialization 
    /// </summary> 
    private static Type[] types = AppDomain.CurrentDomain.GetAssemblies() 
             .SelectMany(s => s.GetTypes()) 
             .Where(t => typeof(T).IsAssignableFrom(t) 
              && t.IsClass 
              && !t.IsGenericType) 
              .ToArray(); 
} 
+0

정확히 내가 뭘 찾고 있었는지, 고마워! – whywhywhy

2

당신은 XmlInclude

[XmlInclude(typeof(MyClass))] 
public abstract class MyBaseClass 
{ 
    //... 
} 
직렬화 할 때 유형을 추가하려면, 그렇지 않은 경우

를 사용할 수 있습니다

Type[] types = new Type[]{ typeof(MyClass) } 

XmlSerializer serializer = new XmlSerializer(typeof(MyBaseClass), types);