Сохранение массива в Settings.settings

204
02 мая 2018, 03:13

Смог подружить Settings.settings с List<string>

<Setting Name="qq" Type="System.Collections.Generic.List&lt;System.String&gt;" Scope="User">

А реально такое провернуть с массивом ключ+значение? Пробовал с SortedDictionary

<Setting Name="ww" Type="System.Collections.Generic.SortedDictionary&lt;System.String,System.String&gt;" Scope="User">

И это даже заработало.. ненадолго. После перезапуска приложения всё сбрасывается (естественно, перед закрытием всё сохранил), а в файле user.config это выглядит так

<setting name="ww" serializeAs="Xml">
      <value />
</setting>

А так же, не открывается окно Свойства - Параметры, с ошибкой типа "обнаружены непонятные символы". Очевидно, что он ругается на запятую System.Collections.Generic.SortedDictionary&lt;System.String,System.String&gt;

Реально сохранить массив ключ+значение, или проще написать свою реализацию?

Answer 1

Можно, конечно, но с некоторыми заклинаниями.

Для начала, Settings сериализируются XML-сериализатором. Поэтому так просто из коробки сохранить то, что XML-сериализатор не умеет, не удастася. В частности, и словарь.

Попробуйте выполнить код

new XmlSerializer(typeof(SortedDictionary<string, string>));

— вы получите исключение: The type SortedDictionary`2[System.String, System.String] is not supported because it implements IDictionary.

Поэтому придётся писать обёртку, которая будет сохранять данные в «плоском» списке, и умеет сконструировать нужный словать из этого.

public class DictionaryProxy<K, V>
{
    public List<(K key, V value)> FlatList { get; set; }
    [XmlIgnore]
    public Dictionary<K, V> Dictionary
    {
        get => FlatList.ToDictionary(p => p.key, p => p.value);
        set => FlatList = value.Select(kvp => (kvp.Key, kvp.Value)).ToList();
    }
}

Далее нам понадобится трюк отсюда, чтобы заставить это взлететь. Положите в Settings значение безодидного (например, строкового) типа с названием DictWrapper, закройте Студию, и отредактируйте определение этого свойства Settings.settings так:

    <Setting Name="DictWrapper" Type="ТутВашНеймспейс.DictionaryProxy`2[System.String,System.String]" Scope="User">
      <Value Profile="(Default)">&lt;?xml version="1.0" encoding="utf-16"?&gt;
&lt;DictionaryProxyOfStringString xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"&gt;
  &lt;FlatList&gt;
    &lt;ValueTupleOfStringString&gt;
      &lt;Item1&gt;k1&lt;/Item1&gt;
      &lt;Item2&gt;v1&lt;/Item2&gt;
    &lt;/ValueTupleOfStringString&gt;
    &lt;ValueTupleOfStringString&gt;
      &lt;Item1&gt;k2&lt;/Item1&gt;
      &lt;Item2&gt;v2&lt;/Item2&gt;
    &lt;/ValueTupleOfStringString&gt;
  &lt;/FlatList&gt;
&lt;/DictionaryProxyOfStringString&gt;
</Value>
    </Setting>

(О том, как получить ужасное значение для Value, ниже.)

Далее, отредактируем и Settings.Designer.cs, заменим определение свойства на такое:

        [global::System.Configuration.UserScopedSettingAttribute()]
        [global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
        [global::System.Configuration.DefaultSettingValueAttribute(@"<?xml version=""1.0"" encoding=""utf-16""?>
<DictionaryProxyOfStringString xmlns:xsd=""http://www.w3.org/2001/XMLSchema"" xmlns:xsi=""http://www.w3.org/2001/XMLSchema-instance"">
  <FlatList>
    <ValueTupleOfStringString>
      <Item1>k1</Item1>
      <Item2>v1</Item2>
    </ValueTupleOfStringString>
    <ValueTupleOfStringString>
      <Item1>k2</Item1>
      <Item2>v2</Item2>
    </ValueTupleOfStringString>
  </FlatList>
</DictionaryProxyOfStringString>
")]
        public global::ТутВашНеймспейс.DictionaryProxy<string, string> DictWrapper {
            get {
                return ((global::ТутВашНеймспейс.DictionaryProxy<string, string>)(this["DictWrapper"]));
            }
            set {
                this["DictWrapper"] = value;
            }
        }

И можно пользоваться:

var dict = Properties.Settings.Default.DictWrapper.Dictionary;

Чтобы не набивать «вручную» текстовое представление сериализованного значения, можно использовать подготовительный код. Например, вот такой код

var o = new DictionaryProxy<string, string>();
o.Dictionary = new Dictionary<string, string>() { { "k1", "v1" }, { "k2", "v2" } };
var ss = new XmlSerializer(o.GetType());
using (var tw = new StringWriter())
{
    ss.Serialize(tw, o);
    var result = tw.GetStringBuilder().ToString();
}

производит в переменной result значение

<?xml version="1.0" encoding="utf-16"?>
<DictionaryProxyOfStringString xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
  <FlatList>
    <ValueTupleOfStringString>
      <Item1>k1</Item1>
      <Item2>v1</Item2>
    </ValueTupleOfStringString>
    <ValueTupleOfStringString>
      <Item1>k2</Item1>
      <Item2>v2</Item2>
    </ValueTupleOfStringString>
  </FlatList>
</DictionaryProxyOfStringString>

и вам остаётся лишь удвоить кавычки.

Чтобы заэкранировать знаки </>, можно воспользоваться, например, таким кодом:

var xt = new XText(result);
var encodedResult = xt.ToString();

Получается вот что:

&lt;?xml version="1.0" encoding="utf-16"?&gt;
&lt;DictionaryProxyOfStringString xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema"&gt;
  &lt;FlatList&gt;
    &lt;ValueTupleOfStringString&gt;
      &lt;Item1&gt;k1&lt;/Item1&gt;
      &lt;Item2&gt;v1&lt;/Item2&gt;
    &lt;/ValueTupleOfStringString&gt;
    &lt;ValueTupleOfStringString&gt;
      &lt;Item1&gt;k2&lt;/Item1&gt;
      &lt;Item2&gt;v2&lt;/Item2&gt;
    &lt;/ValueTupleOfStringString&gt;
  &lt;/FlatList&gt;
&lt;/DictionaryProxyOfStringString&gt;
READ ALSO
Управление закрытием форм winforms c#

Управление закрытием форм winforms c#

У меня есть две формы: FormStartMenu и Form15PuzzleМне нужно, чтобы при запуске приложения запускалась форма FormStartMenu, а потом из нее можно было бы перейти...

192
С# и app.config

С# и app.config

имеется программа которая с помощью CodeDom делает ещё одну программу, извините за тавтологиюВ c# для exe приложений можно создавать файл app

131
Почему метод receive не блокирует поток?

Почему метод receive не блокирует поток?

Чего хочу: получить данные с удалённого модуля по сокетуКак реализую: пока в одном потоке, чтобы проще было делать отладку

216