在C#中,如何使用XmlSerializer
反序列化可能属于基类的对象,或者在不事先知道类型的情况下对几个派生类中的任何一个进行反序列化?
我的所有派生类都添加了其他数据成员。我已经制作了一个简单的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();
答案 0 :(得分:18)
你有一些包含派生类型的根类/标签吗?如果是,您可以使用XmlElementAttribute将标记名称映射到类型:
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 { }
答案 1 :(得分:5)
我最近为基类T和T的任何派生类编写了这个通用的serializer \ deserializer。 到目前为止似乎工作。
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();
}
答案 2 :(得分:4)
您可以尝试使用构造函数XmlSerializer(Type type, Type[] extraTypes)来创建一个适用于所有相关类型的序列化程序。
答案 3 :(得分:4)
如果您未使用XmlSerializer
,则可以使用DataContractSerializer
来使用KnownType
属性。
您需要做的就是为每个子类的父类添加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 );
}
}
}
将输出:
<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
在输出中,您会注意到c2和c3的xml包含额外的类型信息,允许DataContractSerializer.ReadObject
创建正确的类型。
答案 4 :(得分:2)
你可以使用XmlInclude
[XmlInclude(typeof(MyClass))]
public abstract class MyBaseClass
{
//...
}
否则,如果要在序列化时添加类型:
Type[] types = new Type[]{ typeof(MyClass) }
XmlSerializer serializer = new XmlSerializer(typeof(MyBaseClass), types);