C# .NET 序列化排序

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/1018490/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-06 05:39:27  来源:igfitidea点击:

.NET Serialization Ordering

c#xmlserializationxml-serialization

提问by Chris Knight

I am trying to serialize some objects using XmlSerializer and inheritance but I am having some problems with ordering the outcome.

我正在尝试使用 XmlSerializer 和继承来序列化一些对象,但我在排序结果时遇到了一些问题。

Below is an example similar to what I have setup: ~

下面是一个类似于我设置的示例:~

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

The outcome I want is as follows: ~

我想要的结果如下:~

<Object>
    <Property1></Property1>
    <Property2></Property2>
    <Property3></Property3>
</Object>

However I am getting an outcome of: ~

但是我得到的结果是:~

<Object>
    <Property1></Property1>
    <Property3></Property3>
    <Property2></Property2>
</Object>

Does anyone know if it is possible or of any alternative?

有谁知道这是否可能或任何替代方案?

Thanks

谢谢

回答by jjxtra

It looks like the XmlSerializer class serializes the base type and then derived types in that order and is only respecting the Order property within each class individually. Even though the order is not totally what you want, it should still Deserialize properly. If you really must have the order just like that you will need to write a custom xml serializer. I would caution against that beacuse the .NET XmlSerializer does a lot of special handling for you. Can you describe why you need things in the order you mention?

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

回答by Steve Cooper

EDIT: This approach doesn't work. I've left the post in so that people can avoid this line of thinking.

编辑:这种方法不起作用。我已经离开了帖子,以便人们可以避免这种想法。

The serializer acts recursively. There's a benefit to this; on deserialization, the deserialization process can read the base class, then the derived class. This means that a property on the derived class isn't set before the properties on the base, which could lead to problems.

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

If it really matters (and I'm not sure why it's important to get these in order) then you can try this --

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

1) make the base class' Property1 and Property3 virtual. 2) override them with trivial properties in your derived class. Eg

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

}

This puts a concrete implementtion of the property on the most derived class, and the order should be respected.

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

回答by Nader Shirazie

Technically, from a pure xml perspective, I would say that this is probably a bad thing to want to do.

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

.NET hides much of the complexity of things like XmlSerialization - in this case, it hides the schema to which your serialized xml should conform.

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

The inferred schema will use sequence elements to describe the base type, and the extension types. This requires strict ordering -- even if the Deserializer is less strict and accepts out of order elements.

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

In xml schemas, when defining extension types, the additional elements from the child class must come afterthe elements from the base class.

在 xml 模式中,定义扩展类型时,来自子类的附加元素必须位于来自基类的元素之后

you would essentially have a schema that looks something like (xml-y tags removed for clarity)

你基本上会有一个看起来像(为了清楚起见删除了 xml-y 标签)的架构

base
  sequence
    prop1
    prop3

derived1 extends base
  sequence
    <empty>

derived2 extends base
  sequence
    prop2

There's no way to stick a placeholder in between prop1 and prop3 to indicate where the properties from the derived xml can go.

无法在 prop1 和 prop3 之间插入占位符来指示派生 xml 中的属性可以去哪里。

In the end, you have a mismatch between your data format and your business object. Probably your best alternative is to define an object to deal with your xml serialization.

最后,您的数据格式和您的业务对象不匹配。可能你最好的选择是定义一个对象来处理你的 xml 序列化。

For example

例如

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

This separates your xml serialization code from your object model. Copy all the values from SerializableObject1 or SerializableObject2 to SerializableObjectForPersistance, and then serialize it.

这将您的 xml 序列化代码与您的对象模型分开。将 SerializableObject1 或 SerializableObject2 中的所有值复制到 SerializableObjectForPersistance,然后对其进行序列化。

Essentially, if you want such specific control over the format of your serialized xml that doesn't quite jive with the expectations xml serialization framework, you need to decouple your business object design (inheritance structure in this case) and the responsibility for serialization of that business object.

本质上,如果您想要对与预期的 xml 序列化框架不太一致的序列化 xml 格式进行这种特定控制,您需要将业务对象设计(在本例中为继承结构)与序列化的责任分离业务对象。

回答by fourpastmidnight

Like Nader said, maybe think about making a more loose-coupled design. However, in my case, loose-coupling was not appropriate. Here's my class hierarchy, and how I propose to solve the problem without using custom serialization or DTOs.

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

In my project, I'm constructing a whole bunch of objects to represent pieces of an XML document that will be submitted via a web service. There are a very large number of pieces. Not all are sent with every request (actually, in this example, I'm modeling a response, but the concepts are the same). These pieces are used much like building blocks to assemble a request (or disassemble a response, in this case). So here's an example of using aggregation/encapsulation to accomplish the desired ordering despite the inheritance hierarchy.

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

So, while I have to create properties for all of the contained classes, I can delegate any custom logic I might have within the contained class(es) property setters/getters by simply using the contained class's properties when the leaf class's properties are accessed. Since there's no inheritance, I can decorate all the properties of the leaf class with the XmlElementAttributeattribute and use any ordering that I see fit.

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



UPDATE:

更新:

I came back to revisit this article because my design decisions about using class inheritance came back to bite me again. While my solution above does work, I'm using it, I really think that Nader's solution is the best and should be considered before the solution I presented. In fact, I'm +1'ing him today! I really like his answer, and if I ever have the opportunity to refactor my current project, I will definitely be separating the business object from the serialization logic for objects that would otherwise benefit greatly from inheritance in order to simplify the code and make it easier for others to use and understand.

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

Thanks for posting your response Nader, as I think many will find it very instructive and useful.

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

回答by MarkD

This post is quite old now, but I had a similar problem in WCF recently, and found a solution similar to Steve Cooper's above, but one that does work, and presumably will work for XML Serialization too.

这篇文章现在已经很老了,但我最近在 WCF 中遇到了类似的问题,并找到了类似于上面 Steve Cooper 的解决方案,但确实有效,并且可能也适用于 XML 序列化。

If you remove the XmlElement attributes from the base class, and add a copy of each property with a different name to the derived classes that access the base value via the get/set, the copies can be serialized with the appropriate name assigned using an XmlElementAttribute, and will hopefully then serialize in the default order:

如果从基类中删除 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; }
  }
}

I also added an Interface to add to the derived classes, so that the copies could be made mandatory:

我还添加了一个接口以添加到派生类,以便可以强制复制:

interface ISerializableObjectEnsureProperties
{
  bool copyOfProperty1  { get; set; }
  bool copyOfProperty2  { get; set; }
}

This is not essential but means that I can check everything is implemented at compile time, rather than checking the resultant XML. I had originally made these abstract properties of SerializableBase, but these then serialize first (with the base class), which I now realise is logical.

这不是必需的,但意味着我可以检查在编译时实现的所有内容,而不是检查生成的 XML。我最初创建了 SerializableBase 的这些抽象属性,但这些属性首先(使用基类)序列化,我现在意识到这是合乎逻辑的。

This is called in the usual way by changing one line above:

这是通过更改上面的一行以通常的方式调用的:

public class SerializableObject : SerializableBase, ISerializableObjectEnsureProperties

I've only tested this in WCF, and have ported the concept to XML Serialization without compiling, so if this doesn't work, apologies, but I would expect it to behave in the same way - I'm sure someone will let me know if not...

我只在 WCF 中对此进行了测试,并且在没有编译的情况下将概念移植到了 XML 序列化,所以如果这不起作用,抱歉,但我希望它的行为方式相同 - 我相信有人会让我知道如果没有...

回答by Yama Kamyar

I know this question has expired; however, here is a solution for this problem:

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

The name of the method should always begin with ShouldSerialize and then end with the property name. Then you simply need to return a boolean based on whatever conditional you want, as to whether to serialize the value or not.

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

The outcome when using SerializableObject2: ~

使用 SerializableObject2 时的结果:~

<Object>
    <Property1></Property1>
    <Property2></Property2>
    <Property3></Property3>
</Object>

The outcome when using SerializableObject1: ~

使用 SerializableObject1 时的结果:~

<Object>
    <Property1></Property1>
    <Property3></Property3>
</Object>

Hope this helps many others!

希望这可以帮助许多其他人!