11

I have a Common project inside which I've added my public constants for QueryStringNames.

I know generally constants should be as internal or private but I'd need public constants here as I'd like to allow a global access to the query string names, session keys, etc.

There are 3 solutions that I know of but all of them have an important issue. The caller assembly would contain the copy of my constant which means if I have to change a constant value, I'll have to compile both my Common assembly and the caller assembly!

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

What would be the best approach to define public constants in C# apart from storing them in the web.config file (which doesn't have intellisense)?

Jeff LaFay
  • 12,882
  • 13
  • 71
  • 101
The Light
  • 26,341
  • 62
  • 176
  • 258
  • 1
    You can have intellisense in config if you create a proper xsd schema – Felice Pollano Dec 09 '11 at 13:54
  • I'm a little confused. What does the storage location of the value (ie webconfig) have to do with the accesability of a copy of that value in memory? – asawyer Dec 09 '11 at 13:55
  • you're right @asawyer; that wouldn't resolve the problem if again const and readonly are used. – The Light Dec 09 '11 at 13:59
  • @asawyer - I think the OP's point was that he wouldn't have to recompile all the assemblies if it was in a config? – james lewis Dec 09 '11 at 14:03
  • you cannot use the constant without its class instance, if you mean that. i use a static co class for this so i use co.PI so it is only 3 character longer than just use PI in code. – Stefanos Zilellis Oct 28 '20 at 18:35
  • Microsoft has an answer: https://learn.microsoft.com/en-us/dotnet/csharp/programming-guide/classes-and-structs/how-to-define-constants – james.garriss Jun 08 '21 at 17:27

7 Answers7

7

It depends. If it is truly a constant that won't change, even in future versions of your code, then const is fine. Else go with a static readonly field.

A const will get embedded into the calling assembly, whereas with static readonly the calling assembly only contains a reference to the field. This means const requires recompilation of all dependent code whenever you change the value, whereas public readonly uses the new value even without recompiling the calling assembly.

If you want to store the "constant" in a config file, but like Intellisense, you can use a property with no public setter. And then fill it from the config file at runtime. But I'd argue that configuration values should not be static in the first place. For configuration values I'd use a singleton of some sort, preferably the IoC variation and not the Class.Instance variation. So I'd just define an interface like the following:

interface IMyConfig
{
  string Key{get;}
}

And have classes that need this config take it as a constructor parameter:

public MyClass(IMyConfig config)
{
    ...
}
CodesInChaos
  • 106,488
  • 23
  • 218
  • 262
  • why?! I tested and static readonly would also need the recomilation of the caller. – The Light Dec 09 '11 at 14:00
  • 1
    I'm pretty sure that changing a static readonly field doesn't require recompiling the caller. The calling assembly only contains the name of your field, and not the value of it. – CodesInChaos Dec 09 '11 at 14:01
  • @CodeInChaos is correct. If you were to use a constant, that value is inserted by the compiler, whereas the readonly field is inserted at runtime (either at declaration or in the constructor, depending on how you implement the readonly value). Even a static readonly would be done at runtime, whenever the static fields for the class are initialized. – Jason Down Dec 09 '11 at 14:06
  • I agree, if there is a possibility they can change (and cause recompile), then they're not really constants. I prefer const as they are easier to use in switch / case statements. But if you are talking about querystring & session keys, changing those will cause a recompile and redeploy anyway. – danludwig Dec 09 '11 at 14:08
  • @CodeInChaos, it does apparently; test it yourself and see! or maybe I haven't tested properly. – The Light Dec 09 '11 at 14:24
  • 2
    @William Perhaps you're confused by VS rebuilding all dependent assemblies by default. But if you take the new "static readonly" containing assembly and simply replace that single dll with a different version, all assemblies building on it should still work, and use the new values. – CodesInChaos Dec 09 '11 at 14:27
  • @CodeinChaos, I had manually copied the assemblies; otherwise VS would have rebuilt everything. – The Light Dec 09 '11 at 14:45
6

If you think you'd be changing it and you're worried about having to compile it, then why not use appSettings in the web config file? That's what it's for. If you really need intellisense then you could just put a class in one of the assemblies that reads the config value and exposes it as a property for easier referencing. If it's sensitive data then I wouldn't put it in a config file, I would just compile it anyways since you don't want to compromise your application.

<appSettings>
    <add key="myconstant" value="here's the value!" />
</appSettings>

Here's the class to reference that value, which gives you intellisense, ability to change it easily in the future, and without having to recompile anything

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

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

It may not be the answer to your solution but it may give you some other ideas.

Jeff LaFay
  • 12,882
  • 13
  • 71
  • 101
  • I'd rewrite the constructor to take a `NameValueCollection` or something similar instead of having it fetch from the static `AppSettings` property. And then inject it via IoC. – CodesInChaos Dec 09 '11 at 14:15
  • yeah, I hoped there would be a simpler solution without using the web.config. I'd usually write my own ConfigWrapper and IConfig and a singleton class as my CustomConfigManager to have all these properties as read-only and to interact with the physical file only once. if a change is required, then the app should be restarted. So is the web.config the Only way to avoid this issue? – The Light Dec 09 '11 at 14:20
  • I'm not sure if it's the only way to avoid it. I mean you _could_ persist it in a database record as well. That way you could change the value when needed and not have to recompile. Honestly though, this really isn't that complex of a solution; it's pretty simple IMO. – Jeff LaFay Dec 09 '11 at 14:30
  • @CodeInChaos, that's totally acceptable too but my implementation encapsulates reading from the config completely and prevents client code that uses the class to have to fetch a `NameValueCollection` to pass it. Keep's it loosely coupled. If you need to write unit tests against the config class (I don't know why you would, there isn't any logic to test) you could provide a second constructor that takes a NameValueCollection. I think that would be over doing it though. – Jeff LaFay Dec 09 '11 at 14:31
  • Your class is tightly coupled to the configuration storage mechanism. But client code shouldn't construct instances of `MyAppConfigSettings`. The only code constructing `MyAppConfigSettings` should be the IoC container. I'm not talking about testing the config class, but about testing classes that depend on the config class. And testing with a different configuration sounds pretty standard to me. – CodesInChaos Dec 09 '11 at 14:35
  • So if you needed to test classes that use the config, couldn't you just write an interface for the config class and then mock it? I'm not a unit test guru but that's how I would do it. – Jeff LaFay Dec 09 '11 at 14:39
  • @jlafay yes, if you have an interface and the client code only refers to the interface, and never to the class(especially not constructing it), your class is fine. – CodesInChaos Dec 09 '11 at 14:40
3

I'm not sure if I understand the problem completely... you're asking for a solution to storing some global variables that won't cause recompiles to assemblies that reference those global variables if you change them? If so then why not try thinking about redesigning your architecture as per the Inversion of Control principle? Think "don't call us, we'll call you" the hollywood principle. If all the assemblies that require some const just call an interface (that they own) that exposes a property with the value they require, and then you have a project of constants that implement those interface (by referencing those projects and then implementing those interfaces) then those projects will never need recompilling when you change the value of the constants.

I'm sure you know them anyway but have a read up on the SOLID principles, "D" being the Dependency Inversion principle (Inversion of Control). I think given your concerns (assuming I've understood you right) they could really help you out.

An example of Inversion of Control could be as simple as:

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 { ... }

}

So the myconstants.dll references the myservice.dll rather than the other way around (meaning myservices won't need recompiling). Then the bootstrapping code (to set it all up and inject dependencies) lives elsewhere.

Sorry if I misunderstood you, hope that helps though!

Community
  • 1
  • 1
james lewis
  • 2,486
  • 3
  • 24
  • 30
  • You're right, configuration should be injected via IoC and not read from static members. A few nitpicks: I'd use constructor injection, and I wouldn't use the name `Constant` here, since a configuration value isn't really a constant. If it were truly a constant we wouldn't need DI here. – CodesInChaos Dec 09 '11 at 14:21
  • Wikipedia quotes the I in SOLID as "Interface segregation principle". I think the principle you want is "Push, don't pull". – CodesInChaos Dec 09 '11 at 14:25
  • yeah that sounds about right - you know what I mean though. I'm just getting mixed up with some interpretations of DI I think! – james lewis Dec 09 '11 at 14:33
  • To anyone else who ever reads my above comment - I've edited a little to contain the correct reference to the SOLID principles. Cheers @CodeInChaos ! – james lewis Dec 09 '11 at 14:46
2

If you are activating fxCop (code analysis tool included in Visual studio distribution), you may get sugestion to change constant to become:

public static readonly string ConstName = "a value";

habibillah
  • 111
  • 1
  • 1
  • 3
    I don't have a compiler to hand,but I'm surprised that the `static` keyword would be required or even legal, given the nature of a `const` value. – spender Dec 09 '11 at 13:58
  • 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 Dec 09 '11 at 14:04
  • @spender are you referring to an earlier version of this answer? Because `static readonly` certainly makes sense. `static` is only implicit when using `const` and not when using `readonly`. – CodesInChaos Dec 09 '11 at 14:32
  • @CodeInChaos : It's possible that I misread the answer. At time of posting my comment, I thought it contained both `const` and `static` in the same declaration. – spender Dec 09 '11 at 14:39
0

I prefer the 2nd option in most case since it won't cause problem (by copy value to other assemblies). The speed may have a slower than constants but this kind of nano-second speed is pretty immature.

Jeffrey Zhao
  • 4,923
  • 4
  • 30
  • 52
0

You could use the Cache object and define them in Global.asax

Barry Kaye
  • 7,682
  • 6
  • 42
  • 64
0

As said before, it's not the same scenario:

  • const: is contant and cannot be modified except by recompiling.
  • readonly: the value is initialized in the declaration or in the constructor and stay readonly after.

When a field declaration includes a readonly modifier, assignments to the fields introduced by the declaration can only occur as part of the declaration or in a constructor in the same class

lnu
  • 1,404
  • 1
  • 9
  • 25
  • For the problem I pointed out, they'd both behave similarly. Callers Recomilation would be required in the case of a change unless the values are initialized from a separate physical file such as a config file. – The Light Dec 09 '11 at 14:23
  • in your case I'd go with const in a static helper class. – lnu Dec 09 '11 at 14:25
  • you didn't understand the problem. – The Light Dec 09 '11 at 14:45