Настройки и IsDirty при изменении настроек через внешние ссылки

У меня есть приложение C# с полем настроек, которое представляет собой набор пользовательских объектов.

Когда я запускаю приложение, я создаю несколько экземпляров класса, которые берут экземпляры из коллекции записей настроек, сохраняют внутреннюю ссылку на них и изменяют их. Во время отладки я увидел, что изменения, сделанные с помощью этих внешних ссылок, не отражаются при вызовеSettings.Default.Save() но изменения сделаны путем прямого доступа к таким свойствам, какSettings.Default.<property> отлично работает

Я посмотрел код, ответственный за Save() и увидел, что реализация фактически проверяет поле SettingsPropertyValue.IsDirty, чтобы решить, следует ли его сериализовать или нет. Конечно, когда я получаю доступ к внешним ссылкам на объекты в поле настроек, это значение не устанавливается.

Есть ли легкое решение для этого?

Я не думаю, что я первый, кто столкнулся с этим. Один из способов, который я могу придумать, - это реализовать IsDirty свойство в коллекциях я сериализую и добавляю INotifyPropertyChanged интерфейсное событие PropertyChanged для всех содержащихся экземпляров, чтобы контейнер уведомлялся об изменениях и мог отражать их в свойстве фактических настроек. Но это означает обертывание каждого из классов настроек с помощью этой логики. Итак, что я прошу, так это то, что кто-нибудь, кто сталкивался с этой проблемой, найдет легкое решение этой проблемы.

пример

Рассмотрим этот класс:

namespace SettingsTest
{
    [DataContract(Name="SettingsObject")]
    public class SettingsObject
    {
        [DataMember]
        public string Property { get; set; }
    }
}

И следующая программа:

namespace SettingsTest
{
    class Program
    {
        static void Main(string[] args)
        {
            var settingsObjects = new List<SettingsObject>();
            var settingsObject = new SettingsObject{Property = "foo"};
            settingsObjects.Add(settingsObject);
            Settings.Default.SettingsObjects = settingsObjects;
            Settings.Default.Save();
            settingsObject.Property = "bar";
            Settings.Default.Save();
        }
    }
}

После второго Save() вызвать окончательный вывод в user.config файл:

<?xml version="1.0" encoding="utf-8"?>
<configuration>
    <userSettings>
        <SettingsTest.Settings>
            <setting name="SettingsObjects" serializeAs="Xml">
                <value>
                    <ArrayOfSettingsObject xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
                        xmlns:xsd="http://www.w3.org/2001/XMLSchema">
                        <SettingsObject>
                            <Property>foo</Property>
                        </SettingsObject>
                    </ArrayOfSettingsObject>
                </value>
            </setting>
        </SettingsTest.Settings>
    </userSettings>
</configuration>

Как вы можете увидеть изменение свойства через внешнюю ссылку на SettingsObject экземпляр не сохранился.

1 ответ

Похоже, вы затрагиваете кучу вопросов этим вопросом. Как вы правильно сказали, "изменения, сделанные прямым доступом к таким свойствам, как Sets.s.Default. Работают нормально". Изменение ссылок в памяти не приведет к автоматическому обновлению настроек по вашему желанию. Изменение INotifyProperty является одним из решений.

Если вам нужен быстрый и грязный способ сериализации этих объектов в файл настроек, вы можете использовать следующий код.

Вовлеченные объекты:

/// <summary>Generic class to support serializing lists/collections to a settings file.</summary>
[Serializable()]
public class SettingsList<T> : System.Collections.ObjectModel.Collection<T> 
{
    public string ToBase64()
    {
        // If you don't want a crash& burn at runtime should probaby add 
        // this guard clause in: 'if (typeof(T).IsDefined(typeof(SerializableAttribute), false))'
            using (var stream = new MemoryStream())
            {
                var formatter = new BinaryFormatter();
                formatter.Serialize(stream, this);
                stream.Position = 0;
                byte[] buffer = new byte[(int)stream.Length];
                stream.Read(buffer, 0, buffer.Length);
                return Convert.ToBase64String(buffer);
            }
    }
    public static SettingsList<T> FromBase64(string settingsList)
    {
        using (var stream = new MemoryStream(Convert.FromBase64String(settingsList)))
        {
            var deserialized = new BinaryFormatter().Deserialize(stream);

            return (SettingsList<T>)deserialized;
        }
    }  
}
[Serializable()]
public class SettingsObject
{
    public string Property { get; set; }

    public SettingsObject()
    {
    }
}

Основной метод демонстрирует проблему, с которой вы сталкиваетесь, с помощью решения.

class Program
{
    static void Main(string[] args)
    {
        // Make sure we don't overwrite the previous run's settings.
        if (String.IsNullOrEmpty(Settings.Default.SettingsObjects))
        {
            // Create the initial settings.
            var list = new SettingsList<SettingsObject> { 
                new SettingsObject { Property = "alpha" }, 
                new SettingsObject { Property = "beta" }
            };
            Console.WriteLine("settingsObject.Property[0] is {0}", list[0].Property);

            //Save initial values to Settings
            Settings.Default.SettingsObjects = list.ToBase64();
            Settings.Default.Save();

            // Change a property
            list[0].Property = "theta";

            // This is where you went wrong, settings will not be persisted at this point
            // because you have only modified the in memory list.

            // You need to set the property on settings again to persist the value.
            Settings.Default.SettingsObjects = list.ToBase64();
            Settings.Default.Save();
        }
        // pull that property back out & make sure it saved.
        var deserialized = SettingsList<SettingsObject>.FromBase64(Settings.Default.SettingsObjects);
        Console.WriteLine("settingsObject.Property[0] is {0}", deserialized[0].Property);            

        Console.WriteLine("Finished! Press any key to continue.");
        Console.ReadKey();
    }

}

Так что все, что я делаю, это хранит весь ваш список объектов в виде строки в кодировке base64. Затем мы производим десериализацию при следующем запуске.

Из того, что я понимаю, вопрос, который вы хотите хранить только в памяти, не затрагивая объект настроек. Вы можете просто запустить некоторый код в конце main, чтобы сохранить этот список и любые другие настройки, которые вам нужны. Любые изменения будут оставаться в памяти до тех пор, пока вы удерживаете ссылку на объект и будут оставаться до тех пор, пока вы не закроете приложение.

Если вам нужно сохранить настройки во время работы приложения, просто создайте собственный метод Save() и вызывайте его с конца Main(), а также когда пользователь выполняет действие, требующее сохранения настроек. например

public static void SaveSettings(SettingsList list)
{
    Settings.Default.SettingsObjects = list.ToBase64();
    Settings.Default.Save();
}

Изменить: одна оговорка, как указано в комментариях ниже.

Исходя из моих тестов, этот метод очень медленный, а это значит, что не стоит сохранять таким образом большие списки объектов в настройках. Если у вас есть более чем несколько свойств, вы можете захотеть взглянуть на встроенную базу данных, такую ​​как SQLite. Файл CSV, INI или XML также может быть вариантом для большого количества тривиальных настроек. Одним из преимуществ более простых форматов хранения является легкая модификация сторонними разработчиками, например, CSV в Excel. Конечно, это может быть не то, что вы хотите;)

Другие вопросы по тегам