接口属性的 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如果您事先了解您的接口实现者,那么您可以使用一个相当简单的技巧来让您的接口类型序列化,而无需编写任何解析代码:
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】:在我的项目中,我有一个
列出
然后我使用上面的解决方案“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 序列化的主要内容,如果未能解决你的问题,请参考以下文章