我正在尝试使用XmlSerializer和继承序列化一些对象,但是我在排序结果时遇到了一些问题。
下面是一个类似于我设置的示例:〜
public class SerializableBase
{
[XmlElement(Order = 1)]
public bool Property1 { get; set;}
[XmlElement(Order = 3)]
public bool Property3 { get; set;}
}
[XmlRoot("Object")]
public class SerializableObject1 : SerializableBase
{
}
[XmlRoot("Object")]
public class SerializableObject2 : SerializableBase
{
[XmlElement(Order = 2)]
public bool Property2 { get; set;}
}
我想要的结果如下:〜
<Object>
<Property1></Property1>
<Property2></Property2>
<Property3></Property3>
</Object>
但是我得到的结果是:〜
<Object>
<Property1></Property1>
<Property3></Property3>
<Property2></Property2>
</Object>
有人知道是否可能或有其他选择吗?
由于
答案 0 :(得分:17)
从技术上讲,从纯xml的角度来看,我想说这可能是一件坏事。
.NET隐藏了XmlSerialization之类的复杂功能 - 在这种情况下,它隐藏了序列化xml应符合的模式。
推断的模式将使用序列元素来描述基类型和扩展类型。这需要严格的排序 - 即使反序列化器不那么严格并且接受乱序元素。
在xml架构中,定义扩展类型时,子类中的其他元素必须在之后来自基类的元素。
你基本上会有一个类似的模式(为了清晰起见,删除了xml-y标签)
base
sequence
prop1
prop3
derived1 extends base
sequence
<empty>
derived2 extends base
sequence
prop2
没有办法在prop1和prop3之间插入占位符来指示派生xml的属性可以去哪里。
最后,您的数据格式与业务对象之间存在不匹配。可能你最好的选择是定义一个对象来处理你的xml序列化。
例如
[XmlRoot("Object")
public class SerializableObjectForPersistance
{
[XmlElement(Order = 1)]
public bool Property1 { get; set; }
[XmlElement(Order = 2, IsNullable=true)]
public bool Property2 { get; set; }
[XmlElement(Order = 3)]
public bool Property3 { get; set; }
}
这会将xml序列化代码与对象模型分开。将SerializableObject1或SerializableObject2中的所有值复制到SerializableObjectForPersistance,然后将其序列化。
基本上,如果您希望对序列化xml的格式进行这样的特定控制,而这种格式与期望的xml序列化框架并不相符,那么您需要解耦业务对象设计(在这种情况下为继承结构)并负责该业务对象的序列化。
答案 1 :(得分:4)
编辑:此方法不起作用。我已经离开了这个帖子,以便人们可以避免这种想法。
序列化程序以递归方式执行。这样做有好处;在反序列化时,反序列化过程可以读取基类,然后读取派生类。这意味着派生类上的属性未在基础上的属性之前设置,这可能会导致问题。
如果它真的很重要(而且我不确定为什么按顺序排列这些很重要)那么你可以试试这个 -
1)使基类'Property1和Property3成为虚拟。 2)在派生类中使用普通属性覆盖它们。例如
public class SerializableBase
{
[XmlElement(Order = 1)]
public virtual bool Property1 { get; set;}
[XmlElement(Order = 3)]
public virtual bool Property3 { get; set;}
}
[XmlRoot("Object")]
public class SerializableObject1 : SerializableBase
{
}
[XmlRoot("Object")]
public class SerializableObject2 : SerializableBase
{
[XmlElement(Order = 1)]
public override bool Property1
{
get { return base.Property1; }
set { base.Property1 = value; }
}
[XmlElement(Order = 2)]
public bool Property2 { get; set;}
[XmlElement(Order = 3)]
public override bool Property3
{
get { return base.Property3; }
set { base.Property3 = value; }
}
}
这将属性的具体实现放在最派生的类上,并且应该遵守该顺序。
答案 2 :(得分:3)
看起来XmlSerializer类按顺序序列化基类型然后派生类型,并且仅单独尊重每个类中的Order属性。即使订单并非完全符合您的要求,它仍应正确反序列化。如果你真的必须有这样的订单,你需要编写一个自定义的xml序列化程序。我会提醒您,因为.NET XmlSerializer会为您做很多特殊处理。你能按照你提到的顺序描述你为什么需要的东西吗?
答案 3 :(得分:3)
这篇文章现在已经很老了,但我最近在WCF中遇到了类似的问题,并找到了类似于上面的Steve Cooper的解决方案,但它确实有效,并且可能也适用于XML序列化。
如果从基类中删除XmlElement属性,并将具有不同名称的每个属性的副本添加到通过get / set访问基值的派生类,则可以使用指定的相应名称序列化副本使用XmlElementAttribute,然后希望以默认顺序序列化:
public class SerializableBase
{
public bool Property1 { get; set;}
public bool Property3 { get; set;}
}
[XmlRoot("Object")]
public class SerializableObject : SerializableBase
{
[XmlElement("Property1")]
public bool copyOfProperty1
{
get { return base.Property1; }
set { base.Property1 = value; }
}
[XmlElement]
public bool Property2 { get; set;}
[XmlElement("Property3")]
public bool copyOfProperty3
{
get { return base.Property3; }
set { base.Property3 = value; }
}
}
我还添加了一个接口来添加到派生类中,以便可以强制使用副本:
interface ISerializableObjectEnsureProperties
{
bool copyOfProperty1 { get; set; }
bool copyOfProperty2 { get; set; }
}
这不是必需的,但意味着我可以检查所有内容是否在编译时实现,而不是检查生成的XML。我最初制作了SerializableBase的这些抽象属性,但这些属性首先序列化(使用基类),我现在意识到它是合乎逻辑的。
通过改变上面的一行,以通常的方式调用:
public class SerializableObject : SerializableBase, ISerializableObjectEnsureProperties
我只是在WCF中对此进行了测试,并且已经将这个概念移植到XML序列化而没有编译,所以如果这不起作用,请道歉,但我希望它的行为方式相同 - 我敢肯定有人如果没有,我会告诉我......
答案 4 :(得分:2)
我知道这个问题已经过期了;但是,这是解决这个问题的方法:
方法的名称应始终以ShouldSerialize开头,然后以属性名称结尾。然后你只需要根据你想要的条件返回一个布尔值,关于是否序列化值。
public class SerializableBase
{
public bool Property1 { get; set;}
public bool Property2 { get; set;}
public bool Property3 { get; set;}
public virtual bool ShouldSerializeProperty2 { get { return false; } }
}
[XmlRoot("Object")]
public class SerializableObject1 : SerializableBase
{
}
[XmlRoot("Object")]
public class SerializableObject2 : SerializableBase
{
public override bool ShouldSerializeProperty2 { get { return true; } }
}
使用SerializableObject2时的结果:〜
<Object>
<Property1></Property1>
<Property2></Property2>
<Property3></Property3>
</Object>
使用SerializableObject1时的结果:〜
<Object>
<Property1></Property1>
<Property3></Property3>
</Object>
希望这有助于其他许多人!
答案 5 :(得分:0)
在我的项目中,我正在构建一大堆对象来表示将通过Web服务提交的XML文档片段。有很多件。并非所有请求都与每个请求一起发送(实际上,在此示例中,我正在建模响应,但概念是相同的)。这些部分非常类似于构建块来组合请求(或者在这种情况下反汇编响应)。所以这是一个使用聚合/封装来实现所需顺序的例子,尽管有继承层次结构。
[Serializable]
public abstract class ElementBase
{
// This constructor sets up the default namespace for all of my objects. Every
// Xml Element class will inherit from this class.
internal ElementBase()
{
this._namespaces = new XmlSerializerNamespaces(new XmlQualifiedName[] {
new XmlQualifiedName(string.Empty, "urn:my-default-namespace:XSD:1")
});
}
[XmlNamespacesDeclaration]
public XmlSerializerNamespaces Namespaces { get { return this._namespaces; } }
private XmlSerializationNamespaces _namespaces;
}
[Serializable]
public abstract class ServiceBase : ElementBase
{
private ServiceBase() { }
public ServiceBase(Guid requestId, Guid? asyncRequestId = null, Identifier name = null)
{
this._requestId = requestId;
this._asyncRequestId = asyncRequestId;
this._name = name;
}
public Guid RequestId
{
get { return this._requestId; }
set { this._requestId = value; }
}
private Guid _requestId;
public Guid? AsyncRequestId
{
get { return this._asyncRequestId; }
set { this._asyncRequestId = value; }
}
private Guid? _asyncRequestId;
public bool AsyncRequestIdSpecified
{
get { return this._asyncRequestId == null && this._asyncRequestId.HasValue; }
set { /* XmlSerializer requires both a getter and a setter.*/ ; }
}
public Identifier Name
{
get { return this._name; }
set { this._name; }
}
private Identifier _name;
}
[Serializable]
public abstract class ServiceResponseBase : ServiceBase
{
private ServiceBase _serviceBase;
private ServiceResponseBase() { }
public ServiceResponseBase(Guid requestId, Guid? asyncRequestId = null, Identifier name = null, Status status = null)
{
this._serviceBase = new ServiceBase(requestId, asyncRequestId, name);
this._status = status;
}
public Guid RequestId
{
get { return this._serviceBase.RequestId; }
set { this._serviceBase.RequestId = value; }
}
public Guid? AsyncRequestId
{
get { return this._serviceBase.AsyncRequestId; }
set { this._serviceBase.AsyncRequestId = value; }
}
public bool AsynceRequestIdSpecified
{
get { return this._serviceBase.AsyncRequestIdSpecified; }
set { ; }
}
public Identifier Name
{
get { return this._serviceBase.Name; }
set { this._serviceBase.Name = value; }
}
public Status Status
{
get { return this._status; }
set { this._status = value; }
}
}
[Serializable]
[XmlRoot(Namespace = "urn:my-default-namespace:XSD:1")]
public class BankServiceResponse : ServiceResponseBase
{
// Determines if the class is being deserialized.
private bool _isDeserializing;
private ServiceResponseBase _serviceResponseBase;
// Constructor used by XmlSerializer.
// This is special because I require a non-null List<T> of items later on.
private BankServiceResponse()
{
this._isDeserializing = true;
this._serviceResponseBase = new ServiceResponseBase();
}
// Constructor used for unit testing
internal BankServiceResponse(bool isDeserializing = false)
{
this._isDeserializing = isDeserializing;
this._serviceResponseBase = new ServiceResponseBase();
}
public BankServiceResponse(Guid requestId, List<BankResponse> responses, Guid? asyncRequestId = null, Identifier name = null, Status status = null)
{
if (responses == null || responses.Count == 0)
throw new ArgumentNullException("The list cannot be null or empty", "responses");
this._serviceResponseBase = new ServiceResponseBase(requestId, asyncRequestId, name, status);
this._responses = responses;
}
[XmlElement(Order = 1)]
public Status Status
{
get { return this._serviceResponseBase.Status; }
set { this._serviceResponseBase.Status = value; }
}
[XmlElement(Order = 2)]
public Guid RequestId
{
get { return this._serviceResponseBase.RequestId; }
set { this._serviceResponseBase.RequestId = value; }
}
[XmlElement(Order = 3)]
public Guid? AsyncRequestId
{
get { return this._serviceResponseBase.AsyncRequestId; }
set { this._serviceResponseBase.AsyncRequestId = value; }
}
[XmlIgnore]
public bool AsyncRequestIdSpecified
{
get { return this._serviceResponseBase.AsyncRequestIdSpecified; }
set { ; } // Must have this for XmlSerializer.
}
[XmlElement(Order = 4)]
public Identifer Name
{
get { return this._serviceResponseBase.Name; }
set { this._serviceResponseBase.Name; }
}
[XmlElement(Order = 5)]
public List<BankResponse> Responses
{
get { return this._responses; }
set
{
if (this._isDeserializing && this._responses != null && this._responses.Count > 0)
this._isDeserializing = false;
if (!this._isDeserializing && (value == null || value.Count == 0))
throw new ArgumentNullException("List cannot be null or empty.", "value");
this._responses = value;
}
}
private List<BankResponse> _responses;
}
因此,虽然我必须为所有包含的类创建属性,但是当叶类的属性时,我可以通过简单地使用包含的类的属性来委托我在包含的类属性setter / getter中可能具有的任何自定义逻辑。被访问。由于没有继承,我可以使用XmlElementAttribute
属性修饰叶类的所有属性,并使用我认为合适的任何排序。
<强>更新强>
我回过头来重温这篇文章,因为我关于使用类继承的设计决定又回来了。虽然我的上述解决方案确实有效,但我正在使用它,我认为Nader的解决方案是最好的,在我提出的解决方案之前应该考虑。事实上,我今天对他不屑一顾!我真的很喜欢他的答案,如果我有机会重构我当前的项目,我肯定会将业务对象与对象的序列化逻辑分开,否则这些对象将从继承中获益,以简化代码并使其更容易供他人使用和理解。
感谢您发布您的回复Nader,因为我认为很多人会发现它很有启发性和实用性。