【问题标题】:.NET Serialization Ordering.NET 序列化排序
【发布时间】:2010-11-04 08:18:27
【问题描述】:

我正在尝试使用 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>

有谁知道这是否可能或有其他选择吗?

谢谢

【问题讨论】:

  • 我遇到了与此类似的问题,我需要派生类中的属性最后出现在 SOAP 消息中,我的解决方案是将属性添加为基类中的内部属性,然后将其隐藏派生类中的“new”关键字。请参阅我的回答here。希望对您有所帮助。

标签: c# xml serialization xml-serialization


【解决方案1】:

从技术上讲,从纯 xml 的角度来看,我想说这可能是一件不好的事情。

.NET 隐藏了 XmlSerialization 之类的大部分复杂性 - 在这种情况下,它隐藏了序列化 xml 应符合的架构。

推断的模式将使用序列元素来描述基本类型和扩展类型。这需要严格的排序——即使 Deserializer 不那么严格并且接受乱序元素。

在 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 序列化框架不太相符,您需要将您的业务对象设计(在这种情况下为继承结构)与该业务对象的序列化。

【讨论】:

  • 您很好地说明了为什么排序没有“按预期”运行,因为我们考虑的是 OOP 而不是 XML 模式。在我的特殊情况下,松散耦合不是一个合适的设计(同样,这是利基——总是争取松散耦合!),如果这是你的情况,你总是可以尝试聚合,其中“子”对象 包含“父”对象。您仍然可以实现封装和可重用性,但您还可以为子项指定元素的确切顺序。
  • @Nader 我在回答这个问题时引用了你的回答。虽然我的解决方案有效,但您的解决方案显然更胜一筹。当我说松耦合在我的设计中不合适时,我是不正确的。如果我有机会重构,我会使用你的建议!
  • 我不同意。是的,架构很重要。但对象不是模式。这可能有点不寻常,但以其他方式定义对象并没有本质上的错误,而不是与消息的一对一映射。 XmlElementAttribute.Order 的重点是能够定义序列中元素的顺序。它的行为不像指定的那样 - 这确实应该导致 OP 预期的输出。很明显,这是一个错误!
【解决方案2】:

编辑:这种方法不起作用。我把帖子留了下来,这样人们就可以避免这种想法。

序列化程序以递归方式运行。这样做有一个好处;在反序列化上,反序列化过程可以读取基类,然后是派生类。这意味着派生类的属性不会在基类的属性之前设置,这可能会导致问题。

如果它真的很重要(我不确定为什么按顺序排列这些很重要)那么你可以试试这个 --

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; }
    }

}

这将属性的具体实现放在最派生的类上,并且应该遵守顺序。

【讨论】:

  • 啊,抱歉——我原以为它可以通过查看包含具体实现的类来工作,但显然不是。我将更新帖子以表明这种方法不起作用。
  • 我遇到了 exact 同样的问题。实际上,[XmlElement(Order = n)] 的继承确实有效,但仅适用于父->子关系,没有更大的关系,即它不适用于祖父母->父->子。我将尝试这个解决方案的修改版本,看看它是否有效(因为我认为它会有效),我会报告。
  • 好吧,我的想法没有奏效:)。但我有一个部分解决方案。我有大约 10 个对象,其中大多数具有相同的属性(有些缺少一两个在这里和那里),所以我有一个类层次结构来保持它干燥。我仍然想让它保持干燥,但是,正如我们所知,Xml 序列化不会“正确”地排序。因此,不要使用继承,而是使用聚合。换句话说,派生对象应该“有一个”基对象作为类成员。这样,您可以将所有功能封装在“基类”中,并仅从“派生类”中公开它。不优雅,但它会工作。
  • “派生类的属性未设置在基类的属性之前,这可能会导致问题”。如果课程设计得当,则不会。 Microsoft 自己的指南声明“请注意,类型的属性应该能够以任何顺序设置和检索。”
【解决方案3】:

看起来 XmlSerializer 类依次序列化基类型和派生类型,并且仅单独尊重每个类中的 Order 属性。即使订单不完全是您想要的,它仍然应该正确反序列化。如果你真的必须有这样的命令,你将需要编写一个自定义的 xml 序列化程序。我会提醒您不要这样做,因为 .NET XmlSerializer 为您做了很多特殊处理。你能按照你提到的顺序描述为什么你需要东西吗?

【讨论】:

    【解决方案4】:

    这篇文章现在已经很老了,但是我最近在 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 序列化,所以如果这不起作用,请道歉,但我希望它的行为方式相同 - 我相信有人如果没有,会告诉我...

    【讨论】:

      【解决方案5】:

      我知道这个问题已经过期了;但是,这里有一个解决这个问题的方法:

      方法的名称应始终以 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>
      

      希望这对其他人有帮助!

      【讨论】:

      • 这确实有效。但是,您实际上已将 Property2 添加到它不属于的类中(即使它可能不会出现在 xml 中)。
      【解决方案6】:

      就像纳德说的,也许可以考虑做一个更松耦合的设计。但是,就我而言,松耦合是不合适的。这是我的类层次结构,以及我建议如何在不使用自定义序列化或 DTO 的情况下解决问题。

      在我的项目中,我正在构建一大堆对象来表示将通过 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;
      }
      

      因此,虽然我必须为所有包含的类创建属性,但我可以通过在叶类的属性时简单地使用包含的类的属性来委派我在包含的类属性设置器/获取器中可能拥有的任何自定义逻辑被访问。由于没有继承,我可以使用 XmlElementAttribute 属性装饰叶子类的所有属性,并使用我认为合适的任何顺序。


      更新:

      我回来重温这篇文章是因为我关于使用类继承的设计决策又回来了。虽然我上面的解决方案确实有效,但我正在使用它,我真的认为纳德的解决方案是最好的,应该在我提出的解决方案之前考虑。事实上,我今天正在为他 +1!我真的很喜欢他的回答,如果我有机会重构我当前的项目,我肯定会将业务对象与对象的序列化逻辑分开,否则这些对象将从继承中受益匪浅,以简化代码并使其更容易供他人使用和理解。

      感谢纳德发布您的回复,因为我认为很多人会发现它非常有启发性和有用性。

      【讨论】:

        猜你喜欢
        • 2012-04-26
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2010-09-09
        • 2010-11-15
        • 1970-01-01
        相关资源
        最近更新 更多