Armazenando dados no código


17

Algumas vezes no passado, eu queria armazenar dados em código. Seriam dados que raramente mudam e são usados ​​em locais onde o acesso a um banco de dados não é possível, prático ou desejável. Um pequeno exemplo seria armazenar uma lista de países. Para isso, você pode fazer 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);
    }
}

Eu já fiz isso no passado porque os conjuntos de dados eram relativamente pequenos e os objetos bastante simples. Agora estou trabalhando com algo que terá objetos mais complexos (5 a 10 propriedades cada, algumas propriedades sendo dicionários) e cerca de 200 objetos no total.

Os dados em si mudam muito raramente e, quando mudam, não são tão importantes assim. Portanto, rolá-lo para a próxima versão está perfeitamente bem.

Estou planejando usar o T4 ou o ERB ou alguma outra solução de modelo para transformar minha fonte de dados em algo armazenado estaticamente no assembly.

Parece que minhas opções são

  1. Armazene os dados em XML. Compile o arquivo XML como um recurso de montagem. Carregue os dados conforme necessário, armazene os dados carregados em um dicionário para desempenho de uso repetido.
  2. Gere algum tipo de objeto estático ou objetos que são inicializados na inicialização.

Tenho certeza de que entendi as implicações de desempenho da opção 1. Pelo menos, meu palpite é que não teria desempenho estelar.

Quanto à opção 2, não sei o que fazer. Não sei o suficiente sobre as partes internas da estrutura .NET para saber a melhor maneira de realmente armazenar esses dados no código C # e as melhores maneiras de inicializá-los. Eu usei o refletor .NET para ver como System.Globalization.CultureInfo.GetCulture(name)funciona, pois esse é realmente um fluxo de trabalho muito semelhante ao que eu quero. Infelizmente essa trilha terminou em um ponto extern, então não há dicas. A inicialização de uma propriedade estática com todos os dados, como no meu exemplo, é o caminho a percorrer? Ou seria melhor criar os objetos sob demanda e depois armazená-los em cache, assim?

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

A vantagem de criá-los de uma só vez em um membro estático é que você pode usar o LINQ ou qualquer outra coisa para examinar todos os objetos e consultá-los, se desejar. Embora eu suspeito que fazer isso tenha uma penalidade no desempenho da inicialização. Espero que alguém tenha experiência com isso e possa compartilhar suas opiniões!


5
200 objetos? Não acho que você precise se preocupar com desempenho, especialmente se for um custo único.
svick

1
Você tem outra opção, que é armazenar os objetos no código e, em seguida, passar uma referência à injeção de dependência passada normalmente. Dessa forma, se você quiser alterar a forma como eles são construídos, não terá referências estáticas apontando diretamente para eles de qualquer lugar.
Amy Blankenship

@ Rick: Isso é verdade. Provavelmente estou sendo muito cauteloso em relação ao desempenho.
mroach

@AmyBlankenship Eu sempre usaria métodos auxiliares, mas DI é uma boa idéia. Eu não tinha pensado nisso. Vou tentar e ver se gosto do padrão. Obrigado!
mroach

Os dados em si mudam muito raramente e, quando mudam, não são tão importantes assim. ” Diga isso aos bósnios, sérvios, croatas, ucranianos, sul do Sudão, ex-iemenitas do sul, ex-soviéticos, etc. Diga isso a todos os programadores que tiveram que lidar com a transição para a moeda euro ou aos programadores que possam ter que lidar com a saída potencial da Grécia dela. Os dados pertencem às estruturas de dados. Os arquivos XML funcionam bem e podem ser facilmente alterados. Idem bancos de dados SQL.
Ross Patterson

Respostas:


11

Eu iria com a opção um. É simples e fácil de ler. Alguém mais olhando o seu código entenderá imediatamente. Também será mais fácil atualizar seus dados XML, se necessário. (Além disso, você pode deixar o usuário atualizá-los se você tiver um bom front end e armazenar os arquivos separadamente)

Otimize-o apenas se for necessário - a otimização prematura é má :)


3
Se você estiver escrevendo C #, estará executando em uma plataforma que pode analisar XMLs pequenos e com iluminação rápida. Portanto, não vale a pena se preocupar com problemas de desempenho.
James Anderson

7

Dado que esses são essencialmente pares de valores-chave, recomendo armazenar essas seqüências de caracteres como um arquivo de recurso incorporado ao seu assembly em tempo de compilação. Você pode lê-los usando a ResourceManger. Seu código seria algo parecido com isto:

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 torná-lo um pouco mais eficiente, você pode carregá-los todos de uma vez, assim:

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

Isso o ajudará muito se você precisar fazer com que seu aplicativo suporte vários idiomas.

Se isso for um aplicativo WPF, um dicionário de recursos é uma solução muito mais óbvia.


2

A decisão é realmente: você deseja que apenas o desenvolvedor (ou qualquer pessoa com acesso a um sistema de construção) modifique os dados quando precisar modificar, ou um usuário final ou possivelmente alguém na organização do usuário final possa modificar o dados?

No último caso, sugiro que um arquivo em formato legível e editável pelo usuário seja armazenado em um local onde o usuário possa acessá-lo. Obviamente, quando você lê os dados, precisa ter cuidado; o que você encontrar não pode ser confiável para ser dados válidos. Eu provavelmente preferiria JSON a XML; Acho mais fácil entender e acertar. Você pode verificar se há um editor disponível para o formato de dados; por exemplo, se você usar um plist no MacOS X, todos os usuários que chegarem perto dos dados terão um editor decente em seu computador.

No primeiro caso, se os dados fizerem parte da sua compilação, realmente não fará diferença. Faça o que for mais conveniente para você. No C ++, gravar os dados com uma sobrecarga mínima é um dos poucos locais onde as macros são permitidas. Se o trabalho de manutenção dos dados for realizado por terceiros, você poderá enviar a eles os arquivos de origem, deixá-los editá-los, e você será responsável por verificá-los e usá-los em seu projeto.


1

O melhor exemplo disso que existe é provavelmente o WPF ... Seus formulários são gravados em XAML, que é basicamente XML, exceto que o .NET pode reidratá-lo em uma representação de objeto muito rapidamente. O arquivo XAML é compilado em um arquivo BAML e compactado no arquivo ".resources", que é incorporado ao assembly (a versão mais recente do refletor .NET pode mostrar esses arquivos).

Embora não exista uma boa documentação para suportá-lo, o MSBuild deve, de fato, ser capaz de pegar um arquivo XAML, convertê-lo em BAML e incorporá-lo para você (o faz para formulários, certo?).

Portanto, minha abordagem seria: armazene seus dados em XAML (é apenas uma representação XML de um gráfico de objeto), coloque esse XAML em um arquivo de recursos e incorpore-o no assembly. Em tempo de execução, pegue o XAML e use XamlServices para reidratá-lo. Em seguida, envolva-o em um membro estático ou use a Injeção de Dependência, se desejar que seja mais testável, para consumi-lo do restante do seu código.

Alguns links que são úteis material de referência:

Além disso, é possível usar os arquivos app.config ou web.config para carregar objetos razoavelmente complexos por meio do mecanismo de configurações, se você desejar que os dados sejam alterados com mais facilidade. E você também pode carregar o XAML no sistema de arquivos.


1

Eu acho que o System.Globalization.CultureInfo.GetCulture (name) chamaria APIs do Windows para obter os dados de um arquivo do sistema.

Para carregar os dados no código, exatamente como o @svick disse, se você estiver prestes a carregar 200 objetos, na maioria dos casos não será um problema, a menos que seu código seja executado em alguns dispositivos com pouca memória.

Se seus dados nunca mudam, minha experiência é simplesmente usar uma lista / dicionário como:

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

Mas o problema é que você precisa encontrar uma maneira de inicializar seu dicionário. Eu acho que esse é o ponto de dor.

Portanto, o problema foi alterado para "Como gerar seus dados?". Mas isso está relacionado ao que você precisa.

Se você deseja gerar dados para os países, você pode usar o

System.Globalization.CultureInfo.GetCultures()

obtenha a matriz CultrueInfo e você poderá inicializar seu dicionário com suas necessidades.

E é claro que você pode colocar o código em uma classe estática e inicializar o dicionário no construtor 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;
        }
    }
}
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.