Десериализация объекта без потери обязательных ссылок - PullRequest
0 голосов
/ 01 ноября 2019

Я пишу приложение с графическим интерфейсом на основе WPF, в котором пользователь может сохранять текущие значения поля ввода в различных пресетах, которые впоследствии могут быть загружены в любое время (например, после перезапуска приложения или когда пользовательнеобходимо восстановить определенный набор значений). Все поля, которые должны быть сохранены, используют привязку к полям в классе MainWindowViewModel, например:

public class MainWindowViewModel : INotifyPropertyChanged
    {

        [XmlIgnore]
        public int _UpperLimit_Textbox;
        public int UpperLimit_Textbox
        {
            get
            {
                return _UpperLimit_Textbox;
            }
            set
            {
                _UpperLimit_Textbox = value;
                NotifyPropertyChanged("UpperLimit_Textbox");
            }
        }
    }

Этот класс создается следующим образом:

public static MainWindowViewModel mainTaskpaneWindow_ModelView = new MainWindowViewModel();

И в Xaml,привязка выполняется следующим образом:

<TextBox Name="UpperLimit_Textbox" Width="30" HorizontalAlignment="Left"
    >
        <Binding
            Path="UpperLimit_Textbox"
            UpdateSourceTrigger="PropertyChanged"
            Mode="TwoWay">
        </Binding>
</TextBox>

Теперь, чтобы сохранить пресет, я просто сериализовал класс MainWindowViewModel в файл .xml:

static void SerializeMainTaskpaneModelView(string PresetPath)
    {
        StreamWriter streamWriter = new StreamWriter(path, true);
        streamWriter.WriteLine(SerializeObject(mainTaskpaneWindow_ModelView));

        streamWriter.Close();
    }

public static string SerializeObject<T>(this T toSerialize)
{
    XmlSerializer xmlSerializer = new XmlSerializer(toSerialize.GetType());

    using (StringWriter textWriter = new StringWriter())
    {
        xmlSerializer.Serialize(textWriter, toSerialize);
        return textWriter.ToString();
    }
}

Эта часть работает как шарм. Проблема в том, что я пытаюсь десериализовать этот объект обратно:

static void DeserializeMainTaskpaneModelView(string presetPath)
{
        StreamReader streamReader = new StreamReader(path);

        string str = streamReader.ReadToEnd();
        mainTaskpaneWindow_ModelView = Deserialize<MainWindowViewModel>(str);
}

public static T Deserialize<T>(this string toDeserialize)
{
    XmlSerializer xmlSerializer = new XmlSerializer(typeof(T));
    using (StringReader textReader = new StringReader(toDeserialize))
    {
        return (T)xmlSerializer.Deserialize(textReader);
    }
}

Теперь эта часть не работает. MainTaskpaneModelView десериализован, но связь с Xaml потеряна (изменения в полях ввода в графическом интерфейсе не отражаются в десериализованном объекте). Насколько я понимаю, обязательная ссылка почему-то не переносится со старого объекта на новый. Поэтому я реализовал другой подход - десериализацию в новый объект MainTaskpaneModelView, а затем копирование его свойств по значению по отдельности в старый объект MainTaskpaneModelView, например, так:

static void DeserializeMainTaskpaneModelView(string presetPath)
{
    MainWindowViewModel deserializedMainViewModel = new MainWindowViewModel();

    StreamReader streamReader = new StreamReader(path);

    string str = streamReader.ReadToEnd();
    deserializedMainViewModel = Deserialize<MainWindowViewModel>(str);


    CopyObjectProperties(deserializedMainViewModel, mainTaskpaneWindow_ModelView);
}

public static void CopyObjectProperties(this object source, object destination) 
{
    Type typeDest = destination.GetType();
    Type typeSrc = source.GetType();
    var results = from srcProp in typeSrc.GetProperties()
                  let targetProperty = typeDest.GetProperty(srcProp.Name)
                  where srcProp.CanRead
                  && targetProperty != null
                  && (targetProperty.GetSetMethod(true) != null && !targetProperty.GetSetMethod(true).IsPrivate)
                  && (targetProperty.GetSetMethod().Attributes & MethodAttributes.Static) == 0
                  && targetProperty.PropertyType.IsAssignableFrom(srcProp.PropertyType)
                  select new { sourceProperty = srcProp, targetProperty = targetProperty };
    foreach (var props in results)
    {
        props.targetProperty.SetValue(destination, props.sourceProperty.GetValue(source, null), null);
    }
}

Этот подход работает с примитивными типами полей ввода (string, int, double и т. д.), но когда я использую привязку со свойствами типа объекта (например, ListView для ObservableCollection), десериализация больше не работает с этими свойствами - ссылка на привязку теряется. Я предполагаю, что это потому, что ObservableCollection является объектом внутри объекта MainTaskpaneModelView, а метод CopyObjectProperties копирует ObservableCollection по ссылке, а не копирует его внутренние свойства по значению.

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

В качестве альтернативы мне интересно,возможно, я могу каким-то образом заменить привязанный к привязке MainTaskpaneModelView десериализованным MainTaskpaneModelView таким образом, чтобы привязка теперь ссылалась на десериализованный. Возможно, как-то перенести ссылочный указатель? Я даже не уверен, что это можно сделать в C #. Таким образом, мне не пришлось бы использовать метод CopyObjectProperties.

И у меня возникает ощущение, что это становится намного сложнее, чем должно быть, и должен быть какой-то более простой способ сохранить входные данныемой графический интерфейс в XML-файл, а затем восстановить их обратно, не испортив привязку. Может кто-нибудь посоветовать, пожалуйста?

...