接口属性的XML序列化

我想XML序列化具有(其他) IModelObject (这是一个接口)types的属性的对象。

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

当我尝试序列化这个类的对象时,我收到以下错误:
“由于它是一个接口,所以不能序列化Exampletypes的Example.Model”。

我明白,问题是一个接口不能被序列化。 但是,具体的Model对象types在运行时才是未知的。

用一个抽象的或具体的typesreplaceIModelObject接口,并使用XMLIncludeinheritance是可能的,但似乎是一个丑陋的解决方法。

有什么build议么?

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

尝试将<Flibble Foo="10" />

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

序列化程序如何知道它是否应该是一个int,一个string,一个双(或其他)…

为了使这个工作,你有几个选项,但如果你真的不知道直到运行时,最简单的方法可能是使用XmlAttributeOverrides 。

可悲的是,这只适用于基类,而不是接口。 你可以做的最好的就是忽略那些不足以满足你需求的财产。

如果你真的必须保持接口,你有三个真正的select:

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

丑陋的,不愉快的锅炉板和很多重复,但大多数类的消费者将不必处理的问题:

 [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结构交互,增加灵活性/版本
  • 缺点
    • 您可能最终不得不重新实施课程中的所有其他属性

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

修改您的财产使用包装types

 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> 

虽然避免了太多的锅炉板,但这对于class的用户来说显然是比较麻烦的。

一个幸福的媒介可能会将XmlAnything的想法合并到第一种技术的“支持”属性中。 通过这种方式,大部分的烦琐的工作都是为你而做的,但是这个阶级的消费者不会受到内省的混乱和冲击。

解决scheme是使用DataContractSerializer进行reflection。 你甚至不必用[DataContract]或[DataMember]标记你的class级。 它将序列化任何对象,而不pipe它是否具有接口types属性(包括字典)到xml中。 这是一个简单的扩展方法,即使它有接口,也可以将任何对象序列化为XML(注意你可以调整它以recursion方式运行)。

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

LINQexpression式所做的是枚举每个属性,返回作为接口的每个属性,获取该属性(基础对象)的值,获取该具体对象的types将其放入数组,并将其添加到序列化器已知types的列表。

现在序列化器知道它正在序列化的types,所以它可以完成它的工作。

用一个抽象的或具体的typesreplaceIModelObject接口,并使用XMLIncludeinheritance是可能的,但似乎是一个丑陋的解决方法。

如果有可能使用抽象的基础,我会build议路线。 它仍然比使用手动序列化更清洁。 我在抽象基础上看到的唯一困难是你还需要具体的types吗? 至less我以前是这么用的,比如:

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

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

 ExtendedXmlSerializer serializer = new ExtendedXmlSerializer(); var obj = new Example { Model = new Model {Id = 2} }; var xml = serializer.Serialize(obj); 

你的xml将如下所示:

 <?xml version="1.0" encoding="utf-8"?> <Example type="Models.Example"> <Model type="Models.Model"> <Id>2</Id> </Model> </Example> 

ExtendedXmlSerializer支持.net 4.5和.net Core。 您可以将其与WebApi和AspCore集成。

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

如果你事先知道你的接口实现,那么你可以用一个相当简单的方法来让你的接口types序列化,而不需要编写任何parsing代码:

 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... --> 

在我的项目中,我有一个
List <IFormatStyle> FormatStyleTemplates;
包含不同的types。

然后我使用上面的解决scheme'XmlAnything'来序列化这个不同types的列表。 生成的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)); } }