Daten im Code speichern

17

Einige Male in meiner Vergangenheit wollte ich Daten in Code speichern. Dies sind Daten, die sich selten ändern und an Orten verwendet werden, an denen der Zugriff auf eine Datenbank nicht möglich, praktisch oder wünschenswert ist. Ein kleines Beispiel wäre das Speichern einer Liste von Ländern. Dafür könnten Sie etwas tun wie:

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

Ich bin in der Vergangenheit damit durchgekommen, weil die Datensätze relativ klein und die Objekte ziemlich einfach waren. Jetzt arbeite ich mit etwas, das komplexere Objekte (5 - 10 Eigenschaften, wobei einige Eigenschaften Wörterbücher sind) und insgesamt etwa 200 Objekte haben wird.

Die Daten selbst ändern sich sehr selten, und wenn sie sich ändern, ist das nicht einmal so wichtig. Es ist also vollkommen in Ordnung, es in die nächste Release-Version zu rollen.

Ich plane, T4 oder ERB oder eine andere Vorlagenlösung zu verwenden, um meine Datenquelle in etwas zu verwandeln, das statisch in der Assembly gespeichert ist.

Es scheint, dass meine Optionen sind

  1. Speichern Sie die Daten in XML. Kompilieren Sie die XML-Datei als Assemblyressource. Laden Sie Daten nach Bedarf, speichern Sie geladene Daten in einem Wörterbuch, um die Leistung bei wiederholter Verwendung zu verbessern.
  2. Generieren Sie eine Art statisches Objekt oder Objekte, die beim Start initialisiert werden.

Ich bin mir ziemlich sicher, dass ich die Auswirkungen von Option 1 auf die Leistung verstehe. Zumindest ist meine Vermutung, dass es keine herausragende Leistung haben würde.

Was Option 2 betrifft, weiß ich nicht, was ich tun soll. Ich weiß nicht genug über die Interna des .NET Frameworks, um zu wissen, wie diese Daten am besten in C # -Code gespeichert und am besten initialisiert werden können. Ich habe mich mit .NET Reflector umgesehen, um zu sehen, wie das System.Globalization.CultureInfo.GetCulture(name)funktioniert, da dies eigentlich ein sehr ähnlicher Workflow ist, wie ich es möchte. Leider endete dieser Trail an einem extern, also keine Hinweise da. Ist das Initialisieren einer statischen Eigenschaft mit allen Daten, wie in meinem Beispiel, der richtige Weg? Oder ist es besser, die Objekte nach Bedarf zu erstellen und sie dann so zwischenzuspeichern?

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

Der Vorteil beim gleichzeitigen Erstellen in einem statischen Member besteht darin, dass Sie LINQ oder was auch immer verwenden können, um alle Objekte zu betrachten und sie abzufragen, wenn Sie möchten. Obwohl ich vermute, dass dies einen Leistungsabfall beim Start zur Folge hat. Ich hoffe, jemand hat Erfahrung damit und kann seine Meinung mitteilen!

mroach
quelle
5
200 Objekte? Ich glaube nicht, dass Sie sich um die Leistung sorgen müssen, besonders wenn es sich um einmalige Kosten handelt.
Svick
1
Sie haben auch die Möglichkeit, die Objekte im Code zu speichern und dann wie gewohnt einen Verweis auf die Through-Dependency-Injection zu übergeben. Auf diese Weise verfügen Sie nicht über statische Referenzen, die von überall direkt auf sie verweisen, wenn Sie ihre Konstruktion ändern möchten.
Amy Blankenship
@svick: Das stimmt. Ich bin wahrscheinlich übermäßig vorsichtig in Bezug auf die Leistung.
Mroach
@AmyBlankenship Ich würde immer Hilfsmethoden verwenden, aber DI ist eine gute Idee. Ich hatte nicht daran gedacht. Ich werde das ausprobieren und sehen, ob mir das Muster gefällt. Vielen Dank!
Mroach
" Die Daten selbst ändern sich sehr selten, und wenn sie sich ändern, ist es wirklich nicht einmal so wichtig. " Sagen Sie das den Bosniern, Serben, Kroaten, Ukrainern, Südsudanern, ehemaligen Südjemeniten, ehemaligen Sowjets und anderen . Sagen Sie das jedem Programmierer, der sich mit dem Übergang zur Euro-Währung befassen musste, oder den Programmierern, die sich möglicherweise mit dem möglichen Austritt Griechenlands aus der Euro-Währung befassen müssen. Daten gehören in Datenstrukturen. XML-Dateien funktionieren gut und können einfach geändert werden. Ebenso SQL-Datenbanken.
Ross Patterson

Antworten:

11

Ich würde mit Option eins gehen. Es ist einfach und leicht zu lesen. Jemand anders, der sich Ihren Code ansieht, wird ihn sofort verstehen. Es ist auch einfacher, Ihre XML-Daten bei Bedarf zu aktualisieren. (Außerdem können Sie sie vom Benutzer aktualisieren lassen, wenn Sie ein nettes Front-End haben und die Dateien separat gespeichert haben.)

Optimieren Sie es nur, wenn Sie es müssen - vorzeitige Optimierung ist böse :)

Rocklan
quelle
3
Wenn Sie C # schreiben, werden Sie auf einer Plattform ausgeführt, die kleinere XML-Dateien, die schnell leuchten, analysieren kann. Es lohnt sich also nicht, sich um Leistungsprobleme zu kümmern.
James Anderson
7

Da es sich im Wesentlichen um Schlüssel-Wert-Paare handelt, empfehle ich, diese Zeichenfolgen zum Zeitpunkt der Kompilierung als in Ihre Assembly eingebettete Ressourcendatei zu speichern . Sie können sie dann mit a auslesen ResourceManger. Ihr Code würde ungefähr so ​​aussehen:

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

Um es ein bisschen effizienter zu machen, können Sie sie alle auf einmal laden, wie folgt:

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

Dies hilft Ihnen sehr, wenn Ihre Anwendung mehrere Sprachen unterstützen muss.

Wenn dies eine WPF-Anwendung ist, ist ein Ressourcenwörterbuch eine viel naheliegendere Lösung.

pswg
quelle
2

Die Entscheidung ist wirklich: Möchten Sie, dass nur der Entwickler (oder jeder mit Zugriff auf ein Build-System) die Daten ändert, wenn Änderungen erforderlich sind, oder sollte ein Endbenutzer oder möglicherweise eine Person in der Organisation des Endbenutzers in der Lage sein, die Daten zu ändern Daten?

Im letzteren Fall würde ich vorschlagen, dass eine Datei in einem vom Benutzer lesbaren und bearbeitbaren Format an einem Ort gespeichert wird, an dem der Benutzer darauf zugreifen kann. Wenn Sie die Daten lesen, müssen Sie natürlich vorsichtig sein. Was auch immer Sie finden, es kann nicht als vertrauenswürdig angesehen werden. Ich würde wahrscheinlich JSON XML vorziehen; Ich finde es einfacher zu verstehen und richtig zu machen. Sie können überprüfen, ob ein Editor für das Datenformat verfügbar ist. Wenn Sie beispielsweise unter MacOS X eine plist verwenden, hat jeder Benutzer, der sich jemals den Daten nähern sollte, einen anständigen Editor auf seinem Computer.

Im ersten Fall, wenn die Daten Teil Ihres Builds sind, macht es keinen wirklichen Unterschied. Tun Sie, was für Sie am bequemsten ist. In C ++ ist das Schreiben der Daten mit minimalem Overhead eine der wenigen Stellen, an denen Makros zulässig sind. Wenn die Pflege der Daten von Dritten durchgeführt wird, können Sie ihnen die Quelldateien senden, sie bearbeiten lassen und sind dann dafür verantwortlich, sie zu überprüfen und in Ihrem Projekt zu verwenden.

gnasher729
quelle
1

Das beste Beispiel dafür ist wahrscheinlich WPF ... Ihre Formulare sind in XAML geschrieben, das im Grunde XML ist, mit der Ausnahme, dass .NET es sehr schnell wieder in eine Objektdarstellung umwandeln kann. Die XAML-Datei wird in eine BAML-Datei kompiliert und in eine ".resources" -Datei komprimiert, die dann in die Assembly eingebettet wird (die neueste Version von .NET Reflector kann Ihnen diese Dateien anzeigen).

Obwohl es keine großartige Dokumentation gibt, die dies unterstützt, sollte MSBuild tatsächlich in der Lage sein, eine XAML-Datei zu nehmen, in BAML zu konvertieren und für Sie einzubetten (funktioniert dies für Formulare, oder?).

Mein Ansatz wäre also: Speichern Sie Ihre Daten in XAML (es ist nur eine XML-Darstellung eines Objektdiagramms), fügen Sie diese XAML in eine Ressourcendatei ein und binden Sie sie in die Assembly ein. Greifen Sie zur Laufzeit nach der XAML und verwenden Sie XamlServices, um sie zu rehydrieren. Schließen Sie es dann entweder in ein statisches Element ein, oder verwenden Sie die Abhängigkeitsinjektion, wenn Sie möchten, dass es testbarer ist, um es vom Rest Ihres Codes zu verbrauchen.

Einige nützliche Links:

Nebenbei bemerkt, Sie können die Dateien app.config oder web.config verwenden, um relativ komplexe Objekte über den Einstellungsmechanismus zu laden, wenn die Daten einfacher geändert werden sollen. Außerdem können Sie XAML aus dem Dateisystem laden.

jungsherman
quelle
1

Ich vermute, dass System.Globalization.CultureInfo.GetCulture (Name) Windows-APIs aufruft, um die Daten aus einer Systemdatei abzurufen.

Wie @svick bereits sagte, ist das Laden der Daten im Code in den meisten Fällen kein Problem, wenn Sie 200 Objekte laden möchten, es sei denn, Ihr Code läuft auf Geräten mit geringem Arbeitsspeicher.

Wenn sich Ihre Daten nie ändern, benutze ich nach meiner Erfahrung einfach eine Liste / ein Wörterbuch wie:

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

Das Problem ist jedoch, dass Sie einen Weg finden müssen, um Ihr Wörterbuch zu initialisieren. Ich denke, das ist der Schmerzpunkt.

Das Problem wird also in "So generieren Sie Ihre Daten" geändert. Aber das bezieht sich auf das, was Sie brauchen.

Wenn Sie Daten für die Länder generieren möchten, können Sie das verwenden

System.Globalization.CultureInfo.GetCultures()

Holen Sie sich das CultrueInfo-Array, und Sie können Ihr Wörterbuch mit Ihren Anforderungen initialisieren.

Und natürlich können Sie den Code in eine statische Klasse einfügen und das Wörterbuch im statischen Konstruktor wie folgt initialisieren:

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;
        }
    }
}
benutze deine Illusion
quelle