如何序列化接口类型化成员
本文关键字:类型化 成员 接口类型 接口 序列化 | 更新日期: 2023-09-27 18:31:45
我有一个类,它有一个定义为接口的属性。我的类的用户可以将实现接口的任何类实现分配给此属性。我希望能够从磁盘上的文本文件加载此类状态。用户应该能够手动修改 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>
可以将属性标记为不包含。
但还有一个更深层次的问题:序列化只能捕获简单的"状态",而不能捕获行为。您的类不属于可序列化的类型。您希望该属性在反序列化后具有什么"价值"? null
是唯一的选择。
正确的解决方法是考虑实际应该保存的内容,并为该部分使用 DTO。
可以序列化以下模型:
public class BaseEngine { }
[XmlInclude(typeof(InternalCombustionEngine))]
[XmlInclude(typeof(ElectricEngine))]
public class Car
{
public BaseEngine Engine { get; set; }
}
也许您可以使用基类而不是接口并对其进行序列化。
更新
我意识到使用基类并不是您的真正选择。
最好的解决方案可能是像Henk Holterman所说的那样使用DTO进行解决方法。
但是,如果您真的想要解决您的问题,我认为您必须创建自己的自定义序列化程序,但我不建议这样做,因为您最终会有很多错误需要解决。
下面是自定义序列化程序的示例,请记住,此示例将需要一些工作才能在实际应用程序中完全使用。
至少必须添加两件事才能使其不仅仅是一个示例:
- 异常处理
- 将 xml 元素值强制转换为行
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
{
}
基于@Jens解决方案,我创建了一个序列化程序,可以满足我的需要。谢谢珍。这是代码:
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;
}
}
您可以使用 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
这是在线示例