Порядок сортировки по .NET

Я пытаюсь сериализовать некоторые объекты с помощью 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>

Кто-нибудь знает, возможно ли это или какая-либо альтернатива?

Спасибо

Ответы

Ответ 1

Технически, с чистой точки зрения xml, я бы сказал, что это, вероятно, плохо, что нужно делать.

.NET скрывает большую часть сложности таких вещей, как XmlSerialization - в этом случае он скрывает схему, которой должен соответствовать ваш serialized 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, вам необходимо отделить дизайн вашего бизнес-объекта (структура наследования в этом случае) и ответственность за сериализация этого бизнес-объекта.

Ответ 2

Похоже, что класс XmlSerializer сериализует базовый тип, а затем производные типы в этом порядке и только уважает свойство Order в каждом классе отдельно. Несмотря на то, что заказ не совсем то, что вы хотите, он все равно должен Deserialize правильно. Если вам действительно нужен такой порядок, вам нужно будет написать собственный XML-сериализатор. Я бы предостерег от этого, что .NET XmlSerializer делает для вас много специальной обработки. Можете ли вы описать, почему вам нужны вещи в указанном вами порядке?

Ответ 3

EDIT: Этот подход не работает. Я оставил сообщение, чтобы люди могли избежать этой линии мышления.

Сериализатор действует рекурсивно. Там преимущество для этого; при десериализации процесс десериализации может читать базовый класс, а затем производный класс. Это означает, что свойство на производном классе не задано перед свойствами на базе, что может привести к проблемам.

Если это действительно важно (и я не уверен, почему это важно, чтобы привести их в порядок), вы можете попробовать это -

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

}

Это ставит конкретное воплощение свойства в самом производном классе, и порядок должен соблюдаться.

Ответ 4

Это сообщение довольно устарело, но у меня была аналогичная проблема в WCF в последнее время, и я нашел решение, подобное Стиву Куперу выше, но тот, который действительно работает, и, по-видимому, будет работать и для 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 Serialization без компиляции, поэтому, если это не работает, извинения, но я ожидаю, что она будет вести себя одинаково - я уверен, что кто-то дайте мне знать, если нет...

Ответ 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>

Надеюсь, это поможет многим другим!

Ответ 6

Как сказал Надер, возможно, подумайте о том, чтобы создать более свободный дизайн. Однако, в моем случае, ослабление связи было неприемлемым. Вот моя иерархия классов и как я предлагаю решить проблему без использования пользовательской сериализации или DTO.

В моем проекте я создаю целую кучу объектов для представления фрагментов 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 и использовать любое упорядочение, которое я считаю нужным.


UPDATE:

Я вернулся, чтобы вернуться к этой статье, потому что мои дизайнерские решения об использовании наследования классов вернулись, чтобы снова укусить меня. Хотя мое решение выше работает, я использую его, я действительно считаю, что решение Nader является лучшим и должно быть рассмотрено до решения, которое я представил. На самом деле, я + 1 его сегодня! Мне очень нравится его ответ, и если у меня когда-нибудь будет возможность реорганизовать мой текущий проект, я определенно буду отделять бизнес-объект от логики сериализации для объектов, которые в противном случае выиграли бы от наследования, чтобы упростить код и упростить его для других использовать и понимать.

Спасибо, что опубликовал ваш ответ Nader, поскольку многие считают, что это очень поучительно и полезно.