序列化和恢复未知类

基础项目包含抽象基类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序列化。 只是序列化会很容易。 🙂

您也可以通过在构造函数中提供其他详细信息来创建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 extraTypes = new List(); // 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 {} 

您不必将序列化函数放入任何基类,而是可以将其添加到Utility类中。

例如(代码仅供参考,rootName是可选的)

 public static class Utility { public static void ToXml(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的家庭类型可以使用它,还有所有其他可序列化的对象。

编辑

OK全服务…(rootName是可选的)

 public static T FromXml(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; } 

序列化应该不是问题,XmlSerializer构造函数采用Type参数,甚至通过抽象基础上的方法在派生类的实例上调用GetType将返回派生类型的实际Type。 所以从本质上讲,只要你知道反序列化时的正确类型,那么正确类型的序列化是微不足道的。 所以你可以在名为serialize的基础上实现一个方法,或者你将这个方法传递给XmlSerializer的构造函数。或者只是传递当前引用并让serialize方法处理它你应该是精细。

编辑:OP编辑的更新..

如果你不知道反序列化的类型那么你真的只有一个字符串或字节数组,没有某种标识符,你在某个小溪上。 你可以做一些事情,比如尝试反序列化为xx基类的每个已知派生类型,我不建议这样做。

你的另一个选择是手动遍历XML并通过将类型作为属性或者你有什么来重构一个对象,也许这就是你在文章中最初的意思,但是现在我认为没有办法内置的序列化可以在不指定类型的情况下为您处理。

在XML命名空间深处的某个地方有一个很棒的类,叫做XmlReflectionImporter。

如果您需要在运行时创建模式,这可能对您有所帮助。

您也可以通过在构造函数中创建所有可能类型的XmlSerializer passign来完成此操作。 请注意,当您使用此构造函数时,xmlSerializer将每次都被编译,如果您不断重新创建它将导致泄漏。 您将需要创建一个序列化程序并在应用程序中重用它。

然后,您可以引导序列化程序并使用reflection查找foo的任何后代。

这些链接可能对您有所帮助:

  • CodeProject文章
  • 博客文章
  • Stack Overflow问题

我有一个复杂的远程处理项目,并希望对序列化的XML进行非常严格的控制。 服务器可以接收它不知道如何反序列化的对象,反之亦然,所以我需要一种方法来快速识别它们。

我尝试的所有.NET解决方案都缺乏我项目所需的灵活性。

我在基础xml中存储一个int属性来标识对象的类型。

如果我需要从xml创建一个新对象,我创建了一个检查type属性的工厂类,然后创建相应的派生类并将其提供给xml。

我做了类似这样的事情(拉出内存,所以语法可能有点偏离):

(1)创建一个界面

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

(2)基础class

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

此方法读取XML根元素并检查当前正在执行的程序集是否包含具有此类名称的类型。 如果是这样,则反序列化XML文档。 如果不是,则抛出错误。

 public static T FromXml(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); } } } 

将类标记为Serializable并使用Soap BinaryFormatter而不是XmlSerializer将自动为您提供此function。 序列化序列化实例的类型信息时,将写入XML, Soap BinaryFormatter可以在反序列化时实例化子类。

我使用未知(但预期)类的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 where AbstractType : class { private Dictionary typeMap; public AbstractXmlSerializer(List types) { typeMap = new Dictionary(); 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; } } 

unit testing:

 [TestMethod] public void TestMethod1() { List extraTypes = new List(); extraTypes.Add(typeof(IdleMessage)); AbstractXmlSerializer ser = new AbstractXmlSerializer(extraTypes); String xmlMsg = ""; MutcMessage result = ser.Deserialize(xmlMsg); Assert.IsTrue(result is IdleMessage); }