将 class 属性反序列化为派生 class 而不是基础抽象 class
Deserialize class properties to derived class instead of base abstract class
这里的结构有点复杂,请多多包涵。我希望有办法做想做的事,但如果不可能,请随时告诉我!
遗憾的是我从一开始就没有参与XML文件规范的设计过程,门柱已经移动了十几次,规范不能为了制作而修改这对我来说更容易做到,因为任何规范修正案的价格都非常高(是的,甚至重命名 XML 元素!)
无论如何我离题了。
有两种不同类型的采购订单,它们的文件结构和处理逻辑略有不同,我试图用相同的代码来处理它们,而不是为几乎相同的内容创建两个不同的项目。
两种类型的采购订单都源自一组抽象 classes,这些抽象确定两种 PO 类型共享的基本业务逻辑(数量、成本、PO 编号等)。
基础classes
abstract class PO {
[XmlIgnore]
abstract POType PurchaseOrderType {get;}
[XmlIgnore]
abstract PO_Head Header {get;set;}
[XmlIgnore]
abstract List<PO_Line> LineItems {get;set;}
}
abstract class PO_Head {
[XmlIgnore]
abstract string PONumber {get;set;}
}
abstract class PO_Line {
[XmlIgnore]
abstract string ItemCode {get;set;}
[XmlIgnore]
abstract decimal UnitCost {get;set;}
[XmlIgnore]
abstract int OrderQty {get;set;}
}
派生classes
class POR : PO {
// POR implementations
}
class POR_Head : PO_Line {
// POR implementations
}
class POR_Line : PO_Line {
// POR implementations
}
class CPO : PO {
// CPO implementations
}
class CPO_Head : PO_Line {
// CPO implementations
}
class CPO_Line : PO_Line {
// CPO implementations
}
然后在代码中使用基本摘要 class 来处理每个采购订单并将它们导入我们的会计系统。
for (int i = pending.Transactions.Count -1; i > -1; i--) {
PO pendingOrder = (PO)pending.Transactions[i];
// Import PO type
}
问题是,当我尝试反序列化为每个派生类型 class 时,它会尝试将 Header
和 LineItems
反序列化为派生类型 PO_Head
和 PO_Line
。有什么办法可以明确地告诉 XmlSerializer
将 Header
和 LineItems
视为派生的 class 版本 - CPO_Head 和 CPO_Line 或 POR_Head 和 POR_Line - 取决于正在序列化的 class - 分别是 CPO 或 POR。类似于下面的东西。
class CPO : PO {
[XmlIgnore]
override POType PurchaseOrderType => POType.CPO;
[XmlElement("CPO_Head")]
[XmlDeserializerType(typeof(CPO_Head))]
override PO_Head Header {get;set;}
[XmlArray("CPO_Lines")]
[XmlArrayItem("CPO_Line")]
[XmlDeserializerType(typeof(CPO_Line))]
override List<PO_Line> LineItems {get;set;}
}
有点给自己挖了一个坑(第一次使用反序列化)所以我希望有一个简单的方法可以让我不必重写我已经完成的大量工作!
TIA
编辑 - 根据要求deserialize/serialize使用的代码
public static void SerializeToXmlFile(object obj, FileInfo dstFile)
{
XmlSerializer xmlSerializer = new XmlSerializer(obj.GetType());
using (FileStream fs = dstFile.Open(FileMode.Create, FileAccess.Write))
{
xmlSerializer.Serialize(fs, obj);
}
}
public static object DeserializeFromXmlFile(FileInfo srcFile, Type type)
{
XmlSerializer xmlSerializer = new XmlSerializer(type);
using (FileStream fs = srcFile.Open(FileMode.Open, FileAccess.Read))
{
return xmlSerializer.Deserialize(fs);
}
}
public static void Main(string[] args)
{
// Deserialize from XML file
FileInfo srcFile = new FileInfo("path\to\file");
Type t;
if (srcFile.Name.Substring(0,3) == "CPO")
t = typeof(CPO);
else if (srcFile.Name.Substring(0,3) == "POR")
t = typeof(POR);
PO po = DeserializeFromXmlFile(file, t);
// Process the file
// ...
// Serialize back to file
FileInfo dstFile = new FileInfo("new\path\to\file");
SerializeToXmlFile(po, dstFile);
}
编辑 - 修复
根据标记的正确答案,我能够通过在 XmlElement
和 XmlArrayItem
属性中指定 Type
来解决此问题。
class POR {
[XmlElement(typeof(POR_Head), ElementName="PO_Head")]
override PO_Head Header {get;set;} = new POR_Head();
[XmlArray("PO_Lines")]
[XmlArrayItem(typeof(POR_Line), ElementName="PO_Line")]
override List<PO_Line> LineItems {get;set;} = new List<LineItems>();
}
class CPO {
[XmlElement(typeof(CPO_Head), ElementName="PO_Head")]
override PO_Head Header {get;set;} = new CPO_Head();
[XmlArray("PO_Lines")]
[XmlArrayItem(typeof(CPO_Line), ElementName="PO_Line")]
override List<PO_Line> LineItems {get;set;} = new List<LineItems>();
}
我认为您正在寻找 XmlArrayItemAttribute。根据文档:
You can apply the XmlArrayItemAttribute to any public read/write member that returns an array, or provides access to one. For example, a field that returns an array of objects, a collection, an ArrayList, or any class that implements the IEnumerable interface.
The XmlArrayItemAttribute supports polymorphism--in other words, it allows the XmlSerializer to add derived objects to an array.
如果我对示例的理解正确,则应该使用序列化可枚举中允许的可能派生类型的列表来编写属性,例如
[XmlArrayItem (typeof(PO_Line), ElementName = "PO_Line"),
XmlArrayItem (typeof(CPO_Line),ElementName = "CPO_Line")]
由于您只传递一个字符串,属性将其解释为 ElementName,而不是类型。必须使用 typeof(ClassName)
.
传递类型
这里的结构有点复杂,请多多包涵。我希望有办法做想做的事,但如果不可能,请随时告诉我!
遗憾的是我从一开始就没有参与XML文件规范的设计过程,门柱已经移动了十几次,规范不能为了制作而修改这对我来说更容易做到,因为任何规范修正案的价格都非常高(是的,甚至重命名 XML 元素!)
无论如何我离题了。
有两种不同类型的采购订单,它们的文件结构和处理逻辑略有不同,我试图用相同的代码来处理它们,而不是为几乎相同的内容创建两个不同的项目。
两种类型的采购订单都源自一组抽象 classes,这些抽象确定两种 PO 类型共享的基本业务逻辑(数量、成本、PO 编号等)。
基础classes
abstract class PO {
[XmlIgnore]
abstract POType PurchaseOrderType {get;}
[XmlIgnore]
abstract PO_Head Header {get;set;}
[XmlIgnore]
abstract List<PO_Line> LineItems {get;set;}
}
abstract class PO_Head {
[XmlIgnore]
abstract string PONumber {get;set;}
}
abstract class PO_Line {
[XmlIgnore]
abstract string ItemCode {get;set;}
[XmlIgnore]
abstract decimal UnitCost {get;set;}
[XmlIgnore]
abstract int OrderQty {get;set;}
}
派生classes
class POR : PO {
// POR implementations
}
class POR_Head : PO_Line {
// POR implementations
}
class POR_Line : PO_Line {
// POR implementations
}
class CPO : PO {
// CPO implementations
}
class CPO_Head : PO_Line {
// CPO implementations
}
class CPO_Line : PO_Line {
// CPO implementations
}
然后在代码中使用基本摘要 class 来处理每个采购订单并将它们导入我们的会计系统。
for (int i = pending.Transactions.Count -1; i > -1; i--) {
PO pendingOrder = (PO)pending.Transactions[i];
// Import PO type
}
问题是,当我尝试反序列化为每个派生类型 class 时,它会尝试将 Header
和 LineItems
反序列化为派生类型 PO_Head
和 PO_Line
。有什么办法可以明确地告诉 XmlSerializer
将 Header
和 LineItems
视为派生的 class 版本 - CPO_Head 和 CPO_Line 或 POR_Head 和 POR_Line - 取决于正在序列化的 class - 分别是 CPO 或 POR。类似于下面的东西。
class CPO : PO {
[XmlIgnore]
override POType PurchaseOrderType => POType.CPO;
[XmlElement("CPO_Head")]
[XmlDeserializerType(typeof(CPO_Head))]
override PO_Head Header {get;set;}
[XmlArray("CPO_Lines")]
[XmlArrayItem("CPO_Line")]
[XmlDeserializerType(typeof(CPO_Line))]
override List<PO_Line> LineItems {get;set;}
}
有点给自己挖了一个坑(第一次使用反序列化)所以我希望有一个简单的方法可以让我不必重写我已经完成的大量工作!
TIA
编辑 - 根据要求deserialize/serialize使用的代码
public static void SerializeToXmlFile(object obj, FileInfo dstFile)
{
XmlSerializer xmlSerializer = new XmlSerializer(obj.GetType());
using (FileStream fs = dstFile.Open(FileMode.Create, FileAccess.Write))
{
xmlSerializer.Serialize(fs, obj);
}
}
public static object DeserializeFromXmlFile(FileInfo srcFile, Type type)
{
XmlSerializer xmlSerializer = new XmlSerializer(type);
using (FileStream fs = srcFile.Open(FileMode.Open, FileAccess.Read))
{
return xmlSerializer.Deserialize(fs);
}
}
public static void Main(string[] args)
{
// Deserialize from XML file
FileInfo srcFile = new FileInfo("path\to\file");
Type t;
if (srcFile.Name.Substring(0,3) == "CPO")
t = typeof(CPO);
else if (srcFile.Name.Substring(0,3) == "POR")
t = typeof(POR);
PO po = DeserializeFromXmlFile(file, t);
// Process the file
// ...
// Serialize back to file
FileInfo dstFile = new FileInfo("new\path\to\file");
SerializeToXmlFile(po, dstFile);
}
编辑 - 修复
根据标记的正确答案,我能够通过在 XmlElement
和 XmlArrayItem
属性中指定 Type
来解决此问题。
class POR {
[XmlElement(typeof(POR_Head), ElementName="PO_Head")]
override PO_Head Header {get;set;} = new POR_Head();
[XmlArray("PO_Lines")]
[XmlArrayItem(typeof(POR_Line), ElementName="PO_Line")]
override List<PO_Line> LineItems {get;set;} = new List<LineItems>();
}
class CPO {
[XmlElement(typeof(CPO_Head), ElementName="PO_Head")]
override PO_Head Header {get;set;} = new CPO_Head();
[XmlArray("PO_Lines")]
[XmlArrayItem(typeof(CPO_Line), ElementName="PO_Line")]
override List<PO_Line> LineItems {get;set;} = new List<LineItems>();
}
我认为您正在寻找 XmlArrayItemAttribute。根据文档:
You can apply the XmlArrayItemAttribute to any public read/write member that returns an array, or provides access to one. For example, a field that returns an array of objects, a collection, an ArrayList, or any class that implements the IEnumerable interface.
The XmlArrayItemAttribute supports polymorphism--in other words, it allows the XmlSerializer to add derived objects to an array.
如果我对示例的理解正确,则应该使用序列化可枚举中允许的可能派生类型的列表来编写属性,例如
[XmlArrayItem (typeof(PO_Line), ElementName = "PO_Line"),
XmlArrayItem (typeof(CPO_Line),ElementName = "CPO_Line")]
由于您只传递一个字符串,属性将其解释为 ElementName,而不是类型。必须使用 typeof(ClassName)
.