用代码存储数据


17

我过去几次想将数据存储在代码中。这将是很少更改的数据,并用于不可能,不实际或不希望访问数据库的地方。一个小例子是存储国家列表。为此,您可以执行以下操作:

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

过去我没有这样做,因为数据集相对较小并且对象非常简单。现在,我正在处理的对象将具有更复杂的对象(每个对象5-10个属性,某些属性是字典),总共约200个对象。

数据本身很少更改,并且当确实更改时,它甚至不那么重要。因此,将其滚动到下一个发行版本中就很好。

我打算使用T4或ERB或其他模板解决方案将数据源转换为静态存储在程序集中的数据。

看来我的选择是

  1. 将数据存储为XML。将XML文件编译为程序集资源。根据需要加载数据,将加载的数据存储到Dictionary中以提高重复使用性能。
  2. 生成某种静态对象或在启动时初始化的对象。

我非常确定我了解选项1的性能含义。至少,我的直觉是它不会具有出色的性能。

至于选项2,我不知道该怎么办。我对.NET框架的内部了解不多,无法知道将数据实际存储在C#代码中的最佳方法以及对其进行初始化的最佳方法。我四处寻找使用.NET反射器的方法System.Globalization.CultureInfo.GetCulture(name),因为这实际上与我想要的工作流非常相似。不幸的是,那条足迹以结束extern,因此没有任何提示。是否像我的示例一样,用所有数据初始化静态属性?还是按需创建对象然后缓存它们,这样会更好?

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

在静态成员中一次创建它们的好处是,您可以使用LINQ或其他任何方式查看所有对象并根据需要查询它们。尽管我怀疑这样做会降低启动性能。我希望有人对此有经验并可以分享他们的意见!


5
200个物件?我认为您不必担心性能,特别是如果这是一次性成本。
svick

1
您还有另一个选择,将对象存储在代码中,然后像平常一样将引用传递给贯穿依赖注入。这样,如果您想更改它们的构造方式,则没有从任何地方直接指向它们的静态引用。
艾米·布兰肯希

@svick:是的。我可能对性能过于谨慎。
mroach

@AmyBlankenship我一直都会使用辅助方法,但是DI是个好主意。我没想到 我会去看看我是否喜欢这种模式。谢谢!
mroach

数据本身很少发生变化,当确实发生变化时,它实际上甚没有那么重要。 ”告诉波斯尼亚人,塞族人,克罗地亚人,乌克兰人,南苏丹人,前南也门人,前苏联人等。 告诉所有必须处理向欧元过渡的程序员,或者告诉可能要应对希腊可能退出欧元区的程序员。 数据属于数据结构。XML文件可以很好地工作,并且可以轻松更改。同上SQL数据库。
罗斯·帕特森

Answers:


11

我会选择第一种。简单易读。看着您的代码的其他人将立即理解它。如果需要,更新XML数据也将更加容易。(另外,如果您的前端很好,并且可以单独存储文件,则可以让用户更新它们)

仅在需要时对其进行优化-过早的优化是有害的:)


3
如果您正在编写C#,那么您正在运行一个可以快速解析较小的XML的平台。因此,它不值得困扰性能问题。
James Anderson

7

鉴于它们本质上是键-值对,我建议在编译时将这些字符串存储为嵌入在程序集中的资源文件。然后,您可以使用读出它们ResourceManger。您的代码如下所示:

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

为了提高效率,您可以一次加载它们,如下所示:

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

如果您需要使应用程序支持多种语言,这将对您有很大帮助。

如果碰巧是WPF应用程序,则资源字典是一个更明显的解决方案。


2

决定确实是:您是否只希望开发人员(或有权访问构建系统的任何人)在需要修改数据时对其进行修改,还是让最终用户或最终用户组织中的某人能够修改数据?数据?

在后一种情况下,我建议将用户可读和用户可编辑格式的文件存储在用户可以访问的位置。显然,当您读取数据时,需要小心。您发现的任何内容都不能被视为有效数据。我可能更喜欢JSON而不是XML。我发现它更容易理解和正确。您可能会检查是否有适用于该数据格式的编辑器;例如,如果您在MacOS X上使用plist,那么每个应该访问数据的用户都将在计算机上拥有不错的编辑器。

在第一种情况下,如果数据是构建的一部分,则实际上并没有什么不同。做对您来说最方便的事情。在C ++中,以最小的开销编写数据是允许使用宏的少数几个地方之一。如果维护数据的工作是由第三方完成的,则可以将源文件发送给他们,让他们编辑它们,然后负责检查它们并在项目中使用它们。


1

存在的最好的例子可能是WPF ...您的表单是用XAML编写的,它基本上是XML,除了.NET能够很快将其重新水化为对象表示形式。XAML文件被编译为BAML文件,并被压缩为“ .resources”文件,然后将其嵌入到程序集中(最新版本的.NET反射器可以向您显示这些文件)。

尽管没有很好的文档来支持它,但MSBuild实际上应该能够获取XAML文件,将其转换为BAML并为您嵌入(它对表单有用吗?)。

因此,我的方法是:将数据存储在XAML中(这只是对象图的XML表示形式),将XAML填充到资源文件中,并将其嵌入到程序集中。在运行时,获取XAML,然后使用XamlServices对其进行补水。然后,要么将其包装在一个静态成员中,要么使用Dependency Injection(如果您希望它更具可测试性)从其余的代码中使用它。

一些有用的参考资料链接:

附带说明一下,如果您想更轻松地更改数据,则实际上可以使用app.config或web.config文件通过设置机制加载相当复杂的对象。您还可以从文件系统加载XAML。


1

我想System.Globalization.CultureInfo.GetCulture(name)会调用Windows API从系统文件中获取数据。

就像@svick所说的那样,要在代码中加载数据,如果您要加载200个对象,则在大多数情况下这不是问题,除非您的代码在某些低内存设备上运行。

如果您的数据永远不变,那么我的经验就是使用列表/字典,例如:

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

但是问题在于您需要找到一种初始化字典的方法。我想这就是痛点。

因此,问题变为“如何生成数据?”。 但这与您所需要的有关。

如果您想为这些国家/地区生成数据,则可以使用

System.Globalization.CultureInfo.GetCultures()

获取CultrueInfo数组,您可以根据需要初始化字典。

当然,您可以将代码放在静态类中,并在静态构造函数中初始化字典,例如:

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;
        }
    }
}
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.