Don't repeat yourself; consts are already static

Don't repeat yourself; consts are already static

Rate This
  • Comments 20

Today, another entertaining question from StackOverflow. Presented again as a dialogue, as is my wont.

The specification says "even though constants are considered static members, a constant-declaration neither requires nor allows a static modifier." Why was the decision made to not force constants to use the static modifier if they are considered static?

Let's grant that it is sensible that constants are considered to be "static" - that is, members associated with the type itself, rather than members of a particular instance. Let's also grant that "const" has to be in there somewhere. There are three possible choices for how to notate a constant declaration:

1) Make static optional: "const int x..." or "static const int x..." are both legal.
2) Make static required: "const int x..." is illegal, "static const int x..." is legal
3) Make static illegal: "const int x..." is legal, "static const int x..." is illegal.

Does that pretty much sum it up?

Yes. Why did the design team choose option (3) instead of (1) or (2)?

The design notes from 1999 do not say. But we can deduce what was probably going through the language designer's heads.

The problem with (1) is that you could read code that uses both "const int x..." and "static const int y..." and then you would naturally ask yourself "what's the difference?" Since the default for non-constant fields and methods is "instance" unless marked "static", the natural conclusion would be that some constants are per-instance and some are per-type, and that conclusion would be wrong. This is bad because it is misleading.

The problem with (2) is that first off, it is redundant. It's just more typing without adding clarity or expressiveness to the language. And second, I don't know about you, but I personally hate it when the compiler gives me the error "You forgot to say the magic word right here. I know you forgot to say the magic word, I am one hundred percent capable of figuring out that the magic word needs to go there, but I'm not going to let you get any work done until you say the magic word". I once heard that tendency of compilers compared to the guy at the party who interrupts your conversation to point out that you said "a historic occasion" when clearly you meant to say "an historic occasion". No one likes that guy.

The problem with (3) is that the developer is required to know that const logically implies static. However, once the developer learns this fact, they've learned it. It's not like this is a complex idea that is hard to figure out.

The solution which presents the fewest problems and costs to the end user is (3).

That seems reasonable. Does the C# language apply this principle of eschewing redundancy consistently?

Nope! It is interesting to compare and contrast this with other places in the language where different decisions were made.

For example, overloaded operators are required to be both public and static. In this case, again we are faced with three options:

(1) make public static optional,
(2) make it required, or
(3) make it illegal.

For overloaded operators we chose (2). Since the "natural state" of a method is private/instance it seems bizarre and misleading to make something that looks like a method public/static invisibly, as (1) and (3) both require.

For another example, a virtual method with the same signature as a virtual method in a base class is supposed to have either "new" or "override" on it. Again, three choices.

(1) make it optional: you can say new, or override, or nothing at all, in which case we default to new.
(2) make it required: you have to say new or override, or
(3) make it illegal: you cannot say new at all, so if you don't say override then it is automatically new.

In this case we chose (1) because that works best for the brittle base class situation of someone adds a virtual method to a base class that you don't realize you are now overriding. This produces a warning, but not an error.

Each of these situations has to be considered on a case-by-case basis. The general guidance is not , say "always pick option 3", but rather, to figure out what is least misleading to the typical user and go with that.

Eric is at TechEd; this posting was pre-recorded.

 

  • Don't forget about optional accessibility modifiers.

    We actually prefer explicitly declared accessibility for class/struct members ("private void Foo()" rather than "void Foo()").

    I prefer to declare things explicitly as well. Reason being, the omission can be read as "I did this deliberately" or as "I did this by accident, I meant to say something else". If it is explicit then you know that someone probably thought about whether it should be private or not. - Eric

  • The second example you give (operators) reminds me of explicit interface implementations.  Since they're implementing interfaces, they're required to be public instance members - but unlike the operators example the compiler does not force you to state the accessibility.  I prefer it, I just wonder what makes that case different than operators?

    Excellent question. Explicit interface implementations are weird; they are public in the sense that anyone who can see the instance can access the member directly. They are private in the sense that you cannot see them from the class itself. In some sense they're sort of not members of the class at all; you cannot call them directly. Suppose you have a public class with an explicit implementation of an internal interface: would it make sense to mark those as "internal"? No matter what you do, it seems weird. Therefore, no modifiers at all. - Eric

  • Eric,

    You are the Raymond Chen of C#

  • I've been mulling similar questions. Whenever I've suggested (at tech events, on Stack Overflow etc) that I'd prefer it if classes were sealed by default, I've found it's a controversial opinion. There's real debate to be had about this (and I'm not suggesting we should debate it here) but there's an obvious middle ground: don't make the language itself have an opinion itself. Where there are two options, force the user to think about the decision and specify one of them.

    I know what you mean. I am strongly in favour of sealing, but that's been controversial in the past. - Eric

    It's too late to do any of this now, but I wonder what options might be best made explicit? Obvious possibilities:

    - Sealed/unsealed classes
    - Static/non-static classes (this would be hideous)
    - Static/non-static members
    - Readonly/mutable fields
    - Accessibility modifiers
    - Virtual/non-virtual methods
    - Override/new (basically turn the current warning into an error)

    I think in *most* of these cases I'm happy that there are defaults - and most of the defaults feel "right" too. But it's an interesting thought experiment...

    I once sketched out a language that I was going to implement for fun caled "Verbose" which was basically a simplified C# 1.0 with no optional anything, and so on. Basically, it would have been a slightly nicer syntax than MSIL. I never did get around to it. - Eric

  • One question about constants and redundancy: why do we have to specify the constant type explicitly, even though it can definitely be inferred? Using "const int a", but "var a" looks inconsistent.

    Good question. I noted earlier that there's no var on fields in part because this produces a new kind of circularity. But constants are already checked for circularity, so that doesn't hold water. Instead, I'd push back on this because it buys you so little. Constants are never of anonymous type, so var is never required. Constants are never of a long complicated type, so var does not save typing. Constants are never of the form "const Blah x = new Blah();" so var does not eliminate redundancy. And finally, "var" means "variable" and constants are not variables. Having no good reason to do the feature, and some reasons against puts it very, very low on the priority list. - Eric

  • An interesting thing to know is that const variables are resolved at compile time and static variables are resolved at runtime. It would make no sense if someone could declare a static const variable.

    Indeed, this is a good distinction. Though I will note that const fields are not variables; "const variable" is an oxymoron. Variables by definition are storage locations that contain values. Variables by definition change; that's why they're called variables. Constants do not represent storage locations, they represent values, and they do not change. - Eric

  • Nice article Eric. I think it would also be useful to address in this difference between something like "private const int X = 123" and "private static readonly int = 123". It's another example of potential confusion/redundancy in the language - and in my experience, many folks don't necessarily understand how "static readonly" and "const" differ.

  • @Leo Bushkin : Don't understand the difference != There is no difference

    The difference while subtle, is very important in those two cases. const is a compile time-constant, while static readonly can be set to whatever value you wish in the static constructor.

    This is one of my favorite question in c# interviews :)

    I'm not so sure that is a good interview question; it seems to test for knowledge of trivia rather than any particular skill or aptitude. Do you find you get good results with this question? - Eric

  • Explicit declaration syntax is perferable over variable ones.  There should be one way to declare something for each combination of scope, protection, etc.

  • Personally I've always been kind of agitated that you have to differentiate between "const" and "readonly" for variables depending on if they are reference type or value type.  I understand that there is a semantic difference between the two, but it's frustrating that when I go to declare a constant I have to consult a flowchart to decide if it needs to be "const", "readonly", or "static readonly".  It makes my variable declarations in the class look all mismatched.  I'm sure there's some technical reason why this distinction exists, but I wish it were abstracted away.

    There are a number of mis-statements above which might be contributing to your agitation. First off, constants are not variables; constant and variable are opposites, so it would be a neat trick to have something that is both. Variables represent storage locations that contain values and those values can change at runtime. Constants represent values, not storage locations, and they never change.

    Second, whether the field type is of reference type or value type has nothing to do with whether the field may be constant or not. You can have a const of any reference type provided that it is null, or a string constant. You cannot have a const of any value type; it can only be one of the built-in value types or an enum.

    The distinction between const and static readonly is "does the value of this field ever change?" If it changes once in the lifetime of the type in an appdomain then it's static readonly. If it doesn't change at all over all time then it can be a constant. Does that help? - Eric

    From the "missing the point of the analogy" department:

    The guy at the party is wrong.  "Historic" does not have a silent h, you are supposed to pronounce it.  Since the word begins with a consonant sound, "a" is the proper article.  It is good etiquette to correct the guy at the party by attempting to shove your fist through his face.  "An historic event" sounds like something I'd hear from a comedic relief "generic uneducated English accent guy" on a TV show.  "Oi!  The guv here's in the pub before sundown, this is an 'istoric event, buy 'im a round!"

    My Canadian-ness is showing again. I always thought that the rule was "an historic occasion"; apparently Americans do not follow this rule. See this article for some more thoughts on it. - Eric
  • Hey Eric,

    Your article is educational.

    Can you shed some light on expression

    "public enum Xyz : int"

    you can not inherit integer but this statement is not aobut inheriting (even thought it looks same)its saying that you can represent enum values as integer.

    is not this confusing?

    If not, then how to determine what was intentional and what is left my mistake?

    I'm with you. I find lots of things about enums confusing and weird; this is one of them. I wish we had gone with two distinct kinds of enums: actual bounds-checked enums that were guaranteed to be values of a particular set, and a nice way to represent named bits in a bitfield. Conflating those two things is confusing. More generally, it would be nice to be able to more easily make types that wrap new semantics around integers, for things like counters and indices. - Eric

  • It would be nice to be able to wrap new semantics around any basic numeric type.  Have you guys considered doing the F# thing and turning the basic numeric types into templates parameterized by the unit type, like int<Apples> or int<USDollars> or float<cm>?  But being able to inherit from the basic types would be almost as good.  I'm not convinced they need to be sealed, just don't have them expose any members except the operators.

  • Although const may be implemented somewhat as a static, logically it isn't even that. Otherwise, const would be the same as static readonly, which it isn't

  • @Eric, I actually think that the const / readonly distinction is quite an important distinction to make, when one applies for a senior developer / architect position.

    For junior level position I find questions like "Have you heard of an Interface" more suitable :)

  • >> Since the "natural state" of a method is private/instance it seems bizarre and misleading to make something that looks like a method public/static invisibly

    And yet, interface members are implicitly public, and cannot be explicitly declared as such.

    Of course, one could argue that it is the "natural state" of a _class_ method to be private/instance, and interface is not a class. But then members of static classes have to be explicitly declared static, and, arguably, the difference between a class and a static class - at least syntactically - is no smaller than the difference between a class and an interface.

Page 1 of 2 (20 items) 12