Stockage des données dans le code


17

Quelques fois dans mon passé, j'ai voulu stocker des données dans du code. Ce sont des données qui changent rarement et qui sont utilisées dans des endroits où l'accès à une base de données n'est pas possible, pratique ou souhaitable. Un petit exemple serait de stocker une liste de pays. Pour cela, vous pouvez faire quelque chose comme:

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);
    }
}

Je m'en suis éloigné dans le passé parce que les ensembles de données étaient relativement petits et les objets assez simples. Maintenant, je travaille avec quelque chose qui aura des objets plus complexes (5 à 10 propriétés chacun, certaines propriétés étant des dictionnaires) et environ 200 objets au total.

Les données elles-mêmes changent très rarement et quand elles changent, ce n'est vraiment même pas si important. Donc, le rouler dans la prochaine version est parfaitement bien.

Je prévois d'utiliser T4 ou ERB ou une autre solution de modélisation pour transformer ma source de données en quelque chose qui est stocké statiquement dans l'assembly.

Il semble que mes options soient

  1. Stockez les données en XML. Compilez le fichier XML en tant que ressource d'assembly. Chargez les données selon vos besoins, stockez les données chargées dans un dictionnaire pour des performances de réutilisation.
  2. Générez une sorte d'objet statique ou des objets qui sont initialisés au démarrage.

Je suis à peu près sûr de comprendre les implications de l'option 1 pour les performances. Au moins, mon intuition est qu'elle n'aurait pas de performances exceptionnelles.

Quant à l'option 2, je ne sais pas quoi faire. Je ne connais pas suffisamment les éléments internes du framework .NET pour connaître la meilleure façon de stocker réellement ces données en code C # et les meilleures façons de les initialiser. J'ai fouillé en utilisant le réflecteur .NET pour voir comment cela System.Globalization.CultureInfo.GetCulture(name)fonctionne, car il s'agit en fait d'un flux de travail très similaire à ce que je veux. Malheureusement, ce sentier s'est terminé à un extern, donc aucun indice là-bas. L'initialisation d'une propriété statique avec toutes les données, comme dans mon exemple, est-elle la voie à suivre? Ou serait-il préférable de créer les objets à la demande, puis de les mettre en cache, comme ceci?

    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();
    }

L'avantage de les créer à la fois dans un membre statique est que vous pouvez utiliser LINQ ou quoi que ce soit d'autre pour regarder tous les objets et les interroger si vous le souhaitez. Bien que je soupçonne que cela ait une pénalité de performance au démarrage. J'espère que quelqu'un a de l'expérience avec cela et peut partager ses opinions!


5
200 objets? Je ne pense pas que vous ayez à vous soucier des performances, surtout si c'est un coût unique.
svick

1
Vous avez une autre option, qui consiste à stocker les objets dans le code, puis à transmettre une référence à l'injection de dépendance traversante comme d'habitude. De cette façon, si vous voulez changer la façon dont ils sont construits, vous n'avez pas de références statiques pointant directement vers eux de partout.
Amy Blankenship

@svick: C'est vrai. Je suis probablement trop prudent sur les performances.
mroach

@AmyBlankenship J'utiliserais toujours des méthodes d'assistance, mais DI est une bonne idée. Je n'y avais pas pensé. Je vais essayer et voir si j'aime le motif. Merci!
mroach

" Les données elles-mêmes changent très rarement et quand elles changent, ce n'est vraiment pas si important que cela. " Dites cela aux Bosniaques, Serbes, Croates, Ukraniens, Soudanais du Sud, anciens Yéménites du Sud, anciens Soviétiques, et al. Dites cela à tous les programmeurs qui ont dû faire face à la transition vers l'euro, ou aux programmeurs qui pourraient avoir à faire face à la sortie potentielle de la Grèce. Les données appartiennent aux structures de données. Les fichiers XML fonctionnent bien et peuvent être facilement modifiés. Bases de données idem SQL.
Ross Patterson

Réponses:


11

J'irais avec la première option. C'est simple et facile à lire. Quelqu'un d'autre qui regarde votre code le comprendra immédiatement. Il sera également plus facile de mettre à jour vos données XML si nécessaire. (De plus, vous pouvez laisser l'utilisateur les mettre à jour si vous avez un bon frontal et stocké les fichiers séparément)

Optimisez-le uniquement si vous en avez besoin - l'optimisation prématurée est mauvaise :)


3
Si vous écrivez C #, vous exécutez sur une plate-forme qui peut analyser rapidement les petits XMLs. Donc, cela ne vaut pas la peine de se soucier des problèmes de performances.
James Anderson

7

Étant donné qu'il s'agit essentiellement de paires clé-valeur, je recommande de stocker ces chaînes en tant que fichier de ressources incorporé dans votre assembly au moment de la compilation. Vous pouvez ensuite les lire en utilisant a ResourceManger. Votre code ressemblerait à ceci:

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 };
    }
}

Pour le rendre un peu plus efficace, vous pouvez les charger tous en même temps, comme ceci:

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 };
    }
}

Cela vous aidera beaucoup si vous avez besoin de faire en sorte que votre application prenne en charge plusieurs langues.

S'il s'agit d'une application WPF, un dictionnaire de ressources est une solution beaucoup plus évidente.


2

La décision est vraiment: voulez-vous que seul le développeur (ou toute personne ayant accès à un système de build) modifie les données quand il doit être modifié, ou si un utilisateur final ou éventuellement quelqu'un dans l'organisation de l'utilisateur final est en mesure de modifier le Les données?

Dans ce dernier cas, je suggère qu'un fichier dans un format lisible et modifiable par l'utilisateur soit stocké dans un endroit où l'utilisateur peut y accéder. Évidemment, lorsque vous lisez les données, vous devez être prudent; tout ce que vous trouvez ne peut être considéré comme une donnée valide. Je préférerais probablement JSON à XML; Je trouve plus facile à comprendre et à bien faire. Vous pouvez vérifier si un éditeur est disponible pour le format de données; par exemple, si vous utilisez un plist sur MacOS X, chaque utilisateur qui devrait s'approcher des données aura un éditeur correct sur son ordinateur.

Dans le premier cas, si les données font partie de votre build, cela ne fait pas vraiment de différence. Faites ce qui vous convient le mieux. En C ++, l'écriture des données avec une surcharge minimale est l'un des rares endroits où les macros sont autorisées. Si le travail de maintenance des données est effectué par un tiers, vous pouvez leur envoyer les fichiers source, les laisser les éditer, et vous êtes ensuite responsable de les vérifier et de les utiliser dans votre projet.


1

Le meilleur exemple de cela qui existe est probablement WPF ... Vos formulaires sont écrits en XAML, qui est fondamentalement XML, sauf que .NET est capable de le réhydrater dans une représentation d'objet très rapidement. Le fichier XAML est compilé dans un fichier BAML et compressé dans un fichier ".resources", qui est ensuite incorporé dans l'assembly (la dernière version du réflecteur .NET peut vous montrer ces fichiers).

Bien qu'il n'y ait pas de bonne documentation pour le prendre en charge, MSBuild devrait en fait être capable de prendre un fichier XAML, de le convertir en BAML et de l'intégrer pour vous (il le fait pour les formulaires, n'est-ce pas?).

Donc, mon approche serait la suivante: stocker vos données dans XAML (c'est juste une représentation XML d'un graphe d'objet), placer ce XAML dans un fichier de ressources et l'intégrer dans l'assembly. Au moment de l'exécution, récupérez le XAML et utilisez XamlServices pour le réhydrater. Ensuite, encapsulez-le dans un membre statique ou utilisez l'injection de dépendance si vous voulez qu'il soit plus testable, pour le consommer du reste de votre code.

Quelques liens qui sont des références utiles:

En passant, vous pouvez réellement utiliser les fichiers app.config ou web.config pour charger des objets raisonnablement complexes via le mécanisme de paramètres si vous souhaitez que les données soient modifiées plus facilement. Et vous pouvez également charger XAML à partir du système de fichiers.


1

Je suppose que System.Globalization.CultureInfo.GetCulture (nom) appellerait des API Windows pour obtenir les données d'un fichier système.

Pour charger les données dans le code, tout comme @svick l'avait dit, si vous êtes sur le point de charger 200 objets, ce ne sera pas un problème dans la plupart des cas, sauf si votre code s'exécute sur certains périphériques à faible mémoire.

Si vos données ne changent jamais, mon expérience consiste simplement à utiliser une liste / dictionnaire comme:

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

Mais le problème est que vous devez trouver un moyen d'initialiser votre dictionnaire. Je suppose que c'est le point douloureux.

Le problème est donc changé en "Comment générer vos données?". Mais cela dépend de ce dont vous avez besoin.

Si vous souhaitez générer des données pour les pays, vous pouvez utiliser le

System.Globalization.CultureInfo.GetCultures()

obtenez le tableau CultrueInfo et vous pouvez initialiser votre dictionnaire avec vos besoins.

Et bien sûr, vous pouvez mettre le code dans une classe statique et initialiser le dictionnaire dans le constructeur statique comme:

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;
        }
    }
}
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.