C# 序列化和恢复未知类
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/590658/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Serializing and restoring an unknown class
提问by mafu
A base project contains an abstract base class Foo. In separate client projects, there are classes implementing that base class.
基础项目包含抽象基类 Foo。在单独的客户端项目中,有实现该基类的类。
I'd like to serialize and restore an instance of a concrete class by calling some method on the base class:
我想通过调用基类上的一些方法来序列化和恢复具体类的实例:
// In the base project:
public abstract class Foo
{
abstract void Save (string path);
abstract Foo Load (string path);
}
It can be assumed that at the time of deserialization, all needed classes are present. If possible in any way, the serialization should be done in XML. Making the base class implement IXmlSerializable is possible.
可以假设在反序列化时,所有需要的类都存在。如果可能的话,序列化应该在 XML 中完成。使基类实现 IXmlSerializable 是可能的。
I'm a bit stuck here. If my understanding of things is correct, then this is only possible by adding an [XmlInclude(typeof(UnknownClass))]
to the base class for every implementing class - but the implementing classes are unknown!
我有点卡在这里。如果我对事情的理解是正确的,那么这只能通过向[XmlInclude(typeof(UnknownClass))]
每个实现类的基类添加一个来实现 - 但实现类是未知的!
Is there a way to do this? I've got no experience with reflection, but i also welcome answers using it.
有没有办法做到这一点?我没有反射的经验,但我也欢迎使用它的答案。
Edit:The problem is Deserializing. Just serializing would be kind of easy. :-)
编辑:问题是De序列化。只是序列化会很容易。:-)
采纳答案by Marc Gravell
You can also do this at the point of creating an XmlSerializer
, by providing the additional details in the constructor. Note that it doesn't re-use such models, so you'd want to configure the XmlSerializer
once (at app startup, from configuration), and re-use it repeatedly... note many more customizations are possible with the XmlAttributeOverrides
overload...
您也可以在创建 时执行此操作,方法是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 {}
回答by leppie
Somewhere deep inside the XML namespaces lies a wonderful class called XmlReflectionImporter.
XML 命名空间的深处有一个很棒的类,称为 XmlReflectionImporter。
This may be of help to you if you need to create a schema at runtime.
如果您需要在运行时创建架构,这可能对您有所帮助。
回答by JoshBerke
You can also do this by creating an XmlSerializer passign in all possible types to the constructor. Be warned that when you use this constructor the xmlSerializer will be compiled each and every time and will result in a leak if you constantly recreate it. You will want to create a single serializer and reuse it in your application.
您也可以通过为构造函数创建一个所有可能类型的 XmlSerializer passign 来实现这一点。请注意,当您使用此构造函数时,xmlSerializer 将每次都被编译,如果您不断重新创建它,将导致泄漏。您将需要创建一个序列化程序并在您的应用程序中重用它。
You can then bootstrap the serializer and using reflection look for any descendants of foo.
然后,您可以引导序列化程序并使用反射查找 foo 的任何后代。
回答by Quintin Robinson
Well the serialization shouldn't be a problem, the XmlSerializer constructor takes a Type argument, even calling GetType on an instance of a derived class through a method on the abstract base will return the derived types actual Type. So in essence as long as you know the proper type upon deserialization then the serialization of the proper type is trivial. So you can implement a method on the base called serialize or what have you that passes this.GetType()
to the constructor of the XmlSerializer.. or just passes the current reference out and lets the serialize method take care of it and you should be fine.
那么序列化应该不是问题,XmlSerializer 构造函数接受一个 Type 参数,即使通过抽象基上的方法在派生类的实例上调用 GetType 也会返回派生类型的实际类型。所以本质上,只要你在反序列化时知道正确的类型,那么正确类型的序列化是微不足道的。因此,您可以在名为 serialize 的基础上实现一个方法,或者您拥有传递this.GetType()
给 XmlSerializer 的构造函数的方法。
Edit: Update for OP Edit..
编辑:更新 OP 编辑..
If you don't know the type at deserialization then you really have nothing but a string or byte array, without some sort of identifier somewhere you are kind of up a creek. There are some things you can do like trying to deserialize as every known derived type of the xx base class, I would not recommend this.
如果您不知道反序列化时的类型,那么您实际上只有一个字符串或字节数组,如果没有某种标识符,您就像一条小溪。您可以做一些事情,例如尝试反序列化为 xx 基类的每个已知派生类型,我不建议这样做。
Your other option is to walk the XML manually and reconstruct an object by embedding the type as a property or what have you, maybe that is what you originally meant in the article, but as it stands I don't think there is a way for the built in serialization to take care of this for you without you specifying the type.
您的另一种选择是手动遍历 XML 并通过将类型嵌入为属性或您拥有的内容来重建对象,也许这就是您在文章中最初的意思,但就目前而言,我认为没有办法内置序列化可以为您处理此问题,而无需您指定类型。
回答by Ray Lu
You don't have to put the serialization functions into any base class, instead, you can add it to your Utility Class.
您不必将序列化函数放入任何基类中,相反,您可以将其添加到您的实用程序类中。
e.g. ( the code is for example only, rootName is optional )
例如(代码仅作为示例,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();
}
}
Simply make call to
只需拨打
Utility.ToXml( fooObj, "Foo", @"c:\foo.xml");
Not only Foo's family types can use it, but all other serializable objects.
不仅 Foo 的家族类型可以使用它,所有其他可序列化的对象也可以使用它。
EDIT
编辑
OK full service... (rootName is optional)
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;
}
回答by Jakob Christensen
Marking the classes as Serializable and using SoapBinaryFormatter instead of XmlSerializer will give you this functionality automatically. When serializing the type information of the instance being serialized will be written to the XML, and SoapBinaryFormatter can instantiate the subclasses when deserializing.
将类标记为 Serializable 并使用SoapBinaryFormatter 而不是 XmlSerializer 将自动为您提供此功能。序列化时被序列化的实例的类型信息会写入到XML中,SoapBinaryFormatter可以在反序列化时实例化子类。
回答by Dana Holt
These links will probably be helpful to you:
这些链接可能对您有所帮助:
I have a complex remoting project and wanted very tight control over the serialized XML. The server could receive objects that it had no idea how to deserialize and vice versa, so I needed a way to identify them quickly.
我有一个复杂的远程项目,并希望对序列化的 XML 进行非常严格的控制。服务器可以接收它不知道如何反序列化的对象,反之亦然,所以我需要一种方法来快速识别它们。
All of the .NET solutions I tried lacked the needed flexibility for my project.
我尝试过的所有 .NET 解决方案都缺乏我的项目所需的灵活性。
I store an int attribute in the base xml to identify the type of object.
我在基本 xml 中存储一个 int 属性来标识对象的类型。
If I need to create a new object from xml I created a factory class that checks the type attribute then creates the appropriate derived class and feeds it the xml.
如果我需要从 xml 创建一个新对象,我创建了一个检查 type 属性的工厂类,然后创建适当的派生类并将 xml 提供给它。
I did something like this (pulling this out of memory, so syntax may be a little off):
我做了这样的事情(把它从内存中拉出来,所以语法可能有点不对):
(1) Created an interface
(1) 创建了一个接口
interface ISerialize
{
string ToXml();
void FromXml(string xml);
};
(2) Base class
(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) Derived class
(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) Object factory
(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;
}
};
回答by jkokorian
This method reads the XML root element and checks if the current executing assembly contains a type with such a name. If so, the XML document is deserialized. If not, an error is thrown.
此方法读取 XML 根元素并检查当前执行的程序集是否包含具有此类名称的类型。如果是,则 XML 文档被反序列化。如果不是,则抛出错误。
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);
}
}
}
回答by Patrick Koorevaar
I used the XmlType attribute of the unknown (but expected) classes to determine the Type for the deserialization. The to be expected types are load during the instantiation of the AbstractXmlSerializer class and placed in a dictionary. During the deserialization the root element is read and with this the type is retrieved form the dictionary. After that it can be deserialized normally.
我使用未知(但预期)类的 XmlType 属性来确定反序列化的类型。预期类型在 AbstractXmlSerializer 类的实例化期间加载并放置在字典中。在反序列化过程中,读取根元素,然后从字典中检索类型。之后就可以正常反序列化了。
XmlMessage.class:
XmlMessage.class:
public abstract class XmlMessage
{
}
IdleMessage.class:
IdleMessage.class:
[XmlType("idle")]
public class IdleMessage : XmlMessage
{
[XmlElement(ElementName = "id", IsNullable = true)]
public string MessageId
{
get;
set;
}
}
AbstractXmlSerializer.class:
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);
}