13

The question is actually very straightforward. The following code throws the exception right below it:

class Foo
{
    public const StringBuilder BarBuilder = new StringBuilder();
    public Foo(){

    }
}

Error:

Foo.BarBuilder' is of type 'System.Text.StringBuilder'. A const field of a reference type other than string can only be initialized with null.

MSDN says this, which I understand and it makes sense from const perspective:

A constant expression is an expression that can be fully evaluated at compile time. Therefore, the only possible values for constants of reference types are string and a null reference.

However, I don't see the reason why or where we would use null constant. So why in the first place that a reference type (other than string) can be defined with const if it can be only set to null and if it was a deliberate decision (which I believe it is) then where can we use constant with null values?

Update:

When we think of an answer, please let's think differently than "We have this so why not that..." context.

Tarik
  • 79,711
  • 83
  • 236
  • 349
  • 1
    Are you asking "why would you ever use `null`?" – Evan Trimboli Dec 30 '14 at 05:13
  • `const` wasn't designed for reference types – Fabio Dec 30 '14 at 05:15
  • The compiler is preventing you to use const like this, the message should _not_ be interpret as "you can assign a const reference type to null", as you can see that is not useful. – kennyzx Dec 30 '14 at 05:19
  • 9
    There are any number of constructs in the language that will compile but have no obvious utility to anyone (sealed, empty classes, for example). The compiler doesn't try to ensure the usefulness of a program. – Asad Saeeduddin Dec 30 '14 at 05:21
  • @Asad: Just because they don't have an obvious utility doesn't mean we should not ask why. – Tarik Dec 30 '14 at 05:27
  • 1
    You can use `null` constant for declaring optional parameters. – user4003407 Dec 30 '14 at 05:27
  • @PetSerAl: You are right actually, I like it. – Tarik Dec 30 '14 at 05:28
  • 2
    @Tarik That *is* the why of it. A constant is merely an alias for a specific literal that has a special meaning in your program. It gets switched out at compile time with the actual literal. It is possible to do this for value types, strings, and null references, since their value is completely known at compile time, so the compiler happily accepts them. There is no reason for the compiler to specifically think about "oh, null consts aren't actually useful", and give you a build error. – Asad Saeeduddin Dec 30 '14 at 05:35
  • @Asad: I understand, but the problem is that if we think that way then we should see many other optimizations, or compiler rules irrelevant. Compiler enforces rules so that people write safer and more consistent codes. – Tarik Dec 30 '14 at 05:38
  • 1
    @Tarik Maybe I'm misunderstanding what you're saying, but compiler optimizations are completely irrelevant to this discussion. A compiler with or without optimizations should conform to the same language spec, which is what your question is about (why does the C# language spec allow nulls in constants). – Asad Saeeduddin Dec 30 '14 at 05:40
  • @Asad: Optimization was just an example for the sake of what compiler does for us. Since you said *"There is no reason for the compiler to specifically think about "oh, null consts aren't actually useful", and give you a build error"*, but I think differently and say if it is against the consistency in the language and/or other reasons, compiler should say "oh null consts aren't useful"... – Tarik Dec 30 '14 at 05:43
  • 1
    Asking "why" something is, rather than how it works or what it does, is getting into very subjective territory. It is technically possible to substitute a null value for a reference type at compile time, and the language creators chose to allow that in the language. Beyond that, there's not much to discuss here, unless you have a different question. – JDB Dec 30 '14 at 05:48
  • 1
    @Tarik Why would it be against "consistency"? You can only add a constant for a **literal**, i.e. a fixed value that is completely known at compile time, and can be inserted directly into the IL. This is the only requirement, and nulls, strings, and bultin value types meet it. Would it be more consistent for the requirement to have an extra clause like: "a const can only hold literals, *except for null, because we feel this is not useful*". – Asad Saeeduddin Dec 30 '14 at 05:48
  • 1
    I think there is one fairly common use case which is that you wish to avoid typing out something like `(ClassA)null` many times, for example to disambiguate a method overload. Since `const` can also be used for locals, but not `readonly`, you can declare a local like `const ClassA NullA = null;` and use that throughout a method. – Mike Zboray Dec 30 '14 at 06:54

4 Answers4

7

From MSDN

when the compiler encounters a constant identifier in C# source code (for example, months), it substitutes the literal value directly into the intermediate language (IL) code that it produces. Because there is no variable address associated with a constant at run time, const fields cannot be passed by reference and cannot appear as an l-value in an expression.

Because reference types (other than null, and strings which are special) need to be constructed at run time, the above would not be possible for reference types.

For reference types, the closest you can get is static readonly:

class Foo
{
    // This is not a good idea to expose a public non-pure field
    public static readonly StringBuilder BarBuilder = new StringBuilder();
    public Foo(){
    }
}

Unlike const substitution (in the calling code), static readonly creates a single shared instance of the reference type which has subtle differences if assembly versions are changed.

Although the reference cannot (normally) be reassigned, it doesn't preclude calling non-pure methods on the StringBuilder (like Append etc). This is unlike consts, where value types and strings are immutable (and arguably should be "eternal").

StuartLC
  • 104,537
  • 17
  • 209
  • 285
7

However, I don't see the reason why or where we would use null constant.

Null constants are useful as sentinel values.

For example, this:

public class MyClass
{
    private const Action AlreadyInvoked = null;

    private Action _action;

    public MyClass(Action action) {
        _action = action;
    }

    public void SomeMethod()
    {
        _action();

        _action = AlreadyInvoked;
    }

    public void SomeOtherMethod()
    {
        if(action == AlreadyInvoked)
        {
            //...
        }
    }
}

Is much more expressive than this:

public class MyClass
{
    //...

    public void SomeMethod()
    {
        _action();

        _action = null;
    }

    public void SomeOtherMethod()
    {
        if(action == null)
        {
            //...
        }
    }
}

The source code for the Lazy<T> class shows Microsoft used a similar strategy. Although they used a static readonly delegate that can never be invoked as a sentinel value, they could have just used a null constant instead:

static readonly Func<T> ALREADY_INVOKED_SENTINEL = delegate
{
    Contract.Assert(false, "ALREADY_INVOKED_SENTINEL should never be invoked.");
    return default(T);
};
dcastro
  • 66,540
  • 21
  • 145
  • 155
1

As you state in the question, there is one reference type that can be put into a const reference - strings. The compiler special-cases this and puts the strings into the compiled output and allows them to be read into the reference type at runtime.

Of course this begs the question - why not have strings be the only reference types that can be const, as long as we're special-casing them anyway? To that, I can only speculate that adding a special case in the compiler is simpler and less problematic than adding a special case in the language. From a language perspective, a string is just a reference type, even if the compiler has special handling to create instances of it from string literals and compiled resources.

Avner Shahar-Kashtan
  • 14,492
  • 3
  • 37
  • 63
  • Thanks Avner, but I don't think that it would cause so much problem since the compiler already can distinguish string from other reference types. The hardest part would be this special treatment between string and other reference types but compiler already handles it. What I am asking should not that hard. – Tarik Dec 30 '14 at 05:34
  • It's not hard, but it *makes the language more complex*. It's better to keep the language simple and the compiler complicated – Avner Shahar-Kashtan Dec 30 '14 at 05:38
  • It is actually making the language simpler by making it more consistent. Compiler is already complex enough. – Tarik Dec 30 '14 at 05:39
0

I think that you are asking that why reference type with null allow as a constant.

I think you are right that it does not make much sense but it is useful if you have designed your own library and if you want to compare with null but want to give special meaning ( like comparing with your library value only rather then directly null)

public class MyClass
    {
        public const MyClass MyClassNull = null;
        public MyClass()
        {
        }
    }

it usage like this.

object obj = GetMyClass();
if(obj == MyClass.MyClassNull) // This going to convert to actual null in MSIL.
{    
}
dotnetstep
  • 17,065
  • 5
  • 54
  • 72