【问题标题】:How to serialize interface typed member如何序列化接口类型成员
【发布时间】:2016-10-03 18:43:13
【问题描述】:

我有一个具有定义为接口的属性的类。 我的类的用户可以将任何实现该接口的类实现分配给该属性。 我希望能够从磁盘上的文本文件加载此类状态。用户应该能够手动修改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>

【问题讨论】:

    标签: c# .net xml-serialization


    【解决方案1】:

    您可以将该属性标记为不包含。

    但还有一个更深层次的问题:序列化只能捕获简单的“状态”,而不是行为。您的课程不是可序列化的。您希望该属性在反序列化后具有什么“价值”? null 是唯一的选择。

    正确的解决方法是考虑实际应该保存哪些内容并为该部分使用 DTO。


    可以序列化以下模型:

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

    【讨论】:

    • 我不想将其标记为不包含,因为我需要对其进行序列化:)。您问“您希望该属性在反序列化后具有什么‘价值’?” - 与序列化之前的值相同。 (在我的问题中解释)
    • "与序列化之前的值相同" - 实现实例是否也序列化?
    • @HenkHolterman Windows 8 IE 10 … 意外。抱歉,我正在尝试撤消它。如果您编辑帖子,我可以更改它....对不起。
    • 好吧,有了 IE10 你就可以原谅了 :)
    • 我能够撤消反对票,但无法按原计划进行反对票。抱歉,我的浏览器最近一直在锁定,而且我在使用 Win8 时遇到了很多问题。
    【解决方案2】:

    也许您可以使用基类而不是接口并将其序列化。

    更新

    我意识到使用基类并不是你真正的选择。

    最好的解决方案可能是像 Henk Holterman 所说的那样使用 DTO 解决问题。

    但如果您真的想要解决您的问题,我认为您必须创建自己的自定义序列化程序,但我不建议这样做,因为您最终会遇到很多错误需要解决。

    这是自定义序列化程序的示例,请记住,此示例将 需要一些工作才能在实际应用程序中充分使用。

    至少要添加两件事才能使其不仅仅是一个示例:

    1. 异常处理
    2. anyThingProperty.SetValue(obj, propertyElement.Value, null); 行上将 xml 元素值转换或转换为正确的类型
    [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
    {
    }
    

    【讨论】:

    • 基本上我想避免使用基类,因为接口看起来更正确。另外,我事先不知道用户可能使用什么样的具体类型。 (我知道我需要在 XmlInclude 中声明它们)。我可以避免事先知道用户可能使用的类的类型吗?
    • @Henk,好的,我想我现在明白了这个问题,是的,这意味着使用 XmlInclude;这会让我的建议变得毫无价值。
    • @itaysk,我认为如果您对要序列化的类一无所知,则无法对其进行序列化。我会再考虑一下,看看我是否能想出一个可行的解决方案。
    • @JensGranlund,确实,我对要序列化的类一无所知。我想如果这个类实现了ISerializable,那是有可能的。
    • 使用interface IEngine : ISerializable 可能会起作用。但这是一个不同的序列化程序。
    【解决方案3】:

    基于@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;
        }
    }
    

    【讨论】:

      【解决方案4】:

      您可以使用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
      

      这里是online example

      【讨论】:

        猜你喜欢
        • 2011-03-07
        • 1970-01-01
        • 2010-12-08
        • 2014-06-07
        • 2014-07-01
        • 1970-01-01
        • 2012-08-11
        • 2022-11-22
        • 2019-03-05
        相关资源
        最近更新 更多