简体   繁体   English

如何序列化接口类型成员

[英]How to serialize interface typed member

I have a class that has a property that is defined as interface. 我有一个具有定义为接口的属性的类。 Users of my class can assign to this property any class implementation that implements the interface. 我的类的用户可以为此属性分配任何实现该接口的类实现。 I want to be able to load this class state from a textual file on disk. 我希望能够从磁盘上的文本文件加载此类状态。 Users should be able to manually modify the xml file, in order to control the application's operation. 用户应该能够手动修改xml文件,以便控制应用程序的操作。

If I try to serialize my class, it tells me I cannot serialize an interface. 如果我尝试序列化我的类,它会告诉我我无法序列化一个接口。 I understand that the serializer has no knowledge about the structure of the property's class, knowing only that it implements an interface. 我知道序列化程序不知道属性类的结构,只知道它实现了一个接口。

I would have expected it to call GetType on the member, and reflect into the structure of the actual class. 我原以为它会在成员上调用GetType,并反映实际类的结构。 Is there a way to achieve this? 有没有办法实现这个目标? Is there another way to implement my requirement? 还有其他方法可以实现我的要求吗?

Edit : Clarifying my intentions: Lets say I have this class: 编辑 :澄清我的意图:让我说我有这个课程:

class Car
{
IEngine engine
}
class ElectricEngine : IEngine 
{
int batteryPrecentageLeft;
}
class InternalCombustionEngine : IEngine 
{
int gasLitersLeft;
}

and the class user has a class with 并且类用户有一个类

Car myCar = new Car();
myCar.Engine = new ElectricEngine() {batteryPrecentageLeft= 70};

When I serialize the class myCar, I expect the the xml to resemble this: 当我序列化myCar类时,我希望xml类似于:

<Car>
<Engine>
<ElectricEngine>
<batteryPrecentageLeft>70</batteryPrecentageLeft>
</ElectricEngine>
<Engine>
</Car>

You can mark the property as do-not-include. 您可以将该属性标记为do-not-include。

There is a deeper problem though: Serialization can only capture simple 'state', not behaviour. 但是存在一个更深层次的问题:序列化只能捕获简单的“状态”,而不是行为。 Your class is not of the serializable kind. 你的班级不是可序列化的。 What 'value' do you expect the property to have after deserialization? 反序列化后您对该物业有什么“价值”? null is the only option. null是唯一的选择。

The proper workaround would be to think about What should actually be save and use a DTO for that part. 正确的解决方法是考虑实际应该保存什么,并为该部分使用DTO。


The following model can be serialized: 可以序列化以下模型:

public class BaseEngine { }

[XmlInclude(typeof(InternalCombustionEngine))]
[XmlInclude(typeof(ElectricEngine))]
public class Car
{      
    public BaseEngine Engine { get; set; }
}

Maybe you could use a base class instead of an interface and serialize that. 也许你可以使用基类而不是接口并序列化它。

Update 更新

I realized that using a base class was not really an option for you. 我意识到使用基类对你来说并不是一个真正的选择。

The best solution would probably be to do a workaround with a DTO like Henk Holterman stated. 最好的解决方案可能是像Henk Holterman所说的DTO做一个解决方法。

But if you really want a solution for your question I think you would have to create your own custom serializer, but I would not recommend that because you would end up with a lot of bugs to sort out. 但是如果你真的想要一个问题的解决方案,我认为你必须创建自己的自定义序列化程序,但我不建议这样做,因为你最终会遇到很多错误需要解决。

Here is a example for a custom serializer, keep in mind that this example will need some work to be use full in a real application. 以下是自定义序列化程序的示例,请记住,此示例需要在实际应用程序中完整使用。

At least two things has to be added for this to work for more than just an example: 至少需要添加两件事才能使其不仅仅是一个例子:

  1. Exception handling 异常处理
  2. Casting or converting the xml element value to correct type on the line anyThingProperty.SetValue(obj, propertyElement.Value, null); 将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
{
}

Based on @Jens solution I have created a serializer that does what I need. 基于@Jens解决方案,我创建了一个可以满足我需要的序列化器。 Thanks Jen. 谢谢Jen。 Here is the code: 这是代码:

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

You can use ExtendedXmlSerializer . 您可以使用ExtendedXmlSerializer If you have a classes: 如果您有课程:

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

and create instance of this class: 并创建此类的实例:

Car myCar = new Car();
myCar.Engine = new ElectricEngine() {batteryPrecentageLeft= 70, Name = "turbo diesel"};

You can serialize this object using ExtendedXmlSerializer: 您可以使用ExtendedXmlSerializer序列化此对象:

ExtendedXmlSerializer serializer = new ExtendedXmlSerializer();
var xml = serializer.Serialize(myCar);

Output xml will look like: 输出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>

You can install ExtendedXmlSerializer from nuget or run the following command: 您可以从nuget安装ExtendedXmlSerializer或运行以下命令:

Install-Package ExtendedXmlSerializer

Here is online example 这是在线示例

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM