如何使用派生 class 进行 XML 序列化?
How to use derived class for XML serialization?
我正在转换我的工作 XML 序列化,以便模型 classes 从抽象基础 classes 继承(以允许将来使用不同的序列格式)。
我的序列化工作正常,但是当我切换到使用从基础派生的模型时 class 我遇到了各种异常,所以我不确定如何继续。
我的 class 基础 class 是:
namespace Data
{
public abstract class Configuration
{
public abstract string Schema { get; set; }
public abstract Command[] Commands { get; set; }
}
public abstract class Command
{
public abstract string Name { get; set; }
}
}
我的派生具体 class(class 在派生之前工作得很好)然后在子名称空间中:
namespace Data.Xml
{
[Serializable()]
[XmlType(AnonymousType = true)]
[XmlRoot(Namespace = "", IsNullable = false)]
public class Configuration : Data.Configuration
{
[XmlAttribute("noNamespaceSchemaLocation",
Namespace = System.Xml.Schema.XmlSchema.InstanceNamespace)]
public override string Schema { get; set; }
[XmlArrayItem("Command", IsNullable = false)]
public override Data.Command[] Commands { get; set; }
}
[Serializable()]
public class Command : Data.Command
{
public override string Name { get; set; }
}
}
我在该子命名空间中调用序列化程序,如下所示:
public override Data.Configuration DeserializeConfig(StreamReader config)
{
var cs = new XmlSerializer(typeof(Configuration),
new Type[] { typeof(Command) });
return (Configuration)ConfigSerializer.Deserialize(ConfigStreamReader);
}
public override string SerializeConfig(Data.Configuration c, Encoding encoding)
{
string Output = null;
var Stream = new MemoryStream();
using (var Writer = new XmlTextWriter(Stream, encoding))
{
Writer.Formatting = Formatting.Indented;
XmlSerializerNamespaces ns = new XmlSerializerNamespaces();
ns.Add("xsi", XmlSchema.InstanceNamespace);
(new XmlSerializer(typeof(Configuration))).Serialize(Writer, c, ns);
Output = encoding.GetString(Stream.ToArray());
}
Stream.Dispose();
return Output;
}
结果 XML 应如下所示:
<?xml version="1.0" encoding="utf-8"?>
<Configuration
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="SomeSchema.xsd">
<Commands>
<Command>
<Name>SomeNameValue</Name>
</Command>
</Commands>
</Configuration>
我在尝试实例化序列化程序时看到以下异常(上面 DeserializeConfig()
方法中的第一行):
InvalidOperationException: Types 'Data.Command' and 'Data.Xml.Command' both use the XML type name, 'Command', from namespace ''. Use XML attributes to specify a unique XML name and/or namespace for the type.
我不太确定为什么序列化器试图从基础创建一些东西 class,确定名称是相同的,这是派生和命名空间等的想法...... 如何使用属性正确标记它以使其正确 de/serialize?
仅供参考:我确实看到了几个关于这个的问题,但答案似乎都足够具体到提问者的要求,我无法弄清楚如何将这些信息应用到这个,看似简单的场景。
更新: 我想出了如何在实例化时将包含的类型传递给序列化程序,而不必注释基础 class 所以我已经从我的问题并更新了代码。这已经过时了布鲁诺的建议和我的回应(尽管建议的问题仍然不适用)。
更新: 我试图通过将派生的 class 添加到名称空间来分隔 XML 名称空间中的名称(即添加 [XmlElement(Namespace = "http://www.foo.com/data/xml")]
对于派生 class 中的每个 属性)但这没有任何区别,因为序列化程序似乎仍然 "see" 基础和派生 class 在一起,所以认为它们都是在该名称空间中。
最后翻转想通了大部分内容。
我退后一步,从一个非常简单的工作非派生示例开始,并根据我的需要进行工作。
这里发生了两件事。首先是冲突的类型名称,然后是冲突的 属性 名称。虽然我对其中的每一个都有一些了解,但组合在一起时用于构建每个选项的选项排列数量让我感到困惑。
为了防止抽象类型和派生类型名称在序列化时发生冲突,我需要使派生 class 类型匿名(此处使用 XmlType
属性)。
为了阻止 属性 名称冲突,我需要忽略派生 class 中的 both 属性 and 基地 class。要在不编辑基础 class 的情况下做到这一点,我错过了一个重要的部分,XmlAttributeOverrides
。我曾在 XmlSerializer.Serialize()
的 MSDN 文档中看到过这一点,但那里的信息在解释它的相关内容方面非常少。 to another question led me to David Woodward's excellent explanation.
我还没有用派生类型列表 属性 或反序列化尝试过任何一个。
下面是一个程序的完整基本示例,它在控制台输出中输出带有一些序列化 XML 的字符串:
using System;
using System.Text;
using System.IO;
using System.Xml;
using System.Xml.Serialization;
namespace Test
{
class Program
{
static void Main(string[] args)
{
var TestBar = new MyXml.Bar()
{
Name = "John Smith",
};
Serializer s = new MyXml.Serializer();
var TestOutput = s.Serialize(TestBar);
Console.WriteLine(TestOutput);
}
}
public abstract class Bar
{
public abstract string Name { get; set; }
}
public abstract class Serializer
{
public abstract string Serialize(Bar bar);
}
namespace MyXml
{
public class Serializer : Test.Serializer
{
public override string Serialize(Test.Bar bar)
{
string Output = null;
var Stream = new MemoryStream();
var Encoding = new UTF8Encoding(false, true);
// Ignore the Name property in the *base* class!
var ao = new XmlAttributeOverrides();
var a = new XmlAttributes();
a.XmlElements.Clear(); // Clear any element attributes
a.XmlAttribute = null; // Remove any attribute attributes
a.XmlIgnore = true; // Set the ignore attribute value true
ao.Add(typeof(Test.Bar), "Name", a); // Set to use with Test.Bar.Name
using (var Writer = new XmlTextWriter(Stream, Encoding))
{
Writer.Formatting = Formatting.Indented;
var s = new XmlSerializer(typeof(Bar), ao);
s.Serialize(Writer, bar);
Output = Encoding.GetString(Stream.ToArray());
}
Stream.Dispose();
return Output;
}
}
[Serializable]
[XmlType(AnonymousType = true)] // Make type anonymous!
[XmlRoot(IsNullable = false)]
public class Bar : Test.Bar
{
[XmlIgnore] // Ignore the Name property in the *derived* class!
public override string Name
{
get => Unreverse(ReverseName);
set => ReverseName = Reverse(value);
}
[XmlElement("Name", IsNullable = false)]
public string ReverseName { get; set; }
private string Unreverse(string name)
{
return "John Smith"; // Smith, John -> John Smith
}
private string Reverse(string name)
{
return "Smith, John"; // John Smith -> Smith, John
}
}
}
}
我正在转换我的工作 XML 序列化,以便模型 classes 从抽象基础 classes 继承(以允许将来使用不同的序列格式)。
我的序列化工作正常,但是当我切换到使用从基础派生的模型时 class 我遇到了各种异常,所以我不确定如何继续。
我的 class 基础 class 是:
namespace Data
{
public abstract class Configuration
{
public abstract string Schema { get; set; }
public abstract Command[] Commands { get; set; }
}
public abstract class Command
{
public abstract string Name { get; set; }
}
}
我的派生具体 class(class 在派生之前工作得很好)然后在子名称空间中:
namespace Data.Xml
{
[Serializable()]
[XmlType(AnonymousType = true)]
[XmlRoot(Namespace = "", IsNullable = false)]
public class Configuration : Data.Configuration
{
[XmlAttribute("noNamespaceSchemaLocation",
Namespace = System.Xml.Schema.XmlSchema.InstanceNamespace)]
public override string Schema { get; set; }
[XmlArrayItem("Command", IsNullable = false)]
public override Data.Command[] Commands { get; set; }
}
[Serializable()]
public class Command : Data.Command
{
public override string Name { get; set; }
}
}
我在该子命名空间中调用序列化程序,如下所示:
public override Data.Configuration DeserializeConfig(StreamReader config)
{
var cs = new XmlSerializer(typeof(Configuration),
new Type[] { typeof(Command) });
return (Configuration)ConfigSerializer.Deserialize(ConfigStreamReader);
}
public override string SerializeConfig(Data.Configuration c, Encoding encoding)
{
string Output = null;
var Stream = new MemoryStream();
using (var Writer = new XmlTextWriter(Stream, encoding))
{
Writer.Formatting = Formatting.Indented;
XmlSerializerNamespaces ns = new XmlSerializerNamespaces();
ns.Add("xsi", XmlSchema.InstanceNamespace);
(new XmlSerializer(typeof(Configuration))).Serialize(Writer, c, ns);
Output = encoding.GetString(Stream.ToArray());
}
Stream.Dispose();
return Output;
}
结果 XML 应如下所示:
<?xml version="1.0" encoding="utf-8"?>
<Configuration
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="SomeSchema.xsd">
<Commands>
<Command>
<Name>SomeNameValue</Name>
</Command>
</Commands>
</Configuration>
我在尝试实例化序列化程序时看到以下异常(上面 DeserializeConfig()
方法中的第一行):
InvalidOperationException: Types 'Data.Command' and 'Data.Xml.Command' both use the XML type name, 'Command', from namespace ''. Use XML attributes to specify a unique XML name and/or namespace for the type.
我不太确定为什么序列化器试图从基础创建一些东西 class,确定名称是相同的,这是派生和命名空间等的想法...... 如何使用属性正确标记它以使其正确 de/serialize?
仅供参考:我确实看到了几个关于这个的问题,但答案似乎都足够具体到提问者的要求,我无法弄清楚如何将这些信息应用到这个,看似简单的场景。
更新: 我想出了如何在实例化时将包含的类型传递给序列化程序,而不必注释基础 class 所以我已经从我的问题并更新了代码。这已经过时了布鲁诺的建议和我的回应(尽管建议的问题仍然不适用)。
更新: 我试图通过将派生的 class 添加到名称空间来分隔 XML 名称空间中的名称(即添加 [XmlElement(Namespace = "http://www.foo.com/data/xml")]
对于派生 class 中的每个 属性)但这没有任何区别,因为序列化程序似乎仍然 "see" 基础和派生 class 在一起,所以认为它们都是在该名称空间中。
最后翻转想通了大部分内容。
我退后一步,从一个非常简单的工作非派生示例开始,并根据我的需要进行工作。
这里发生了两件事。首先是冲突的类型名称,然后是冲突的 属性 名称。虽然我对其中的每一个都有一些了解,但组合在一起时用于构建每个选项的选项排列数量让我感到困惑。
为了防止抽象类型和派生类型名称在序列化时发生冲突,我需要使派生 class 类型匿名(此处使用 XmlType
属性)。
为了阻止 属性 名称冲突,我需要忽略派生 class 中的 both 属性 and 基地 class。要在不编辑基础 class 的情况下做到这一点,我错过了一个重要的部分,XmlAttributeOverrides
。我曾在 XmlSerializer.Serialize()
的 MSDN 文档中看到过这一点,但那里的信息在解释它的相关内容方面非常少。
我还没有用派生类型列表 属性 或反序列化尝试过任何一个。
下面是一个程序的完整基本示例,它在控制台输出中输出带有一些序列化 XML 的字符串:
using System;
using System.Text;
using System.IO;
using System.Xml;
using System.Xml.Serialization;
namespace Test
{
class Program
{
static void Main(string[] args)
{
var TestBar = new MyXml.Bar()
{
Name = "John Smith",
};
Serializer s = new MyXml.Serializer();
var TestOutput = s.Serialize(TestBar);
Console.WriteLine(TestOutput);
}
}
public abstract class Bar
{
public abstract string Name { get; set; }
}
public abstract class Serializer
{
public abstract string Serialize(Bar bar);
}
namespace MyXml
{
public class Serializer : Test.Serializer
{
public override string Serialize(Test.Bar bar)
{
string Output = null;
var Stream = new MemoryStream();
var Encoding = new UTF8Encoding(false, true);
// Ignore the Name property in the *base* class!
var ao = new XmlAttributeOverrides();
var a = new XmlAttributes();
a.XmlElements.Clear(); // Clear any element attributes
a.XmlAttribute = null; // Remove any attribute attributes
a.XmlIgnore = true; // Set the ignore attribute value true
ao.Add(typeof(Test.Bar), "Name", a); // Set to use with Test.Bar.Name
using (var Writer = new XmlTextWriter(Stream, Encoding))
{
Writer.Formatting = Formatting.Indented;
var s = new XmlSerializer(typeof(Bar), ao);
s.Serialize(Writer, bar);
Output = Encoding.GetString(Stream.ToArray());
}
Stream.Dispose();
return Output;
}
}
[Serializable]
[XmlType(AnonymousType = true)] // Make type anonymous!
[XmlRoot(IsNullable = false)]
public class Bar : Test.Bar
{
[XmlIgnore] // Ignore the Name property in the *derived* class!
public override string Name
{
get => Unreverse(ReverseName);
set => ReverseName = Reverse(value);
}
[XmlElement("Name", IsNullable = false)]
public string ReverseName { get; set; }
private string Unreverse(string name)
{
return "John Smith"; // Smith, John -> John Smith
}
private string Reverse(string name)
{
return "Smith, John"; // John Smith -> Smith, John
}
}
}
}