The question is actually very straightforward. The following code throws the exception right below it:
class Foo
{
public const StringBuilder BarBuilder
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);
};
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.
{
}
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.
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").