我有一个具有定义为接口的属性的类。 我的类的用户可以为此属性分配任何实现该接口的类实现。 我希望能够从磁盘上的文本文件加载此类状态。用户应该能够手动修改xml文件,以便控制应用程序的操作。
如果我尝试序列化我的类,它会告诉我我无法序列化一个接口。 我知道序列化程序不知道属性类的结构,只知道它实现了一个接口。
我原以为它会在成员上调用GetType,并反映实际类的结构。有没有办法实现这个目标? 还有另一种方法来实现我的要求吗?
修改:澄清我的意图: 让我们说我有这个课程:
class Car
{
IEngine engine
}
class ElectricEngine : IEngine
{
int batteryPrecentageLeft;
}
class InternalCombustionEngine : IEngine
{
int gasLitersLeft;
}
并且类用户有一个带
的类Car myCar = new Car();
myCar.Engine = new ElectricEngine() {batteryPrecentageLeft= 70};
当我序列化myCar类时,我希望xml类似于:
<Car>
<Engine>
<ElectricEngine>
<batteryPrecentageLeft>70</batteryPrecentageLeft>
</ElectricEngine>
<Engine>
</Car>
答案 0 :(得分:7)
您可以将该属性标记为do-not-include。
但是存在一个更深层次的问题:序列化只能捕获简单的“状态”,而不是行为。你的班级不是可序列化的。反序列化后您对该物业有什么“价值”? null
是唯一的选择。
正确的解决方法是考虑实际应该保存什么,并为该部分使用DTO。
可以序列化以下模型:
public class BaseEngine { }
[XmlInclude(typeof(InternalCombustionEngine))]
[XmlInclude(typeof(ElectricEngine))]
public class Car
{
public BaseEngine Engine { get; set; }
}
答案 1 :(得分:5)
也许您可以使用基类而不是接口并序列化它。
<强>更新强>
我意识到使用基类并不适合你。
最好的解决方案可能是与Henk Holterman所说的DTO做一个解决方法。
但如果你真的想要一个问题的解决方案,我认为你必须创建自己的自定义序列化器,但我不建议这样做,因为你最终会遇到很多错误。
以下是自定义序列化程序的示例,请记住此示例将 需要一些工作才能在实际应用中使用。
至少需要添加两件事才能使其不仅仅是一个例子:
anyThingProperty.SetValue(obj, propertyElement.Value, null);
[TestClass]
public class SerializableInterfaceTest
{
[TestMethod]
public void TestMethod1()
{
string serialize = AnyThingSerializer.Serialize(
new SerializableClass {Name = "test", Description = "test1",
AnyThing = new Animal {Name = "test", Color = "test1"}});
Console.WriteLine(serialize);
object obj = AnyThingSerializer.Deserialize(serialize);
}
}
public sealed class SerializableClass
{
public string Name { get; set; }
public string Description { get; set; }
[AnyThingSerializer]
public object AnyThing { get; set; }
}
public static class AnyThingSerializer
{
public static string Serialize(object obj)
{
Type type = obj.GetType();
var stringBuilder = new StringBuilder();
var serializer = new XmlSerializer(type);
serializer.Serialize(new StringWriter(stringBuilder), obj);
XDocument doc = XDocument.Load(new StringReader(stringBuilder.ToString()));
foreach (XElement xElement in SerializeAnyThing(obj))
{
doc.Descendants().First().Add(xElement);
}
return doc.ToString();
}
public static object Deserialize(string xml)
{
var serializer = new XmlSerializer(typeof (T));
object obj = serializer.Deserialize(new StringReader(xml));
XDocument doc = XDocument.Load(new StringReader(xml));
DeserializeAnyThing(obj, doc.Descendants().OfType().First());
return obj;
}
private static void DeserializeAnyThing(object obj, XElement element)
{
IEnumerable anyThingProperties = obj.GetType()
.GetProperties().Where(p => p.GetCustomAttributes(true)
.FirstOrDefault(a => a.GetType() ==
typeof (AnyThingSerializerAttribute)) != null);
foreach (PropertyInfo anyThingProperty in anyThingProperties)
{
XElement propertyElement = element.Descendants().FirstOrDefault(e =>
e.Name == anyThingProperty.Name && e.Attribute("type") != null);
if (propertyElement == null) continue;
Type type = Type.GetType(propertyElement.Attribute("type").Value);
if (IsSimpleType(type))
{
anyThingProperty.SetValue(obj, propertyElement.Value, null);
}
else
{
object childObject = Activator.CreateInstance(type);
DeserializeAnyThing(childObject, propertyElement);
anyThingProperty.SetValue(obj, childObject, null);
}
}
}
private static List SerializeAnyThing(object obj)
{
var doc = new List();
IEnumerable anyThingProperties =
obj.GetType().GetProperties().Where(p =>
p.GetCustomAttributes(true).FirstOrDefault(a =>
a.GetType() == typeof (AnyThingSerializerAttribute)) != null);
foreach (PropertyInfo anyThingProperty in anyThingProperties)
{
doc.Add(CreateXml(anyThingProperty.Name,
anyThingProperty.GetValue(obj, null)));
}
return doc;
}
private static XElement CreateXml(string name, object obj)
{
var xElement = new XElement(name);
Type type = obj.GetType();
xElement.Add(new XAttribute("type", type.AssemblyQualifiedName));
foreach (PropertyInfo propertyInfo in type.GetProperties())
{
object value = propertyInfo.GetValue(obj, null);
if (IsSimpleType(propertyInfo.PropertyType))
{
xElement.Add(new XElement(propertyInfo.Name, value.ToString()));
}
else
{
xElement.Add(CreateXml(propertyInfo.Name, value));
}
}
return xElement;
}
private static bool IsSimpleType(Type type)
{
return type.IsPrimitive || type == typeof (string);
}
}
public class AnyThingSerializerAttribute : XmlIgnoreAttribute
{
}
答案 2 :(得分:2)
基于@Jens解决方案,我创建了一个能够满足我需要的序列化器。谢谢Jen。 这是代码:
public class RuntimeXmlSerializerAttribute : XmlIgnoreAttribute { }
public class RuntimeXmlSerializer
{
private Type m_type;
private XmlSerializer m_regularXmlSerializer;
private const string k_FullClassNameAttributeName = "FullAssemblyQualifiedTypeName";
public RuntimeXmlSerializer(Type i_subjectType)
{
this.m_type = i_subjectType;
this.m_regularXmlSerializer = new XmlSerializer(this.m_type);
}
public void Serialize(object i_objectToSerialize, Stream i_streamToSerializeTo)
{
StringWriter sw = new StringWriter();
this.m_regularXmlSerializer.Serialize(sw, i_objectToSerialize);
XDocument objectXml = XDocument.Parse(sw.ToString());
sw.Dispose();
SerializeExtra(i_objectToSerialize,objectXml);
string res = objectXml.ToString();
byte[] bytesToWrite = Encoding.UTF8.GetBytes(res);
i_streamToSerializeTo.Write(bytesToWrite, 0, bytesToWrite.Length);
}
public object Deserialize(Stream i_streamToSerializeFrom)
{
string xmlContents = new StreamReader(i_streamToSerializeFrom).ReadToEnd();
StringReader sr;
sr = new StringReader(xmlContents);
object res = this.m_regularXmlSerializer.Deserialize(sr);
sr.Dispose();
sr = new StringReader(xmlContents);
XDocument doc = XDocument.Load(sr);
sr.Dispose();
deserializeExtra(res, doc);
return res;
}
private void deserializeExtra(object i_desirializedObject, XDocument i_xmlToDeserializeFrom)
{
IEnumerable propertiesToDeserialize = i_desirializedObject.GetType()
.GetProperties().Where(p => p.GetCustomAttributes(true)
.FirstOrDefault(a => a.GetType() ==
typeof(RuntimeXmlSerializerAttribute)) != null);
foreach (PropertyInfo prop in propertiesToDeserialize)
{
XElement propertyXml = i_xmlToDeserializeFrom.Descendants().FirstOrDefault(e =>
e.Name == prop.Name);
if (propertyXml == null) continue;
XElement propertyValueXml = propertyXml.Descendants().FirstOrDefault();
Type type = Type.GetType(propertyValueXml.Attribute(k_FullClassNameAttributeName).Value.ToString());
XmlSerializer srl = new XmlSerializer(type);
object deserializedObject = srl.Deserialize(propertyValueXml.CreateReader());
prop.SetValue(i_desirializedObject, deserializedObject, null);
}
}
private void SerializeExtra(object objectToSerialize, XDocument xmlToSerializeTo)
{
IEnumerable propertiesToSerialize =
objectToSerialize.GetType().GetProperties().Where(p =>
p.GetCustomAttributes(true).FirstOrDefault(a =>
a.GetType() == typeof(RuntimeXmlSerializerAttribute)) != null);
foreach (PropertyInfo prop in propertiesToSerialize)
{
XElement serializedProperty = new XElement(prop.Name);
serializedProperty.AddFirst(serializeObjectAtRuntime(prop.GetValue(objectToSerialize, null)));
xmlToSerializeTo.Descendants().First().Add(serializedProperty); //TODO
}
}
private XElement serializeObjectAtRuntime(object i_objectToSerialize)
{
Type t = i_objectToSerialize.GetType();
XmlSerializer srl = new XmlSerializer(t);
StringWriter sw = new StringWriter();
srl.Serialize(sw, i_objectToSerialize);
XElement res = XElement.Parse(sw.ToString());
sw.Dispose();
XAttribute fullClassNameAttribute = new XAttribute(k_FullClassNameAttributeName, t.AssemblyQualifiedName);
res.Add(fullClassNameAttribute);
return res;
}
}
答案 3 :(得分:1)
您可以使用ExtendedXmlSerializer。 如果您有课程:
public interface IEngine
{
string Name {get;set;}
}
public class Car
{
public IEngine Engine {get;set;}
}
public class ElectricEngine : IEngine
{
public string Name {get;set;}
public int batteryPrecentageLeft {get;set;}
}
public class InternalCombustionEngine : IEngine
{
public string Name {get;set;}
public int gasLitersLeft {get;set;}
}
并创建此类的实例:
Car myCar = new Car();
myCar.Engine = new ElectricEngine() {batteryPrecentageLeft= 70, Name = "turbo diesel"};
您可以使用ExtendedXmlSerializer序列化此对象:
ExtendedXmlSerializer serializer = new ExtendedXmlSerializer();
var xml = serializer.Serialize(myCar);
输出xml如下所示:
<?xml version="1.0" encoding="utf-8"?>
<Car type="Program+Car">
<Engine type="Program+ElectricEngine">
<Name>turbo diesel</Name>
<batteryPrecentageLeft>70</batteryPrecentageLeft>
</Engine>
</Car>
您可以从nuget安装ExtendedXmlSerializer或运行以下命令:
Install-Package ExtendedXmlSerializer