【问题标题】:XML serialization of interface property接口属性的 XML 序列化
【发布时间】:2010-11-22 23:05:14
【问题描述】:

我想对一个对象进行 XML 序列化,该对象具有(以及其他)IModelObject 类型的属性(这是一个接口)。

public class Example
{
    public IModelObject Model { get; set; }
}

当我尝试序列化此类的对象时,我收到以下错误:
“无法序列化成员 Example.Model 类型的 Example,因为它是一个接口。”

我了解问题在于接口无法序列化。但是,具体的 Model 对象类型在运行时之前是未知的。

用抽象或具体类型替换 IModelObject 接口并使用 XMLInclude 继承是可能的,但似乎是一个丑陋的解决方法。

有什么建议吗?

【问题讨论】:

    标签: c# .net serialization


    【解决方案1】:

    这只是声明性序列化的固有限制,其中类型信息未嵌入到输出中。

    关于尝试将<Flibble Foo="10" /> 转换回

    public class Flibble { public object Foo { get; set; } }
    

    序列化器如何知道它应该是一个 int、一个字符串、一个 double(或其他)...

    要完成这项工作,您有多种选择,但如果您真的不知道直到运行时,最简单的方法可能是使用XmlAttributeOverrides

    遗憾的是,这只适用于基类,不适用于接口。您可以做的最好的事情就是忽略不足以满足您的需求的属性。

    如果你真的必须使用界面,你有三个真正的选择:

    隐藏它并在另一个属性中处理它

    丑陋,令人不快的样板和大量重复,但该课程的大多数消费者不必处理这个问题:

    [XmlIgnore()]
    public object Foo { get; set; }
    
    [XmlElement("Foo")]
    [EditorVisibile(EditorVisibility.Advanced)]
    public string FooSerialized 
    { 
      get { /* code here to convert any type in Foo to string */ } 
      set { /* code to parse out serialized value and make Foo an instance of the proper type*/ } 
    }
    

    这很可能成为维护的噩梦......

    实现 IXmlSerializable

    与第一个选项类似,您可以完全控制事物,但

    • 优点
      • 您没有任何令人讨厌的“假”属性。
      • 您可以直接与 xml 结构交互,增加灵活性/版本控制
    • 缺点
      • 您可能最终不得不为类中的所有其他属性重新实现轮子

    重复工作的问题与第一个类似。

    修改您的属性以使用包装类型

    public sealed class XmlAnything<T> : IXmlSerializable
    {
        public XmlAnything() {}
        public XmlAnything(T t) { this.Value = t;}
        public T Value {get; set;}
    
        public void WriteXml (XmlWriter writer)
        {
            if (Value == null)
            {
                writer.WriteAttributeString("type", "null");
                return;
            }
            Type type = this.Value.GetType();
            XmlSerializer serializer = new XmlSerializer(type);
            writer.WriteAttributeString("type", type.AssemblyQualifiedName);
            serializer.Serialize(writer, this.Value);   
        }
    
        public void ReadXml(XmlReader reader)
        {
            if(!reader.HasAttributes)
                throw new FormatException("expected a type attribute!");
            string type = reader.GetAttribute("type");
            reader.Read(); // consume the value
            if (type == "null")
                return;// leave T at default value
            XmlSerializer serializer = new XmlSerializer(Type.GetType(type));
            this.Value = (T)serializer.Deserialize(reader);
            reader.ReadEndElement();
        }
    
        public XmlSchema GetSchema() { return(null); }
    }
    

    使用它会涉及到类似(在项目 P 中):

    public namespace P
    {
        public interface IFoo {}
        public class RealFoo : IFoo { public int X; }
        public class OtherFoo : IFoo { public double X; }
    
        public class Flibble
        {
            public XmlAnything<IFoo> Foo;
        }
    
    
        public static void Main(string[] args)
        {
            var x = new Flibble();
            x.Foo = new XmlAnything<IFoo>(new RealFoo());
            var s = new XmlSerializer(typeof(Flibble));
            var sw = new StringWriter();
            s.Serialize(sw, x);
            Console.WriteLine(sw);
        }
    }
    

    给你:

    <?xml version="1.0" encoding="utf-16"?>
    <MainClass 
      xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
      xmlns:xsd="http://www.w3.org/2001/XMLSchema">
     <Foo type="P.RealFoo, P, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null">
      <RealFoo>
       <X>0</X>
      </RealFoo>
     </Foo>
    </MainClass>
    

    虽然避免了很多样板,但这对于类用户来说显然更麻烦。

    一个愉快的媒介可能会将 XmlAnything 想法合并到第一种技术的“支持”属性中。通过这种方式,大部分繁重的工作都为您完成了,但该类的消费者不会受到任何影响,只会产生自省的困惑。

    【讨论】:

    • 我试图实现你的方法巫婆包装属性,但不幸的是有一个问题:(你能看看这篇文章吗,拜托:stackoverflow.com/questions/7584922/…
    • 有没有介绍FooSerialized属性的文章?
    【解决方案2】:

    解决方案是使用 DataContractSerializer 的反射。您甚至不必用 [DataContract] 或 [DataMember] 标记您的课程。它将任何对象序列化,无论它是否具有接口类型属性(包括字典)到 xml。这是一个简单的扩展方法,它将任何对象序列化为 XML,即使它具有接口(注意您也可以调整它以递归运行)。

        public static XElement ToXML(this object o)
        {
            Type t = o.GetType();
    
            Type[] extraTypes = t.GetProperties()
                .Where(p => p.PropertyType.IsInterface)
                .Select(p => p.GetValue(o, null).GetType())
                .ToArray();
    
            DataContractSerializer serializer = new DataContractSerializer(t, extraTypes);
            StringWriter sw = new StringWriter();
            XmlTextWriter xw = new XmlTextWriter(sw);
            serializer.WriteObject(xw, o);
            return XElement.Parse(sw.ToString());
        }
    

    LINQ 表达式的作用是枚举每个属性, 返回作为接口的每个属性, 获取该属性(基础对象)的值, 获取该具体对象的类型 将其放入数组中,并将其添加到序列化程序的已知类型列表中。

    现在序列化程序知道它正在序列化的类型如何,因此它可以完成它的工作。

    【讨论】:

    • 非常优雅且简单的问题解决方案。谢谢!
    • 这似乎不适用于通用 IList 和接口。例如IList。需要将 IMyInterface 的 concreate 值添加到 KnownTypes 但是,将添加 IList
    • @galford13x 我试图使这个例子尽可能简单,同时仍然证明了这一点。在任何情况下添加,例如递归或接口类型,都会使阅读变得不那么清晰,并且会偏离重点。请随时添加任何其他检查以提取所需的已知类型。老实说,我认为没有什么是您无法使用反射获得的。例如,这将获取泛型参数的类型,stackoverflow.com/questions/557340/…
    • 我明白了,我只提到这个是因为问题要求接口序列化。我想我会让其他人知道这个错误会在不修改的情况下发生,以防止他们撞到头。但是,我确实很欣赏您的代码,因为我添加了 [KnownType()] 属性,并且您的代码将我引向了结果。
    • 有没有办法在序列化时省略命名空间?我尝试改用 xmlwriter 来使用 xmlwriterSettings,我使用了可以传递附加类型的重载,但它不起作用...
    【解决方案3】:

    如果您事先了解您的接口实现者,那么您可以使用一个相当简单的技巧来让您的接口类型序列化,而无需编写任何解析代码:

    public interface IInterface {}
    public class KnownImplementor01 : IInterface {}
    public class KnownImplementor02 : IInterface {}
    public class KnownImplementor03 : IInterface {}
    public class ToSerialize {
      [XmlIgnore]
      public IInterface InterfaceProperty { get; set; }
      [XmlArray("interface")]
      [XmlArrayItem("ofTypeKnownImplementor01", typeof(KnownImplementor01))]
      [XmlArrayItem("ofTypeKnownImplementor02", typeof(KnownImplementor02))]
      [XmlArrayItem("ofTypeKnownImplementor03", typeof(KnownImplementor03))]
      public object[] InterfacePropertySerialization {
        get { return new[] { InterfaceProperty }; ; }
        set { InterfaceProperty = (IInterface)value.Single(); }
      }
    }
    

    生成的 xml 应该看起来像

     <interface><ofTypeKnownImplementor01><!-- etc... -->
    

    【讨论】:

    • 非常有用,谢谢。在大多数情况下,我知道实现接口的类。这个答案应该更高。
    • 这是最简单的解决方案。谢谢!
    【解决方案4】:

    您可以使用ExtendedXmlSerializer。这个序列化器支持接口属性的序列化,没有任何技巧。

    var serializer = new ConfigurationContainer().UseOptimizedNamespaces().Create();
    
    var obj = new Example
                    {
                        Model = new Model { Name = "name" }
                    };
    
    var xml = serializer.Serialize(obj);
    

    您的 xml 将如下所示:

    <?xml version="1.0" encoding="utf-8"?>
    <Example xmlns:exs="https://extendedxmlserializer.github.io/v2" xmlns="clr-namespace:ExtendedXmlSerializer.Samples.Simple;assembly=ExtendedXmlSerializer.Samples">
        <Model exs:type="Model">
            <Name>name</Name>
        </Model>
    </Example>
    

    ExtendedXmlSerializer 支持 .net 4.5 和 .net Core。

    【讨论】:

      【解决方案5】:

      用抽象或具体类型替换 IModelObject 接口并使用 XMLInclude 继承是可能的,但似乎是一个丑陋的解决方法。

      如果可以使用抽象基础,我会推荐这条路线。它仍然比使用手动序列化更干净。我看到抽象基础的唯一问题是您仍然需要具体类型吗?至少我过去是这样使用它的,比如:

      public abstract class IHaveSomething
      {
          public abstract string Something { get; set; }
      }
      
      public class MySomething : IHaveSomething
      {
          string _sometext;
          public override string Something 
          { get { return _sometext; } set { _sometext = value; } }
      }
      
      [XmlRoot("abc")]
      public class seriaized
      {
          [XmlElement("item", typeof(MySomething))]
          public IHaveSomething data;
      }
      

      【讨论】:

        【解决方案6】:

        不幸的是,没有简单的答案,因为序列化程序不知道要为接口序列化什么。我在MSDN 上找到了有关如何解决此问题的更完整说明

        【讨论】:

          【解决方案7】:

          不幸的是,我有一个案例,要序列化的类的属性也具有接口作为属性,因此我需要递归处理每个属性。此外,一些接口属性被标记为 [XmlIgnore],所以我想跳过这些。我接受了在这个线程上找到的想法,并在其中添加了一些东西以使其具有递归性。这里只展示反序列化代码:

          void main()
          {
              var serializer = GetDataContractSerializer<MyObjectWithCascadingInterfaces>();
              using (FileStream stream = new FileStream(xmlPath, FileMode.Open))
              {
                  XmlDictionaryReader reader = XmlDictionaryReader.CreateTextReader(stream, new XmlDictionaryReaderQuotas());
                  var obj = (MyObjectWithCascadingInterfaces)serializer.ReadObject(reader);
          
                  // your code here
              }
          }
          
          DataContractSerializer GetDataContractSerializer<T>() where T : new()
          {
              Type[] types = GetTypesForInterfaces<T>();
          
              // Filter out duplicates
              Type[] result = types.ToList().Distinct().ToList().ToArray();
          
              var obj = new T();
              return new DataContractSerializer(obj.GetType(), types);
          }
          
          Type[] GetTypesForInterfaces<T>() where T : new()
          {
              return GetTypesForInterfaces(typeof(T));
          }
          
          Type[] GetTypesForInterfaces(Type T)
          {
              Type[] result = new Type[0];
              var obj = Activator.CreateInstance(T);
          
              // get the type for all interface properties that are not marked as "XmlIgnore"
              Type[] types = T.GetProperties()
                  .Where(p => p.PropertyType.IsInterface && 
                      !p.GetCustomAttributes(typeof(System.Xml.Serialization.XmlIgnoreAttribute), false).Any())
                  .Select(p => p.GetValue(obj, null).GetType())
                  .ToArray();
          
              result = result.ToList().Concat(types.ToList()).ToArray();
          
              // do the same for each of the types identified
              foreach (Type t in types)
              {
                  Type[] embeddedTypes = GetTypesForInterfaces(t);
                  result = result.ToList().Concat(embeddedTypes.ToList()).ToArray();
              }
              return result;
          }
          

          【讨论】:

            【解决方案8】:

            感谢这里的博客,我找到了一个更简单的解决方案(您不需要 DataContractSerializer): XML serializing derived types when base type is in another namespace or DLL

            但是在这个实现中可能会出现两个问题:

            (1)如果DerivedBase不在类Base的命名空间中,或者更糟糕的是在依赖Base命名空间的项目中,那么Base不能XMLInclude DerivedBase

            (2) 如果我们只有类 Base 作为 dll 怎么办,那么 Base 又不能 XMLInclude DerivedBase

            到目前为止,...

            所以解决这两个问题的方法是使用 XmlSerializer Constructor (Type, array[]) :

            XmlSerializer ser = new XmlSerializer(typeof(A), new Type[]{ typeof(DerivedBase)});
            

            MSDN 上提供了详细示例: XmlSerializer Constructor (Type, extraTypesArray[])

            在我看来,对于 DataContracts 或 Soap XML,您需要 check the XmlRoot as mentioned here in this SO question

            一个similar answer is here on SO,但它没有被标记为一个,因为它不是 OP 似乎已经考虑过它。

            【讨论】:

              【解决方案9】:

              在我的项目中,我有一个
              列出 FormatStyleTemplates;
              包含不同的类型。

              然后我使用上面的解决方案“XmlAnything”来序列化这个不同类型的列表。 生成的xml很漂亮。

                  [Browsable(false)]
                  [EditorBrowsable(EditorBrowsableState.Never)]
                  [XmlArray("FormatStyleTemplates")]
                  [XmlArrayItem("FormatStyle")]
                  public XmlAnything<IFormatStyle>[] FormatStyleTemplatesXML
                  {
                      get
                      {
                          return FormatStyleTemplates.Select(t => new XmlAnything<IFormatStyle>(t)).ToArray();
                      }
                      set
                      {
                          // read the values back into some new object or whatever
                          m_FormatStyleTemplates = new FormatStyleProvider(null, true);
                          value.ForEach(t => m_FormatStyleTemplates.Add(t.Value));
                      }
                  }
              

              【讨论】:

                猜你喜欢
                • 2012-02-27
                • 1970-01-01
                • 1970-01-01
                • 2011-06-19
                • 2020-09-15
                • 1970-01-01
                • 1970-01-01
                相关资源
                最近更新 更多