Almacenar datos en código

17

Algunas veces en mi pasado he querido almacenar datos en código. Estos serían datos que rara vez cambian y se utilizan en lugares donde el acceso a una base de datos no es posible, práctico o deseable. Un pequeño ejemplo sería almacenar una lista de países. Para eso podrías hacer algo como:

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

Me salí con la suya en el pasado porque los conjuntos de datos eran relativamente pequeños y los objetos bastante simples. Ahora estoy trabajando con algo que tendrá objetos más complejos (5 - 10 propiedades cada uno, algunas propiedades son diccionarios) y alrededor de 200 objetos en total.

Los datos en sí mismos cambian muy raramente y cuando cambian, en realidad ni siquiera es tan importante. Así que pasarlo a la próxima versión de lanzamiento está perfectamente bien.

Estoy planeando usar T4 o ERB o alguna otra solución de plantillas para convertir mi fuente de datos en algo que esté almacenado estáticamente en el ensamblaje.

Parece que mis opciones son

  1. Almacene los datos en XML. Compile el archivo XML como recurso de ensamblaje. Cargue los datos según sea necesario, almacene los datos cargados en un diccionario para el rendimiento de uso repetido.
  2. Genere algún tipo de objeto estático u objetos que se inicializan al inicio.

Estoy bastante seguro de que entiendo las implicaciones de rendimiento de la opción 1. Al menos, mi presentimiento es que no tendría un rendimiento estelar.

En cuanto a la opción 2, no sé qué hacer. No sé lo suficiente sobre los componentes internos de .NET Framework para conocer la mejor manera de almacenar realmente estos datos en código C # y las mejores formas de inicializarlos. Busqué usando .NET reflector para ver cómo System.Globalization.CultureInfo.GetCulture(name)funciona, ya que este es realmente un flujo de trabajo muy similar al que quiero. Desafortunadamente, ese rastro terminó en un extern, así que no hay pistas allí. ¿Inicializar una propiedad estática con todos los datos, como en mi ejemplo, es el camino a seguir? ¿O sería mejor crear los objetos a pedido y luego almacenarlos en caché, de esta manera?

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

La ventaja de crearlos a la vez en un miembro estático es que puede usar LINQ o cualquier otra cosa para ver todos los objetos y consultarlos si lo desea. Aunque sospecho que hacer esto tiene una penalización de rendimiento de inicio. ¡Espero que alguien tenga experiencia con esto y pueda compartir sus opiniones!

mroach
fuente
55
200 objetos? No creo que tenga que preocuparse por el rendimiento, especialmente si es un costo único.
svick
1
Tiene otra opción, que es almacenar los objetos en código, luego pasar una referencia a la inyección de dependencia a través de forma normal. De esa manera, si desea cambiar la forma en que se construyen, no tiene referencias estáticas que apunten directamente a ellos desde todas partes.
Amy Blankenship
@svick: Eso es cierto. Probablemente estoy siendo demasiado cauteloso sobre el rendimiento.
mroach
@AmyBlankenship Siempre estaría usando métodos de ayuda, pero DI es una buena idea. No lo había pensado. Voy a intentarlo y ver si me gusta el patrón. ¡Gracias!
mroach
" Los datos en sí mismos cambian muy raramente y, cuando cambian, en realidad ni siquiera es tan importante " . Dígaselo a los bosnios, serbios, croatas, ucranianos, sudáníes del sur, ex yemeníes del sur, ex soviéticos, etc. Dígale eso a cada programador que tuvo que lidiar con la transición a la moneda del Euro, o a los programadores que pueden tener que lidiar con la salida potencial de Grecia de ella. Los datos pertenecen a estructuras de datos. Los archivos XML funcionan bien y se pueden cambiar fácilmente. Lo mismo ocurre con las bases de datos SQL.
Ross Patterson

Respuestas:

11

Yo iría con la opción uno. Es simple y fácil de leer. Alguien más que vea su código lo entenderá de inmediato. También será más fácil actualizar sus datos XML si es necesario. (Además, puede permitir que el usuario las actualice si tiene un buen front-end y almacena los archivos por separado)

Optimícelo solo si lo necesita: la optimización prematura es mala :)

Rocklan
fuente
3
Si está escribiendo C #, entonces se está ejecutando en una plataforma que puede analizar rápidamente los XML pequeños. Por lo tanto, no vale la pena preocuparse por los problemas de rendimiento.
James Anderson el
7

Dado que estos son esencialmente pares clave-valor, recomiendo almacenar estas cadenas como un archivo de recursos incrustado en su ensamblaje en tiempo de compilación. Luego puede leerlos usando a ResourceManger. Su código se vería así:

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

Para hacerlo un poco más eficiente, puede cargarlos todos a la vez, así:

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

Esto lo ayudará mucho si alguna vez necesita hacer que su aplicación sea compatible con varios idiomas.

Si se trata de una aplicación WPF, un diccionario de recursos es una solución mucho más obvia.

pswg
fuente
2

La decisión es realmente: ¿Desea que solo el desarrollador (o cualquier persona con acceso a un sistema de compilación) modifique los datos cuando sea necesario modificarlos, o si un usuario final o posiblemente alguien de la organización del usuario final pueda modificar los datos? ¿datos?

En el último caso, sugeriría que un archivo en formato legible y editable por el usuario se almacene en un lugar donde el usuario pueda acceder a él. Obviamente cuando lees los datos debes tener cuidado; no se puede confiar en que lo que encuentre sea información válida. Probablemente prefiera JSON a XML; Me resulta más fácil de entender y acertar. Puede verificar si hay un editor disponible para el formato de datos; por ejemplo, si usa una lista en MacOS X, cada usuario que alguna vez deba acercarse a los datos tendrá un editor decente en su computadora.

En el primer caso, si los datos son parte de su compilación, realmente no hace la diferencia. Haz lo que sea más conveniente para ti. En C ++, escribir los datos con una sobrecarga mínima es uno de los pocos lugares donde se permiten macros. Si el trabajo de mantenimiento de los datos es realizado por un tercero, puede enviarles los archivos de origen, dejar que los editen, y luego es responsable de verificarlos y usarlos en su proyecto.

gnasher729
fuente
1

El mejor ejemplo de esto que existe es probablemente WPF ... Sus formularios están escritos en XAML, que es básicamente XML, excepto que .NET puede rehidratarlo en una representación de objeto muy rápidamente. El archivo XAML se compila en un archivo BAML y se comprime en el archivo ".resources", que luego se incrusta en el ensamblado (la última versión de .NET reflector puede mostrarle estos archivos).

Aunque no hay ninguna documentación excelente que lo respalde, MSBuild debería de hecho poder tomar un archivo XAML, convertirlo a BAML e incrustarlo por usted (¿lo hace para los formularios, verdad?).

Entonces, mi enfoque sería: almacenar sus datos en XAML (es solo una representación XML de un gráfico de objeto), introducir ese XAML en un archivo de recursos e incrustarlo en el ensamblado. En tiempo de ejecución, tome el XAML y use XamlServices para rehidratarlo. Luego, envuélvalo en un miembro estático o use la Inyección de dependencia si desea que sea más comprobable, para consumirlo del resto de su código.

Algunos enlaces que son material de referencia útil:

Como nota al margen, en realidad puede usar los archivos app.config o web.config para cargar objetos razonablemente complejos a través del mecanismo de configuración si desea que los datos se cambien más fácilmente. Y también puede cargar XAML desde el sistema de archivos.

hombre guapo
fuente
1

Supongo que System.Globalization.CultureInfo.GetCulture (nombre) llamaría a las API de Windows para obtener los datos de un archivo del sistema.

Para cargar los datos en el código, como había dicho @svick, si está a punto de cargar 200 objetos, no será un problema en la mayoría de los casos, a menos que su código se ejecute en algunos dispositivos con poca memoria.

Si sus datos nunca cambian, mi experiencia es simplemente usar una lista / diccionario como:

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

Pero el problema es que necesita encontrar una manera de inicializar su diccionario. Supongo que este es el punto de dolor.

Entonces, el problema se cambia a "¿Cómo generar sus datos?". Pero esto se relaciona con lo que necesitas.

Si desea generar datos para los países, puede usar el

System.Globalization.CultureInfo.GetCultures()

obtenga la matriz CultrueInfo y podrá inicializar su diccionario con sus necesidades.

Y, por supuesto, podría poner el código en una clase estática e inicializar el diccionario en el constructor estático como:

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;
        }
    }
}
usa tu ilusión
fuente