在C#中实现全局常量的最佳方法是什么?

11

我有一个Common项目,其中添加了我的QueryStringNames的公共常量。

通常常量应该是内部或私有的,但在这里我需要公共常量,因为我想允许全局访问查询字符串名称、会话密钥等。

我知道有3种解决方案,但它们都有一个重要问题。调用者程序集将包含我的常量副本,这意味着如果我必须更改常量值,我将不得不编译我的Common程序集和调用者程序集!

1) public const string ConstName = "a value";
2) public readonly string ConstName = "a value";
3) To be stored in a public resource file.

除了将公共常量存储在没有 IntelliSense 的 web.config 文件中,还有什么最好的方法来定义 C# 中的公共常量?


1
如果您创建了适当的XSD模式,就可以在配置中使用智能感知。 - Felice Pollano
我有点困惑。值的存储位置(即webconfig)与内存中该值的副本的可访问性有什么关系? - asawyer
你说得对,@asawyer;如果再次使用const和readonly,那么这并不能解决问题。 - The Light
@asawyer - 我认为OP的观点是,如果它在配置文件中,他就不必重新编译所有程序集? - james lewis
你不能在没有类实例的情况下使用该常量,如果你是这个意思的话。我为此使用了一个静态 co 类,所以我使用 co.PI,这样只比在代码中使用 PI 多了3个字符。 - Stefanos Zilellis
微软有一个答案:https://learn.microsoft.com/zh-cn/dotnet/csharp/programming-guide/classes-and-structs/how-to-define-constants - james.garriss
7个回答

7
这取决于情况。如果它是真正不会改变的常量,即使在将来的代码版本中也不会改变,那么使用const就可以了。否则,请使用static readonly字段。 const将被嵌入到调用程序集中,而static readonly仅包含对该字段的引用。这意味着当更改值时,const需要重新编译所有相关代码,而public readonly即使没有重新编译调用程序集也可以使用新值。
如果您想将“常量”存储在配置文件中,并希望使用Intellisense,则可以使用一个没有公共setter的属性。然后在运行时从配置文件中填充它。但我认为配置值首先不应该是static。对于配置值,我会使用某种单例模式,最好是IoC变体,而不是Class.Instance变体。所以我只需定义如下的接口:
interface IMyConfig
{
  string Key{get;}
}

并且需要让需要此配置的类将其作为构造函数参数:

public MyClass(IMyConfig config)
{
    ...
}

为什么?!我测试了一下,static readonly 也需要重新编译调用者。 - The Light
1
我非常确定更改静态只读字段不需要重新编译调用者。调用程序集仅包含您字段的名称,而不是其值。 - CodesInChaos
@CodeInChaos,显然是这样的;你可以自己测试一下看看!或者可能是我没有测试好。 - The Light
2
或许,William,你被 Visual Studio 默认重新构建所有依赖程序集所弄糊涂了。但是,如果你拿着新的包含“static readonly”的程序集,并将该单个 DLL 文件替换为不同的版本,则所有基于它构建的程序集仍应正常工作并使用新值。 - CodesInChaos
@CodeinChaos,我已经手动复制了程序集;否则VS会重新构建一切。 - The Light
显示剩余2条评论

6
如果您想要修改配置并且担心需要编译,那么为什么不在web config文件中使用appSettings呢?那是它的用途。如果您真的需要智能感知,则可以在其中一个程序集中放置一个类来读取配置值并将其公开为属性以便更轻松地引用。如果这是敏感数据,那么我不会将其放在配置文件中,我会仍然进行编译,因为您不希望危及应用程序。
<appSettings>
    <add key="myconstant" value="here's the value!" />
</appSettings>

以下是引用该值的类,可提供智能感知、便于未来轻松更改且无需重新编译。

public class MyAppConfigSettings
{
    public string MyConstant { get; private set; }

    public MyAppConfigSettings()
    {
        MyConstant = ConfigurationManager.AppSettings["myconst"];
    }
}

这可能不是解决方案的答案,但它可能会给你一些其他的想法。


我会重写构造函数,让它接受 NameValueCollection 或类似的东西,而不是从静态的 AppSettings 属性中获取。然后通过 IoC 进行注入。 - CodesInChaos
是的,我希望有一种更简单的解决方案而不必使用web.config。通常我会编写自己的ConfigWrapper和IConfig以及一个单例类作为CustomConfigManager,将所有这些属性设置为只读,并仅与物理文件交互一次。如果需要更改,则应重新启动应用程序。因此,web.config是避免此问题的唯一方法吗? - The Light
@CodeInChaos,这也是完全可以接受的,但我的实现完全封装了从配置文件中读取,并防止使用该类的客户端代码必须获取NameValueCollection来传递它。保持松散耦合。如果您需要针对配置类编写单元测试(我不知道为什么要这样做,因为没有任何逻辑可供测试),您可以提供一个接受NameValueCollection的第二个构造函数。不过我认为那会有点过了。 - Jeff LaFay
你的类与配置存储机制紧密耦合。但客户端代码不应该构造 MyAppConfigSettings 的实例。唯一构造 MyAppConfigSettings 的代码应该是 IoC 容器。我说的不是测试配置类,而是测试依赖于配置类的类。使用不同的配置进行测试对我来说听起来很标准。 - CodesInChaos
@jlafay 是的,如果你有一个接口而且客户端代码只引用接口,从不引用类(尤其是不构造它),那么你的类就没问题了。 - CodesInChaos
显示剩余2条评论

3
我不确定我完全理解这个问题...你正在寻求一种存储一些全局变量的解决方案,这些变量如果更改了,就不会导致引用这些全局变量的程序集重新编译?如果是这样,为什么不考虑根据控制反转原则重新设计架构呢?思考“别打电话给我们,我们会打电话给你”的好莱坞原则。如果所有需要一些常量的程序集都调用一个接口(它们自己拥有),该接口公开一个具有所需值的属性,然后您有一个实现这些接口的常量项目(通过引用这些项目,然后实现这些接口),那么当您更改常量的值时,这些项目将永远不需要重新编译。
我相信你已经知道它们了,但请阅读一下SOLID原则,“D”代表依赖反转原则(控制反转)。我认为,考虑到您的关注点(假设我理解得正确),它们真的可以帮助您。
反转控制的一个例子可能很简单,例如:
MyService.dll:
public class MyService
{

    // injected dependency
    public IMyConstants MyConstants { get; set; }

    public MyMethod(){

        // get your query...
        var query = IMyConstants.Query;
    }

}

MyConstants.dll :
public MyConstants : IMyConstants {

    // implementation of query property from the myservices.dll interface
    public string Query { ... }

}

因此,myconstants.dll 引用 myservice.dll,而不是反过来(这意味着 myservices 不需要重新编译)。然后启动代码(用于设置所有内容并注入依赖项)存放在其他位置。
如果我误解了你的意思,抱歉,但希望这可以帮到你!

你说得对,配置应该通过IoC注入而不是从静态成员中读取。有一些小问题:我会使用构造函数注入,并且我不会在这里使用Constant这个名称,因为配置值并不是真正的常量。如果它真的是一个常量,我们就不需要在这里使用DI了。 - CodesInChaos
维基百科引用SOLID中的I为“接口隔离原则”。我认为你想要的原则是“推动,而不是拉动”。 - CodesInChaos
是的,那听起来大概就是这样 - 你知道我的意思。我只是有些混淆了 DI 的一些解释! - james lewis
对于任何阅读我上面评论的人 - 我已经进行了一些编辑,以包含正确的SOLID原则引用。感谢@CodeInChaos! - james lewis

2

如果您正在启用 fxCop(Visual Studio 发行版中包含的代码分析工具),您可能会收到建议将常量更改为:

public static readonly string ConstName = "a value";

这样可以提高代码的可读性和维护性。

3
我没有编译器在手边,但我很惊讶于“静态”关键字是否需要或甚至合法,鉴于“const”值的性质。 - spender
http://blogs.msdn.com/b/csharpfaq/archive/2004/03/12/why-can-t-i-use-code-static-code-and-code-const-code-together.aspx - spender
@spender,你是在提到这个答案的早期版本吗?因为static readonly确实很有意义。当使用const时,只有隐式的static,而使用readonly时则不然。 - CodesInChaos
@CodeInChaos:我可能误读了答案。在发表评论时,我认为它包含了相同声明中的“const”和“static”。 - spender

0
在大多数情况下,我更喜欢第二个选项,因为它不会引起问题(通过将值复制到其他程序集)。速度可能比常量慢,但这种纳秒级别的速度还是相当不成熟的。

0
你可以使用缓存对象并在Global.asax中定义它们。

0

如前所述,情境并不相同:

  • const: 是常量且除了重新编译以外不能被修改。
  • readonly: 该值在声明或构造函数中初始化后保持只读状态。

当字段声明包括 readonly 修饰符时,对于由声明引入的字段的赋值只能作为声明的一部分或在同一类中的构造函数中进行。


针对我指出的问题,它们的行为都是类似的。除非这些值是从一个单独的物理文件(如配置文件)初始化的,否则在更改时需要调用者重新编译。 - The Light
在您的情况下,我会选择在静态帮助类中使用const。 - lnu
你没有理解问题。 - The Light

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接