Настройки и 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. Конечно, это может быть не то, что вы хотите;)