Як зберігати масив int [] у налаштуваннях програми


93

Я створюю просту програму Windows Forms за допомогою C # express 2008. Я досвідчений розробник C ++, але я в значній мірі абсолютно новий у C # та .NET.

На даний момент я зберігаю деякі свої прості налаштування програми за допомогою дизайнера налаштувань і коду, подібного до цього:

// Store setting  
Properties.Settings.Default.TargetLocation = txtLocation.Text;  
...  
// Restore setting  
txtLocation.Text = Properties.Settings.Default.TargetLocation;  

Тепер я хотів би зберегти або масив ints ( int[]), або, можливо, Список ints ( List< int >), як налаштування. Однак я не можу зрозуміти, як це зробити. Я шукав документацію, stackoverflow і google, і не можу знайти гідного пояснення, як це зробити.

Моя здогадка, заснована на рідкісних прикладах, які я знайшов, полягає в тому, що я повинен створити клас, який можна серіалізувати, який обгортає мій масив або Список, і тоді я зможу використовувати цей тип у конструкторі налаштувань. Однак я не впевнений, як саме це зробити.

Відповіді:


137

Існує також інше рішення - потрібно трохи редагувати файл налаштувань вручну, але згодом чудово працює у середовищі VS та в коді. І не вимагає додаткових функцій або обгортки.

Справа в тому, що VS дозволяє серіалізувати int[]тип за замовчуванням у файлі налаштувань - це просто не дозволяє вам вибрати його за замовчуванням. Отже, створіть налаштування з потрібною назвою (наприклад, SomeTestSetting) і зробіть його будь-яким типом (наприклад, stringза замовчуванням). Збережіть зміни.

Тепер перейдіть до папки проекту та відкрийте файл "Properties \ Settings.settings" за допомогою текстового редактора (наприклад, Блокнот). Або ви можете відкрити його у VS, клацнувши правою кнопкою миші в Провіднику рішень на "-> Властивості -> Settings.settings ", виберіть" Відкрити за допомогою ... ", а потім виберіть" Редактор XML "або" Редактор вихідного коду (тексту) ". У відкритих налаштуваннях xml знайдіть своє налаштування (воно буде виглядати так):

<Setting Name="SomeTestSetting" Type="System.String" Scope="User">
  <Value Profile="(Default)" />
</Setting>

Змініть параметр "Тип" з System.Stringна System.Int32[]. Тепер цей розділ буде виглядати так:

<Setting Name="SomeTestSetting" Type="System.Int32[]" Scope="User">
  <Value Profile="(Default)" />
</Setting>

Тепер збережіть зміни та знову відкрийте налаштування проекту - voilà! - У нас є параметр SomeTestSetting з типом, до System.Int32[]якого можна отримати доступ та редагувати через VS Settings Designer (значення теж), а також у коді.


5
Приголомшливо Щоб щось ввести в налаштування за допомогою редактора у Visual Studio, слід вставити щось подібне, це для масиву рядків, який мені потрібен <? Xml version = "1.0" encoding = "utf-16"?> <ArrayOfString xmlns: xsi = " w3.org/2001/XMLSchema-instance " xmlns: xsd = " w3.org/2001/XMLSchema "> <string> String1 </string> <string> String2 </string> </ArrayOfString>
Karsten

9
+1 .. не розумію, чому це не було прийнято відповідь .. додатковий код не потрібен (просто змініть один рядок існуючого xml), і ви отримаєте безпеку типу та повну підтримку дизайнера!
Кріс

1
Кріс, дякую :) Справа в тому, що я додав свою відповідь набагато пізніше, ніж спочатку прийняту відповідь (насправді приблизно через рік :)). Просто ділюсь досвідом :)
Джен-Арі

6
Для тих, хто цікавиться, синтаксис XML-файлу конфігураційного файлу int[]буде виглядати так (крім досить надрукованого):<setting name="SomeTestSetting" serializeAs="String"><value><ArrayOfInt xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema"><int>1</int><int>2</int><int>3</int></ArrayOfInt></value></setting>
aaaantoine

1
Згоден, це краща відповідь, тому я змінив позначку.
sidewinderguy

40

зберігати:

string value = String.Join(",", intArray.Select(i => i.ToString()).ToArray());

повторно створити:

int[] arr = value.Split(',').Select(s => Int32.Parse(s)).ToArray();

Редагувати: пропозиція Абеля!


Гаразд, отже, виглядає так, що ви серіалізуєте дані вручну у рядок, і навпаки. Я думаю, це могло б спрацювати, я міг би просто зберігати дані як параметр рядка. Я думав зробити щось подібне, але шукав щось більш "чисте", "стандартне" або просто більше .NET'ish. Однак я пам’ятаю про це, якщо нічого кращого не з’явиться. Дякую.
sidewinderguy

Привіт, тоді я поїхав би з проспектом Маккея! (розділ конфігурації / група розділів)
Майк Глісон молодший Кутюр'є

Я збираюся піти з цим, тому що це просто. Дякую за всі ідеї всім, я впевнений, що вони допоможуть у цьому напрямку.
sidewinderguy

2
Примітка. Вам потрібно буде System.Linqдодати до використання / імпорту, щоб вищевказаний фокус працював.
Шон Хенлі,

11

Є ще один спосіб досягти цього результату, який набагато чистіший у використанні, але вимагає більше коду. Моя реалізація нестандартного перетворювача типів і типів можливий наступний код:

List<int> array = Settings.Default.Testing;
array.Add(new Random().Next(10000));
Settings.Default.Testing = array;
Settings.Default.Save();

Для цього вам потрібен тип з перетворювачем типів, що дозволяє перетворювати в і з рядків. Ви робите це, прикрашаючи тип за допомогою TypeConverterAttribute:

[TypeConverter(typeof(MyNumberArrayConverter))]
public class MyNumberArray ...

Потім реалізуємо цей перетворювач типів як похідну TypeConverter:

class MyNumberArrayConverter : TypeConverter
{
    public override bool CanConvertTo(ITypeDescriptorContext ctx, Type type)
    { return (type == typeof(string)); }

    public override bool CanConvertFrom(ITypeDescriptorContext ctx, Type type)
    { return (type == typeof(string)); }

    public override object ConvertTo(ITypeDescriptorContext ctx, CultureInfo ci, object value, Type type)
    {
        MyNumberArray arr = value as MyNumberArray;
        StringBuilder sb = new StringBuilder();
        foreach (int i in arr)
            sb.Append(i).Append(',');
        return sb.ToString(0, Math.Max(0, sb.Length - 1));
    }

    public override object ConvertFrom(ITypeDescriptorContext ctx, CultureInfo ci, object data)
    {
        List<int> arr = new List<int>();
        if (data != null)
        {
            foreach (string txt in data.ToString().Split(','))
                arr.Add(int.Parse(txt));
        }
        return new MyNumberArray(arr);
    }
}

Забезпечивши деякі зручні методи для класу MyNumberArray, які ми можемо потім безпечно присвоїти списку та з нього, повний клас буде виглядати приблизно так:

[TypeConverter(typeof(MyNumberArrayConverter))]
public class MyNumberArray : IEnumerable<int>
{
    List<int> _values;

    public MyNumberArray() { _values = new List<int>(); }
    public MyNumberArray(IEnumerable<int> values) { _values = new List<int>(values); }

    public static implicit operator List<int>(MyNumberArray arr)
    { return new List<int>(arr._values); }
    public static implicit operator MyNumberArray(List<int> values)
    { return new MyNumberArray(values); }

    public IEnumerator<int> GetEnumerator()
    { return _values.GetEnumerator(); }
    IEnumerator IEnumerable.GetEnumerator()
    { return ((IEnumerable)_values).GetEnumerator(); }
}

Нарешті, щоб використовувати це в налаштуваннях, ви додаєте вищезгадані класи до збірки та компіляції. У вашому редакторі Settings.settings ви просто натискаєте опцію "Огляд" і вибираєте клас MyNumberArray, і ви йдете.

Знову ж таки, це набагато більше коду; однак його можна застосувати до набагато складніших типів даних, ніж простий масив.


Дякую, це виглядає цікаво. Я спробую це, коли отримаю можливість.
sidewinderguy

3

Вкажіть параметр як System.Collections.ArrayList, а потім:

Settings.Default.IntArray = new ArrayList(new int[] { 1, 2 });

int[] array = (int[])Settings.Default.IntArray.ToArray(typeof(int));

2

Просте рішення - встановити значення значення за замовчуванням для значення null у властивості, але в конструкторі перевірити, чи властивість є нулем, і якщо так, то встановити для нього фактичне значення за замовчуванням. Отже, якщо ви хочете масив ints:

public class ApplicationSettings : ApplicationSettingsBase
{
    public ApplicationSettings()
    {
        if( this.SomeIntArray == null )
            this.SomeIntArray = new int[] {1,2,3,4,5,6};
    }

    [UserScopedSetting()]
    [DefaultSettingValue("")]
    public int[] SomeIntArray
    {
        get
        {
            return (int[])this["SomeIntArray"];
        }
        set
        {
            this["SomeIntArray"] = (int[])value;
        }
    }
}

Він відчуває себе трохи хакерським, але чистий і працює за бажанням, оскільки властивості ініціалізуються до останніх (або за замовчуванням) налаштувань перед викликом конструктора.


2
Дизайнер для файлів налаштування додатків автоматично генерує код, і тому така зміна буде замінена в будь-який час, коли хтось використовував конструктор, навіть випадково.
Carl G

1

Використовується System.Object.

Приклад:

byte[] arBytes = new byte[] { 10, 20, 30 };
Properties.Settings.Default.KeyObject = arBytes;

Екстракт:

arBytes = (byte[])Properties.Settings.Default.KeyObject;

1
Я спробував використовувати System.Object, але налаштування не зберігалися між сесіями. (Так, це була збірка випуску, автономна установка, як би ви не хотіли це називати, я не налагоджував за допомогою IDE)
Емануель Вінтіла

0

Я думаю, ви маєте рацію щодо серіалізації налаштувань. Див. Мою відповідь на це запитання для зразка:

Прийоми спільного використання конфігурації між двома програмами?

Ви мали б властивість, яка є масивом, наприклад:

/// <summary>
/// Gets or sets the height.
/// </summary>
/// <value>The height.</value>
[XmlAttribute]
public int [] Numbers { get; set; }

0

Зробіть кілька функцій, які перетворюють масив int у рядок, але між кожною вводять символ типу "" (пробіл).

Отже, якщо масив дорівнює {1,34,546,56}, рядок буде "1 34 645 56"

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.