通过外部参考修改设置时的设置和IsDirty

时间:2015-05-11 12:22:54

标签: c# settings.settings

我有一个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 个答案:

答案 0 :(得分:1)

你似乎在解决这个问题的一大堆问题。正确地说,通过直接访问诸如Settings.Default之类的属性来完成更改。工作得很好。&#34;修改内存引用不会导致设置自动更新。 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的末尾运行一些代码来保留此列表以及您需要的任何其他设置。任何更改仍将在内存中,只要您持有参考对象&amp;将一直存在,直到你终止申请。

如果您需要在应用程序运行时保存设置,只需创建自己的&(;)方法的Save()方法。当用户执行需要保存设置的操作时,也可以从Main()的末尾调用它。 e.g。

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

编辑:以下评论中提到的一个警告。

从我的基准测试来看,这种方法非常慢,这意味着以这种方式在设置中保留大型对象列表并不是一个好主意。一旦您拥有多个属性,您可能需要查看像SQLite这样的嵌入式数据库。对于大量简单的设置,CSV,INI或XML文件也可以作为选项。更简单的存储格式的一个好处是非开发人员可以轻松修改,例如excel中的csv。当然这可能不是你想要的;)

相关问题