Memorizzazione dei dati nel codice


17

Alcune volte in passato ho voluto archiviare i dati nel codice. Si tratta di dati che raramente cambiano e vengono utilizzati in luoghi in cui l'accesso a un database non è possibile, pratico o desiderabile. Un piccolo esempio potrebbe essere la memorizzazione di un elenco di paesi. Per questo potresti fare qualcosa del tipo:

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

L'ho fatto in passato perché i set di dati erano relativamente piccoli e gli oggetti piuttosto semplici. Ora sto lavorando con qualcosa che avrà oggetti più complessi (5-10 proprietà ciascuno, alcune proprietà sono dizionari) e circa 200 oggetti in totale.

I dati stessi cambiano molto raramente e quando cambiano non è nemmeno così importante. Quindi inserirlo nella prossima versione di rilascio va benissimo.

Sto pianificando di utilizzare T4 o ERB o qualche altra soluzione di template per trasformare la mia fonte di dati in qualcosa che viene archiviato staticamente nell'assembly.

Sembra che le mie opzioni lo siano

  1. Archivia i dati in XML. Compilare il file XML come risorsa di assembly. Caricare i dati secondo necessità, archiviare i dati caricati in un dizionario per prestazioni di uso ripetuto.
  2. Genera qualche tipo di oggetto statico o oggetti inizializzati all'avvio.

Sono abbastanza sicuro di comprendere le implicazioni delle prestazioni dell'opzione 1. Almeno, il mio sospetto è che non avrebbe prestazioni stellari.

Per quanto riguarda l'opzione 2, non so cosa fare. Non conosco abbastanza gli interni del framework .NET per conoscere il modo migliore per archiviare effettivamente questi dati nel codice C # e i modi migliori per inizializzarli. Ho cercato con .NET reflector per vedere come System.Globalization.CultureInfo.GetCulture(name)funziona, dato che in realtà si tratta di un flusso di lavoro molto simile a quello che voglio. Sfortunatamente quella pista finì ad un extern, quindi nessun suggerimento lì. L'inizializzazione di una proprietà statica con tutti i dati, come nel mio esempio, è la strada da percorrere? O sarebbe meglio creare gli oggetti su richiesta e poi memorizzarli nella cache, in questo modo?

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

Il vantaggio di crearli contemporaneamente in un membro statico è che puoi usare LINQ o qualsiasi altra cosa per guardare tutti gli oggetti e interrogarli se vuoi. Anche se sospetto che ciò comporti una penalità per le prestazioni all'avvio. Spero che qualcuno abbia esperienza con questo e possa condividere le loro opinioni!


5
200 oggetti? Non penso che ti debba preoccupare delle prestazioni, soprattutto se si tratta di un costo una tantum.
svick,

1
Hai un'altra opzione, che è quella di memorizzare gli oggetti nel codice, quindi passare normalmente un riferimento all'iniezione di dipendenza passante. In questo modo, se si desidera modificare il modo in cui sono costruiti, non si hanno riferimenti statici che puntano direttamente su di essi da ogni parte.
Amy Blankenship,

@svick: è vero. Probabilmente sono troppo cauto riguardo alle prestazioni.
mroach il

@AmyBlankenship Userei sempre i metodi di supporto, ma DI è una buona idea. Non ci avevo pensato. Ci proverò e vedrò se mi piace lo schema. Grazie!
mroach

" I dati stessi cambiano molto raramente e quando cambiano non è nemmeno così importante. " Raccontalo a bosniaci, serbi, croati, ucraini, sud sudanesi, ex yemeniti del sud, ex sovietici, ecc. Dillo a tutti i programmatori che hanno dovuto affrontare il passaggio alla valuta euro o ai programmatori che potrebbero dover affrontare la potenziale uscita della Grecia da essa. I dati appartengono a strutture di dati. I file XML funzionano bene e possono essere facilmente modificati. Idem database SQL.
Ross Patterson

Risposte:


11

Vorrei andare con l'opzione uno. È semplice e facile da leggere. Qualcun altro guardando il tuo codice lo capirà immediatamente. Sarà anche più semplice aggiornare i tuoi dati XML se necessario. (Inoltre potresti consentire all'utente di aggiornarli se hai un bel front-end e memorizza i file separatamente)

Ottimizzalo solo se necessario - l'ottimizzazione prematura è cattiva :)


3
Se stai scrivendo C #, stai eseguendo una piattaforma in grado di analizzare rapidamente l'illuminazione XML di piccole dimensioni. Quindi non vale la pena preoccuparsi di problemi di prestazioni.
James Anderson,

7

Dato che si tratta essenzialmente di coppie chiave-valore, consiglio di archiviare queste stringhe come file di risorse incorporato nell'assembly al momento della compilazione. Puoi quindi leggerli usando a ResourceManger. Il tuo codice sarebbe simile a questo:

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

Per renderlo un po 'più efficiente puoi caricarli tutti in una volta, in questo modo:

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

Questo ti aiuterà molto se dovessi mai fare in modo che la tua applicazione supporti più lingue.

Se si tratta di un'applicazione WPF, un dizionario delle risorse è una soluzione molto più ovvia.


2

La decisione è davvero: vuoi che solo lo sviluppatore (o chiunque abbia accesso a un sistema di compilazione) modifichi i dati quando è necessario modificarli, o se un utente finale o possibilmente qualcuno nell'organizzazione dell'utente finale fosse in grado di modificare il dati?

In quest'ultimo caso, suggerirei che un file in formato leggibile e modificabile dall'utente venga archiviato in un luogo in cui l'utente può accedervi. Ovviamente quando leggi i dati devi stare attento; qualsiasi cosa trovi non può essere considerata affidabile per essere dati validi. Preferirei probabilmente JSON a XML; Trovo più facile da capire e fare bene. È possibile verificare se è disponibile un editor per il formato dei dati; ad esempio se usi un plist su MacOS X, ogni utente che dovrebbe mai avvicinarsi ai dati avrà un editor decente sul suo computer.

Nel primo caso, se i dati fanno parte della tua build, non fa davvero la differenza. Fai ciò che è più conveniente per te. In C ++, scrivere i dati con un sovraccarico minimo è uno dei pochi posti in cui sono consentite le macro. Se il lavoro di gestione dei dati è svolto da una terza parte, è possibile inviare loro i file di origine, lasciarli modificarli e si è quindi responsabili del controllo e dell'utilizzo nel progetto.


1

Il miglior esempio di ciò che esiste è probabilmente WPF ... I tuoi moduli sono scritti in XAML, che è fondamentalmente XML, tranne per il fatto che .NET è in grado di reidratarlo in una rappresentazione di oggetti molto rapidamente. Il file XAML viene compilato in un file BAML e compresso nel file ".resources", che viene quindi incorporato nell'assembly (l'ultima versione di .NET Reflector può mostrarti questi file).

Sebbene non ci sia una grande documentazione per supportarlo, MSBuild dovrebbe in effetti essere in grado di prendere un file XAML, convertirlo in BAML e incorporarlo per te (lo fa per i moduli, giusto?).

Quindi, il mio approccio sarebbe: archiviare i dati in XAML (è solo una rappresentazione XML di un grafico a oggetti), inserire tale XAML in un file di risorse e incorporarlo nell'assembly. In fase di esecuzione, prendi XAML e usa XamlServices per reidratarlo. Quindi avvolgilo in un membro statico oppure usa Dependency Injection se vuoi che sia più testabile, per consumarlo dal resto del tuo codice.

Alcuni link che sono utili materiale di riferimento:

In una nota a margine, puoi effettivamente utilizzare i file app.config o web.config per caricare oggetti ragionevolmente complessi attraverso il meccanismo delle impostazioni se desideri che i dati vengano modificati più facilmente. E puoi anche caricare XAML dal file system.


1

Immagino che System.Globalization.CultureInfo.GetCulture (nome) chiamerebbe le API di Windows per ottenere i dati da un file di sistema.

Per caricare i dati nel codice, proprio come aveva detto @svick, se stai per caricare 200 oggetti, nella maggior parte dei casi non sarà un problema, a meno che il tuo codice non sia in esecuzione su alcuni dispositivi con poca memoria.

Se i tuoi dati non cambiano mai, la mia esperienza è semplicemente quella di utilizzare un elenco / dizionario come:

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

Ma il problema è che devi trovare un modo per inizializzare il tuo dizionario. Immagino che questo sia il punto dolente.

Quindi il problema è cambiato in "Come generare i tuoi dati?". Ma questo si riferisce a ciò di cui hai bisogno.

Se si desidera generare dati per i paesi, è possibile utilizzare il

System.Globalization.CultureInfo.GetCultures()

ottieni l'array CultrueInfo e puoi inizializzare il dizionario con le tue esigenze.

E ovviamente potresti mettere il codice in una classe statica e inizializzare il dizionario nel costruttore statico come:

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;
        }
    }
}
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.