.NET序列化排序

时间:2009-06-19 15:07:59

标签: c# xml serialization xml-serialization

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

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

由于

6 个答案:

答案 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)

像纳德所说的那样,或许可以考虑制作一种更松散耦合的设计。但是,在我的情况下,松散耦合是不合适的。这是我的类层次结构,以及我如何在不使用自定义序列化或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;
}

因此,虽然我必须为所有包含的类创建属性,但是当叶类的属性时,我可以通过简单地使用包含的类的属性来委托我在包含的类属性setter / getter中可能具有的任何自定义逻辑。被访问。由于没有继承,我可以使用XmlElementAttribute属性修饰叶类的所有属性,并使用我认为合适的任何排序。


<强>更新

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

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