Есть ли способ переопределить, в какое строковое значение преобразуется мой пользовательский тип при сериализации через DataContract? - PullRequest
1 голос
/ 28 октября 2010

В моей музыке / ритм-игре я использовал сериализацию для сохранения созданных пользователем simfiles (например, музыкальных вкладок или блокнотов). Ничего особенного там нет. Но я использую DataContract для выполнения сериализации, потому что:

1) Мне также нужно сериализовать приватные и защищенные поля. Мне все равно, если они видны, в основном из-за ...
2) Я бы хотел, чтобы пользователь мог редактировать сериализованный файл в своем любимом текстовом редакторе и загружать их в игру (эти файлы будут использоваться для представления музыкальных нот в игре, подумайте * 1004). * StepMania simfiles ).

Один из моих пользовательских типов данных, которые я хотел бы сериализовать, - это созданный мной класс Fraction:

using System.Runtime.Serialization;

namespace Fractions {
    [DataContract(Namespace="")] // Don't need the namespaces.
    public sealed class Fraction {
        // NOTE THAT THESE ARE "READONLY"!
        [DataMember(Name="Num", Order=1)] private readonly long numer;
        [DataMember(Name="Den", Order=2)] private readonly long denom;

        // ...LOTS OF STUFF...

        public static Fraction FromString(string str) {
            // Try and parse string and create a Fraction from it, and return it.
            // This is static because I'm returning a new created Fraction.
        }

        public override ToString() {
            return numer.ToString() + "/" + denom.ToString();
        }
    }
}

Тестируя это, он работает прилично, сериализуя в XML-фрагмент вида:

<Fraction>
   <Num>(INT64 VALUE AS STRING)</Num>
   <Den>(INT64 VALUE AS STRING)</Den>
</Fraction>

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

Мои конечные пользователи, вероятно, не будут в совершенстве знакомы с XML, и в моей игре МНОГО более сложных типов данных, которые будут включать много фракций, поэтому я бы предпочел иметь возможность представлять фракцию в XML-файл как таковой (гораздо более краткий):

<Fraction>(NUMERATOR)/(DENOMINATOR)</Fraction>

Однако я не знаю, как это сделать, не прерывая автоматическую (де) сериализацию. Я заглянул в интерфейс IXmlSerializable, но меня закрыл тот факт, что мой тип данных должен быть изменяемым, чтобы он работал (ReadXml () не возвращает новый объект Fraction, но вместо этого, кажется, выполняет flash-экземпляр один, и вы должны заполнить значения вручную, что не работает из-за readonly). Использование атрибутов OnSerializing и OnDeserialized также не работало по той же причине. Я ДЕЙСТВИТЕЛЬНО предпочел бы, чтобы мой класс фракций оставался неизменным.

Я предполагаю, что есть стандартная процедура, с помощью которой при сериализации в XML примитивы преобразуются в / из строк. Любые числовые примитивы, например, должны быть преобразованы в / из строк при сериализации / десериализации. Есть ли способ для меня, чтобы иметь возможность добавить этот вид автоматической строки из / в преобразование в мой тип фракции? Если бы это было возможно, я бы подумал, что процедура сериализации будет выглядеть примерно так:

1) Сериализуйте этот сложный тип данных, который содержит поля Fraction.
2) Начать сериализацию полей [DataMember].
3) Эй, это поле является дробным объектом. Фракции могут полностью представить себя в виде строки. Запишите эту строку непосредственно в XML, вместо того, чтобы погрузиться в объект Fraction и записать все его поля.
...

Десериализация будет работать наоборот:

1) Десериализовать эти данные, мы ожидаем такой-то тип данных.
2) Запустите десериализацию полей.
3) О, смотри, объект Fraction, я могу просто прочитать строку содержимого, преобразовать эту строку в новый объект Fraction и вернуть только что созданную Fraction.
...

Могу ли я что-нибудь сделать для этого? Спасибо!

РЕДАКТИРОВАТЬ: Суррогаты контрактов данных, похоже, путь, но я не могу понять их или заставить их работать в моей игре. Или, скорее, они добавляют некоторые неприятные автоматические поля имен и идентификаторов к моим сериализованным элементам.

Ответы [ 2 ]

0 голосов
/ 25 ноября 2012

У меня была похожая проблема, хотя в моем случае я использовал шаблон Type-Safe-Enumeration. В каждом случае, когда вы пишете свой DataContract, вы указываете на элементы, содержащие непростой тип данных, C # решает превратить класс в элемент и затем искать класс для этого контракта данных.

К сожалению, этого не хочет каждый из нас.

Мое решение состояло из двух частей:
1) В сложный класс, который мы хотим включить (Fraction в вашем случае), предоставляют методы для сериализации и десериализации объекта в строку. (Я использовал операторы приведения, потому что это несет в себе лучшее значение для меня):

class Complex
{
    ...
    // NOTE: that this can be implicit since every Complex generates a valid string        
    public static implicit operator string(Complex value)
    {
        return <... code to generate a string from the Complex Type...>;
    } 
    // NOTE: this must be explicit since it can throw an exception because not all
    // strings are valid Complex types
    public static explicit operator Complex(string value)
    {
        return <... code to validate and create a Complex object from a string ...>;
    }
    ...
 }

2) Теперь, когда вы используете объект сложного типа в другом классе, вы определяете DataContract со строковым свойством и используете вспомогательное значение фактического сложного типа:

[DataContract]
class User
{
    ...
    [DataMember]
    public string MyComplex
    {
         get { return m_MyComplex; }
         set { m_myComplex = (Complex)value; }
    }
    // NOTE that this member is _not_ part of the DataContract
    Complex m_myComplex;
    ...
}

Хотя я признаю, что это не идеальное решение, требующее наличия дополнительного кода в классе использования, оно допускает произвольное строковое представление сложного класса в формате DataContract без дополнительных слоев, которые в противном случае были бы необходимы.

0 голосов
/ 28 октября 2010

Полагаю, вы, вероятно, можете использовать Суррогаты контрактов на данные .

Но еще более простым способом было бы иметь частный строковый член FractionString в вашем типе, который будет представлять строковое представление вашего Типа. Вы должны инициализировать его только во время создания объекта (так как ваш тип неизменен). Теперь вы можете пропустить num и den из сериализации и пометить ваше поле fraString как DataMember. Недостатком подхода является дополнительное потребление пространства.

public sealed class Fraction {

    private readonly long numer;
    private readonly long denom;

    [DataMember(Name="Fraction")
    private string fractionString;

РЕДАКТИРОВАТЬ : Неважно, просто перечитайте то, что вы хотите, и понял, что выше не будет работать.

...