C# 接口属性的 XML 序列化
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/1333864/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
XML serialization of interface property
提问by Elad
I would like to XML serialize an object that has (among other) a property of type IModelObject(which is an interface).
我想 XML 序列化一个对象,该对象具有(除其他外)IModelObject类型的属性(它是一个接口)。
public class Example
{
public IModelObject Model { get; set; }
}
When I try to serialize an object of this class, I receive the following error:
"Cannot serialize member Example.Model of type Example because it is an interface."
当我尝试序列化此类的对象时,收到以下错误:
“无法序列化 Example 类型的成员 Example.Model,因为它是一个接口。”
I understand that the problem is that an interface cannot be serialized. However, the concrete Modelobject type is unknown until runtime.
我知道问题是接口不能被序列化。但是,具体的Model对象类型直到运行时才知道。
Replacing the IModelObjectinterface with an abstract or concrete type and use inheritance with XMLInclude is possible, but seems like an ugly workaround.
用抽象或具体类型替换IModelObject接口并使用 XMLInclude 继承是可能的,但似乎是一种丑陋的解决方法。
Any suggestions?
有什么建议?
采纳答案by ShuggyCoUk
This is simply an inherent limitation of declarative serialization where type information is not embedded within the output.
这只是声明式序列化的固有限制,其中类型信息未嵌入输出中。
On trying to convert <Flibble Foo="10" />
back into
在尝试转换<Flibble Foo="10" />
回
public class Flibble { public object Foo { get; set; } }
How does the serializer know whether it should be an int, a string, a double (or something else)...
序列化程序如何知道它是否应该是 int、string、double(或其他)...
To make this work you have several options but if you truly don't know till runtime the easiest way to do this is likely to be using the XmlAttributeOverrides.
要完成这项工作,您有多种选择,但如果您真的不知道直到运行时,最简单的方法可能是使用XmlAttributeOverrides。
Sadly this will only work with base classes, not interfaces. The best you can do there is to ignore the property which isn't sufficient for your needs.
遗憾的是,这只适用于基类,而不适用于接口。您可以做的最好的事情就是忽略不足以满足您需求的属性。
If you really must stay with interfaces you have three real options:
如果你真的必须坚持使用接口,你有三个真正的选择:
Hide it and deal with it in another property
隐藏它并在另一个属性中处理它
Ugly, unpleasant boiler plate and much repetition but most consumers of the class will not have to deal with the problem:
丑陋,令人不快的样板和大量重复,但该类的大多数消费者不必处理这个问题:
[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*/ }
}
This is likely to become a maintenance nightmare...
这很可能会成为维护的噩梦……
Implement IXmlSerializable
实现 IXmlSerializable
Similar to the first option in that you take full control of things but
类似于第一个选项,您可以完全控制事物,但
- Pros
- You don't have nasty 'fake' properties hanging around.
- you can interact directly with the xml structure adding flexibility/versioning
- Cons
- you may end up having to re-implement the wheel for all the other properties on the class
- 优点
- 您没有令人讨厌的“假”属性。
- 您可以直接与 xml 结构交互,增加灵活性/版本控制
- 缺点
- 您最终可能不得不为类上的所有其他属性重新实现轮子
Issues of duplication of effort are similar to the first.
重复劳动的问题与第一个类似。
Modify your property to use a wrapping type
修改您的属性以使用包装类型
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); }
}
Using this would involve something like (in project P):
使用它会涉及到类似的东西(在项目 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);
}
}
which gives you:
这给了你:
<?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>
This is obviously more cumbersome for users of the class though avoids much boiler plate.
这对于类的用户来说显然更麻烦,但避免了很多样板。
A happy medium may be merging the XmlAnything idea into the 'backing' property of the first technique. In this way most of the grunt work is done for you but consumers of the class suffer no impact beyond confusion with introspection.
一个愉快的媒介可能是将 XmlAnything 的想法合并到第一种技术的“支持”属性中。通过这种方式,大部分繁重的工作都是为您完成的,但该类的消费者除了自省的困惑之外不会受到任何影响。
回答by MattH
回答by csharptest.net
Replacing the IModelObject interface with an abstract or concrete type and use inheritance with XMLInclude is possible, but seems like an ugly workaround.
用抽象或具体类型替换 IModelObject 接口并使用 XMLInclude 继承是可能的,但似乎是一种丑陋的解决方法。
If it is possible to use an abstract base I would recommend that route. It will still be cleaner than using hand-rolled serialization. The only trouble I see with the abstract base is that your still going to need the concrete type? At least that is how I've used it in the past, something like:
如果可以使用抽象基础,我会推荐该路线。它仍然比使用手动序列化更干净。我在抽象基础上看到的唯一麻烦是您仍然需要具体类型?至少这是我过去使用它的方式,例如:
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;
}
回答by Despertar
The solution to this is using reflection with the DataContractSerializer. You don't even have to mark your class with [DataContract] or [DataMember]. It will serialize any object, regardless of whether it has interface type properties (including dictionaries) into xml. Here is a simple extension method that will serialize any object into XML even if it has interfaces (note you could tweak this to run recursively as well).
对此的解决方案是将反射与 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());
}
what the LINQ expression does is it enumerates each property, returns each property that is an interface, gets the value of that property (the underlying object), gets the type of that concrete object puts it into an array, and adds that to the serializer's list of known types.
LINQ 表达式的作用是枚举每个属性,返回作为接口的每个属性,获取该属性(底层对象)的值,获取该具体对象的类型并将其放入数组中,并将其添加到序列化程序的已知类型列表。
Now the serializer knows how about the types it is serializing so it can do its job.
现在序列化器知道它正在序列化的类型如何,所以它可以完成它的工作。
回答by Detlef Kroll
in my project, I have a
List<IFormatStyle> FormatStyleTemplates;
containing different Types.
在我的项目中,我有一个
List<IFormatStyle> FormatStyleTemplates;
包含不同的类型。
I then use the solution 'XmlAnything' from above, to serialize this list of different types. The generated xml is beautiful.
然后我使用上面的解决方案“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));
}
}
回答by hannasm
If you know your interface implementors up-front there's a fairly simple hack you can use to get your interface type to serialize without writing any parsing code:
如果你预先知道你的接口实现者,那么你可以使用一个相当简单的技巧来让你的接口类型序列化,而无需编写任何解析代码:
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(); }
}
}
The resulting xml should look something along the lines of
生成的 xml 应该看起来像
<interface><ofTypeKnownImplementor01><!-- etc... -->
回答by Wojtpl2
You can use ExtendedXmlSerializer. This serializer support serialization of interface property without any tricks.
您可以使用ExtendedXmlSerializer。这个序列化器支持接口属性的序列化,没有任何技巧。
var serializer = new ConfigurationContainer().UseOptimizedNamespaces().Create();
var obj = new Example
{
Model = new Model { Name = "name" }
};
var xml = serializer.Serialize(obj);
Your xml will look like:
您的 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 support .net 4.5 and .net Core.
ExtendedXmlSerializer 支持 .net 4.5 和 .net Core。
回答by acordner
Unfortuantely for me, I had a case where the class to be serialized had properties that had interfaces as properties as well, so I needed to recursively process each property. Also, some of the interface properties were marked as [XmlIgnore], so I wanted to skip over those. I took ideas that I found on this thread and added some things to it to make it recursive. Only the deserialization code is shown here:
对我来说不幸的是,我有一个案例,要序列化的类的属性也有接口作为属性,所以我需要递归处理每个属性。另外,一些接口属性被标记为 [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;
}
回答by B Charles H
I have found a simpler solution (you don't need the DataContractSerializer), thanks to this blog here: XML serializing derived types when base type is in another namespace or DLL
我找到了一个更简单的解决方案(您不需要 DataContractSerializer),这要归功于这里的博客: XML serializingderived types when base type is in another namespace or DLL
But 2 problems can rise in this implementation:
(1) What if DerivedBase is not in the namespace of class Base, or even worse in a project that depends on Base namespace, so Base cannot XMLInclude DerivedBase
(2) What if we only have class Base as a dll ,so again Base cannot XMLInclude DerivedBase
Till now, ...
So the solution to the 2 problems is by using XmlSerializer Constructor (Type, array[]):
但是在这个实现中可能会出现两个问题:
(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)});
A detailed example is provided here on MSDN: XmlSerializer Constructor (Type, extraTypesArray[])
MSDN 上提供了一个详细示例: XmlSerializer Constructor (Type, extraTypesArray[])
It seems to me that for DataContracts or Soap XMLs, you need to check the XmlRoot as mentioned here in this SO question.
在我看来,对于 DataContracts 或 Soap XMLs,您需要检查 XmlRoot,如在此 SO 问题中所述。
A similar answer is here on SObut it isn't marked as one, as it not the OP seems to have considered it already.