Зберігання даних у коді


17

Кілька разів у минулому я хотів зберігати дані в коді. Це дані, які рідко змінюються і використовуються в місцях, де доступ до бази даних неможливий, практичний або бажаний. Невеликим прикладом може бути зберігання списку країн. Для цього ви можете зробити щось на кшталт:

public class Country
{
    public string Code { get; set; }
    public string EnglishName {get;set;}
}

public static class CountryHelper
{
    public static List<Country> Countries = new List<Country>
        {
            new Country {Code = "AU", EnglishName = "Australia"},
            ...
            new Country {Code = "SE", EnglishName = "Sweden"},
            ...
        };

    public static Country GetByCode(string code)
    {
        return Countries.Single(c => c.Code == code);
    }
}

У минулому я пішов із цього, бо набори даних були відносно невеликими, а об'єкти - досить простими. Зараз я працюю над тим, що матиме складніші об’єкти (5 - 10 властивостей у кожному, деякі властивості - словники) та загалом близько 200 об'єктів.

Самі дані змінюються дуже рідко, а коли вони змінюються, це насправді навіть не так важливо. Тож прокат його до наступної версії - цілком чудовий.

Я планую використовувати T4 або ERB або якесь інше шаблонне рішення, щоб перетворити своє джерело даних на те, що статично зберігається в збірці.

Здається, мої варіанти є

  1. Зберігання даних у XML. Складіть XML-файл як ресурс збирання. Завантажте дані за потребою, зберігайте завантажені дані у словник для виконання повторного використання.
  2. Створити якийсь статичний об'єкт або об'єкти, які ініціалізуються при запуску.

Я майже впевнений, що розумію наслідки для варіанту 1. Принаймні, моя думка полягає в тому, що він не мав би зоряної продуктивності.

Щодо варіанту 2, я не знаю, що робити. Я не знаю достатньо внутрішніх даних .NET Framework, щоб знати найкращий спосіб фактично зберігати ці дані в коді C # та найкращі способи їх ініціалізації. Я роздумував за допомогою рефлектора .NET, щоб побачити, як System.Globalization.CultureInfo.GetCulture(name)працює, оскільки це насправді дуже схожий робочий процес на той, що я хочу. На жаль, ця стежка закінчилася extern, тому ніяких натяків там немає. Чи іде ініціалізація статичної властивості з усіма даними, як у моєму прикладі? Або краще було б створити об’єкти на вимогу, а потім кешувати їх, як це?

    private static readonly Dictionary<string, Country> Cache = new Dictionary<string,Country>(); 

    public static Country GetByCode(string code)
    {
        if (!Cache.ContainsKey(code))
            return Cache[code];

        return (Cache[code] = CreateCountry(code));
    }

    internal static Country CreateCountry(string code)
    {
        if (code == "AU")
            return new Country {Code = "AU", EnglishName = "Australia"};
        ...
        if (code == "SE")
            return new Country {Code = "SE", EnglishName = "Sweden"};
        ...
        throw new CountryNotFoundException();
    }

Перевага їх створення відразу в статичному члені полягає в тому, що ви можете використовувати LINQ або що-небудь ще, щоб переглянути всі об'єкти і запитувати їх, якщо хочете. Хоча я підозрюю, що за це загрожує стартовий показник. Я сподіваюся, що хтось має досвід з цим і може поділитися своєю думкою!


5
200 об’єктів? Я не думаю, що ви повинні турбуватися про продуктивність, особливо якщо це разова вартість.
svick

1
У вас є інший варіант, який полягає в тому, щоб зберігати об'єкти в коді, а потім передавати посилання на ін'єкцію через залежність як звичайне. Таким чином, якщо ви хочете змінити спосіб їх побудови, ви не маєте статичних посилань, що вказують безпосередньо на них звідусіль.
Емі Бланкенсіп

@svick: Це правда. Я, мабуть, надто обережний щодо продуктивності.
марок

@AmyBlankenship Я завжди використовував допоміжні методи, але DI - хороша ідея. Я не думав про це. Я підкажу і побачу, чи подобається мені візерунок. Спасибі!
марок

" Самі дані змінюються дуже рідко, і коли вони змінюються, це насправді навіть не так важливо ". Скажіть це боснійцям, сербам, хорватам, українцям, Південному Судані, колишнім Південному Ємену, колишнім Радам та ін. Скажіть, що кожному програмісту, який мав справу з переходом до євро, або програмістам, яким, можливо, доведеться мати справу з можливим виходом Греції з неї. Дані належать до структур даних. XML-файли працюють добре, і їх можна легко змінити. Бази даних Ditto SQL.
Росс Паттерсон

Відповіді:


11

Я б пішов з варіантом один. Це легко і легко читати. Хтось ще, хто дивиться на ваш код, зрозуміє його відразу. Також буде простіше оновити свої XML-дані, якщо виникне потреба. (Крім того, ви можете дозволити користувачу оновити їх, якщо у вас є гарний передній кінець і збережені файли окремо)

Оптимізуйте її лише за потреби - передчасна оптимізація - це зло :)


3
Якщо ви пишете C #, ви працюєте на платформі, яка може швидко розібрати невеликі XML-освітлення. Тож не варто займатись питаннями продуктивності.
Джеймс Андерсон

7

Враховуючи, що це по суті пари ключ-значення, я рекомендую зберігати ці рядки як файл ресурсу, вбудований у вашу збірку під час компіляції. Потім їх можна прочитати за допомогою ResourceManger. Ваш код виглядатиме приблизно так:

private static class CountryHelper
{
    private static ResourceManager rm;

    static CountryHelper()
    {
        rm = new ResourceManager("Countries",  typeof(CountryHelper).Assembly);
    }

    public static Country GetByCode(string code)
    {
        string countryName = rm.GetString(code + ".EnglishName");
        return new Country { Code = code, EnglishName = countryName };
    }
}

Щоб зробити його трохи ефективнішим, ви можете завантажити їх усі одразу так:

private static class CountryHelper
{
    private static Dictionary<string, Country> countries;

    static CountryHelper()
    {
        ResourceManager rm = new ResourceManager("Countries",  typeof(CountryHelper).Assembly);
        string[] codes = rm.GetString("AllCodes").Split("|"); // AU|SE|... 
        countries = countryCodes.ToDictionary(c => c, c => CreateCountry(rm, c));
    }

    public static Country GetByCode(string code)
    {
        return countries[code];
    }

    private static Country CreateCountry(ResourceManager rm, string code)
    {
        string countryName = rm.GetString(code + ".EnglishName");
        return new Country { Code = "SE", EnglishName = countryName };
    }
}

Це допоможе вам багато чого, якщо вам буде потрібно, щоб ваша програма підтримувала кілька мов.

Якщо це стосується програми WPF, словник ресурсів - набагато очевидніше рішення.


2

Рішення насправді: чи хочете ви, щоб тільки розробник (або хтось із доступом до системи збирання) міняв дані, коли потребує модифікації, чи повинен кінцевий користувач або, можливо, хтось із організації кінцевого користувача зможе змінити дані?

В останньому випадку я б запропонував, щоб файл у читаному для користувача форматі зберігався у місці, де користувач може отримати доступ до нього. Очевидно, що читаючи дані, потрібно бути обережними; все, що ви знайдете, не можна вважати достовірними даними. Я, мабуть, віддав перевагу JSON перед XML; Мені легше зрозуміти і отримати правильно. Ви можете перевірити, чи є редактор для формату даних; наприклад, якщо ви використовуєте пліст на MacOS X, то кожен користувач, який коли-небудь повинен наблизитися до даних, матиме гідний редактор на своєму комп’ютері.

У першому випадку, якщо дані є частиною вашої збірки, це насправді не має значення. Робіть те, що вам найзручніше. У програмі C ++ запис даних із мінімальними накладними витратами є одним з небагатьох місць, де дозволені макроси. Якщо робота із збереження даних проводиться третьою стороною, ви можете надіслати їм вихідні файли, дозволити їх редагувати, а ви потім відповідаєте за їх перевірку та використання у вашому проекті.


1

Найкращий приклад того, що існує - це, мабуть, WPF ... Ваші форми написані в XAML, що в основному є XML, за винятком того, що .NET здатний дуже швидко регідратати його в представлення об'єкта. Файл XAML компілюється у файл BAML і видавлюється у файл ".resources", який потім вбудовується в збірку (остання версія рефлектора .NET може показати вам ці файли).

Хоча не існує жодної чудової документації для її підтримки, MSBuild насправді повинен мати можливість взяти файл XAML, перетворити його в BAML та вставити його для вас (це робиться для форм, правда?).

Отже, моїм підходом було б: зберігайте свої дані в XAML (це лише XML-представлення об’єктного графіка), вкладіть цей XAML у файл ресурсів та вставте їх у збірку. Під час виконання візьміть XAML та використовуйте XamlServices для його повторного зволоження. Тоді або загорніть його в статичний елемент, або використовуйте Dependency Injection, якщо ви хочете, щоб він був більш перевіреним, щоб споживати його з решти коду.

Кілька посилань, що корисні довідкові матеріали:

З іншого боку, ви можете використовувати файли app.config або web.config для завантаження досить складних об'єктів через механізм налаштувань, якщо ви хочете, щоб дані легше змінювались. Ви також можете завантажити XAML з файлової системи.


1

Я думаю, що System.Globalization.CultureInfo.GetCulture (ім'я) закликає API Windows, щоб отримати дані з системного файлу.

Для завантаження даних у код, так, як сказав @svick, якщо ви збираєтеся завантажити 200 об'єктів, у більшості випадків це не буде проблемою, якщо ваш код не працює на деяких пристроях з низькою пам'яттю.

Якщо ваші дані ніколи не змінюються, мій досвід полягає у використанні списку / словника на зразок:

private static readonly Dictionary<string, Country> _countries = new Dictionary<string,Country>();

Але проблема полягає в тому, що вам потрібно знайти спосіб ініціалізації свого словника. Я думаю, це больовий момент.

Тож проблема змінюється на "Як генерувати ваші дані?". Але це стосується того, що вам потрібно.

Якщо ви хочете генерувати дані для країн, ви можете використовувати

System.Globalization.CultureInfo.GetCultures()

отримати масив CultrueInfo, і ви можете ініціалізувати свій словник під свої потреби.

І звичайно, ви можете помістити код у статичний клас та ініціалізувати словник у статичному конструкторі на зразок:

public static class CountryHelper
{
    private static readonly Dictionary<string, Country> _countries;
    static CountryHelper()
    {
        _countries = new Dictionary<string,Country>();
        // initialization code for your dictionary
        System.Globalization.CultureInfo[] cts = System.Globalization.CultureInfo.GetCultures(System.Globalization.CultureTypes.AllCultures);
        for(int i=0; i < cts.Length; i++)
        {
            _countries.Add(cts[i].Name, new Country(cts[i]));
        }
    }

    public static Country GetCountry(string code)
    {
        Country ct = null;
        if(this._countries.TryGet(code, out ct))
        {
            return ct;
        } else
        {
            Log.WriteDebug("Cannot find country with code '{0}' in the list.", code);
            return null;
        }
    }
}
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.