接口属性的 XML 序列化

Posted

技术标签:

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

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

public class Example

    public IModelObject Model  get; set; 

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

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

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

有什么建议吗?

【问题讨论】:

【参考方案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 想法合并到第一种技术的“支持”属性中。通过这种方式,大部分繁重的工作都为您完成了,但该类的消费者不会受到任何影响,只会产生自省的困惑。

【讨论】:

我试图实现你的方法巫婆包装属性,但不幸的是有一个问题:(你能看看这篇文章吗,拜托:***.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 我试图使这个例子尽可能简单,同时仍然证明了这一点。在任何情况下添加,例如递归或接口类型,都会使阅读变得不那么清晰,并且会偏离重点。请随时添加任何其他检查以提取所需的已知类型。老实说,我认为没有什么是您无法使用反射获得的。例如,这将获取泛型参数的类型,***.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));
        
    

【讨论】:

以上是关于接口属性的 XML 序列化的主要内容,如果未能解决你的问题,请参考以下文章

JAXB - java xml解析

如何使用 Xml 属性序列化带有节点集合的 XML

DataContract XML 序列化和 XML 属性

在 C# 中删除 XML 序列化的 DefaultValue 属性

在 xml 序列化期间忽略属性,但在反序列化期间不忽略

反序列化带有属性的 XML 数组