Сериализация в XML через DataContract: пользовательский вывод?

У меня есть собственный класс Fraction, который я использую во всем своем проекте. Все просто, он состоит из одного конструктора, принимает два целых числа и сохраняет их. Я хотел бы использовать DataContractSerializer для сериализации моих объектов, используемых в моем проекте, некоторые из которых включают дроби в качестве полей. В идеале я хотел бы иметь возможность сериализовать такие объекты следующим образом:

<Object>
    ...
    <Frac>1/2</Frac> // "1/2" would get converted back into a Fraction on deserialization.
    ...
</Object>

В отличие от этого:

<Object>
    ...
    <Frac>
        <Numerator>1</Numerator>
        <Denominator>2</Denominator>
    </Frac>
    ...
</Object>

Есть ли способ сделать это с помощью DataContracts?

Я хотел бы сделать это, потому что я планирую сделать XML-файлы редактируемыми пользователем (я использую их в качестве входных данных для музыкальной игры, и они, по сути, действуют как блокноты), и хочу, чтобы обозначения были как можно более краткими. для конечного пользователя, поэтому им не нужно будет иметь дело с таким количеством стен текста.

EDIT: я также должен отметить, что в настоящее время мой класс Fraction является неизменяемым (все поля readonly), поэтому возможность изменить состояние существующей фракции невозможна. Однако возвращение нового объекта Fraction было бы в порядке.


person Mark LeMoine    schedule 26.10.2010    source источник
comment
Не могли бы вы объяснить, почему вы предпочитаете вывод в таком формате? Это может привести к более подходящим ответам или указать направление, о котором вы и не думали.   -  person shaunmartin    schedule 26.10.2010
comment
@shaunmartin Хорошее замечание, перечитав свой вопрос, я немного растерялся. Я немного отредактирую.   -  person Mark LeMoine    schedule 26.10.2010


Ответы (5)


Если вы добавите свойство, представляющее элемент Frac, и примените к нему атрибут DataMember, а не к другим свойствам, вы получите то, что хотите, как мне кажется:

[DataContract]
public class MyObject {
    Int32 _Numerator;
    Int32 _Denominator;
    public MyObject(Int32 numerator, Int32 denominator) {
        _Numerator = numerator;
        _Denominator = denominator;
    }
    public Int32 Numerator {
        get { return _Numerator; }
        set { _Numerator = value; }
    }
    public Int32 Denominator {
        get { return _Denominator; }
        set { _Denominator = value; }
    }
    [DataMember(Name="Frac")]
    public String Fraction {
        get { return _Numerator + "/" + _Denominator; }
        set {
            String[] parts = value.Split(new char[] { '/' });
            _Numerator = Int32.Parse(parts[0]);
            _Denominator = Int32.Parse(parts[1]);
        }
    }
}
person Steve Ellinger    schedule 26.10.2010
comment
К сожалению, числитель и знаменатель доступны только для чтения, поэтому я не могу назначить их после создания экземпляра. - person Mark LeMoine; 28.10.2010

DataContractSerializer будет использовать пользовательский IXmlSerializable, если он указан вместо DataContractAttribute. Это позволит вам настроить форматирование XML так, как вам нужно... но вам придется вручную кодировать процесс сериализации и десериализации для вашего класса.

public class Fraction: IXmlSerializable 
{
    private Fraction()
    {
    }
    public Fraction(int numerator, int denominator)
    {
        this.Numerator = numerator;
        this.Denominator = denominator;
    }
    public int Numerator { get; private set; }
    public int Denominator { get; private set; }

    public XmlSchema GetSchema()
    {
        throw new NotImplementedException();
    }

    public void ReadXml(XmlReader reader)
    {
        var content = reader.ReadInnerXml();
        var parts = content.Split('/');
        Numerator = int.Parse(parts[0]);
        Denominator = int.Parse(parts[1]);
    }

    public void WriteXml(XmlWriter writer)
    {
        writer.WriteRaw(this.ToString());
    }

    public override string ToString()
    {
        return string.Format("{0}/{1}", Numerator, Denominator);
    }
}
[DataContract(Name = "Object", Namespace="")]
public class MyObject
{
    [DataMember]
    public Fraction Frac { get; set; }
}

class Program
{
    static void Main(string[] args)
    {
        var myobject = new MyObject
        {
            Frac = new Fraction(1, 2)
        };

        var dcs = new DataContractSerializer(typeof(MyObject));

        string xml = null;
        using (var ms = new MemoryStream())
        {
            dcs.WriteObject(ms, myobject);
            xml = Encoding.UTF8.GetString(ms.ToArray());
            Console.WriteLine(xml);
            // <Object><Frac>1/2</Frac></Object>
        }

        using (var ms = new MemoryStream(Encoding.UTF8.GetBytes(xml)))
        {
            ms.Position = 0;
            var obj = dcs.ReadObject(ms) as MyObject;

            Console.WriteLine(obj.Frac);
            // 1/2
        }
    }
}
person Matthew Whited    schedule 26.10.2010
comment
Как и другие ответы, это было бы идеально, если бы не тот факт, что числитель и знаменатель доступны только для чтения. - person Mark LeMoine; 28.10.2010
comment
Если вы не откажетесь от полей только для чтения, вы можете переместить логику для создания экземпляра объекта Fraction на уровень MyObject - person Matthew Whited; 28.10.2010

Эта статья MSDN описывает IDataContractSurrogate< /strong> Интерфейс, который:

Предоставляет методы, необходимые для замены одного типа другим с помощью DataContractSerializer во время сериализации, десериализации, а также экспорта и импорта документов XML-схемы.

Хоть и поздновато, может кому поможет. Собственно, позволяет изменить XML для ЛЮБОГО класса.

person jbarr    schedule 26.04.2013

Вы можете сделать это с помощью DataContractSerializer, хотя мне это кажется хакерским. Вы можете воспользоваться тем фактом, что элементы данных могут быть закрытыми переменными, и использовать частную строку в качестве сериализованного члена. Сериализатор контракта данных также будет выполнять методы в определенных точках процесса, которые отмечены атрибутами [On(De)Serializ(ed|ing)] — внутри них вы можете контролировать, как поля int сопоставляются со строкой, и наоборот. Недостатком является то, что вы теряете магию автоматической сериализации DataContractSerializer в своем классе, и теперь вам нужно поддерживать больше логики.

В любом случае, вот что я бы сделал:

[DataContract]
public class Fraction
{
    [DataMember(Name = "Frac")]
    private string serialized;

    public int Numerator { get; private set; }
    public int Denominator { get; private set; }

    [OnSerializing]
    public void OnSerializing(StreamingContext context)
    {
        // This gets called just before the DataContractSerializer begins.
        serialized = Numerator.ToString() + "/" + Denominator.ToString();
    }

    [OnDeserialized]
    public void OnDeserialized(StreamingContext context)
    {
        // This gets called after the DataContractSerializer finishes its work
        var nums = serialized.Split("/");
        Numerator = int.Parse(nums[0]);
        Denominator = int.Parse(nums[1]);
    }
}
person Ben    schedule 27.10.2010
comment
Это было бы здорово, но мой класс Fraction должен быть неизменяемым, поэтому числитель и знаменатель имеют только методы получения, а вспомогательные поля доступны только для чтения. - person Mark LeMoine; 28.10.2010
comment
Я думаю, что это решение все еще будет работать для вас - поля неизменяемы, поскольку они не раскрывают никаких общедоступных мутаторов, а поля записываются только один раз - во время десериализации. Это не удовлетворит ваши потребности? - person Ben; 28.10.2010
comment
Я не думаю, что это так, судя по тому, что я пробовал (если только я не делаю что-то ужасно неправильное). Когда я из любопытства попытался установить поля числителя и знаменателя в методе OnDeserialized, VS наорал на меня за попытку установить поля только для чтения вне конструктора. - person Mark LeMoine; 28.10.2010
comment
Понятно. Предоставленное мной решение гарантирует неизменность, но не через поля, отмеченные readonly. Вместо этого гарантия исходит из того факта, что никакие общедоступные мутаторы не раскрываются. Ключевым моментом здесь является то, что если вы используете автоматические свойства с закрытым методом доступа вместо поля только для чтения, этот пример будет работать для вас. - person Ben; 28.10.2010
comment
Извините, что комментирую старый пост. Но я считаю, что этот код будет сериализован в ‹Object›...‹Fraction›‹Frac›1/2‹/Frac›‹/Fraction›..‹/Object› вместо ‹Object›…‹Frac›1 /2‹/Frac› ...‹/Object› как предполагалось в ОП. - person LxL; 28.09.2016

Для этого вам придется вернуться к XMLSerializer. DataContractSerializer немного более ограничен с точки зрения возможности настройки вывода.

person Cam    schedule 26.10.2010