views:

1407

answers:

5

Why is the following forbidden?

Nullable<Nullable<int>>

whereas

struct MyNullable <T>
{


}

MyNullable<Nullable<int>>

is NOT

+13  A: 

I believe you can only use non-nullable value types in Nullables. Since Nullable itself is nullable, nesting this way is prohibited.

From http://msdn.microsoft.com/en-us/library/kwxxazwb.aspx

public Nullable(
    T value
)

Type: T A value type.

recursive
Nullable is still a value type. it is just a nullable one
ShuggyCoUk
You're right. Didn't realize that. I assumed that since it could be compared to null, it had to be a ref type, but it's a struct. I corrected my answer.
recursive
common misunderstanding judging by the other answers :)
ShuggyCoUk
+1  A: 

The generic type parameter for Nullable must itself be a non-nullable type (i.e. value type). This is the C# compiler warning I get, and it would seem to make sense. Tell me, why would you want to do such a thing anyway? I personally can see no use, and little meaning to such a declaration even.

Noldorin
Four value bool? =D
strager
I frequently use bool? when I might have a true / false / unknown state. Very valid when coming from the SQL world.
Andrew Robinson
It have no intention of using it, I just feel that it violates similar constrain for a different value type generic. Is Nullable special here?
Heh, I think you'd need to relearn how to program if you ever felt a desire to implement such a thing...
Noldorin
@Robinson, But a nullable nullable bool? That's what the question is asking for. I see the benefits of a three value bool (true, false, filenotfound).
strager
@Sasha: I wouldn't say Nullable is that special here (although it does have a built-in language feature in C#). Generic type constraints aren't terribly common, but not exceedingly rare either (you can create them yourself using the "where" clause on type declarations).
Noldorin
Yeah, "three-valued bools" can indeed be useful (see ternary/fuzzy logic for examples), but the case of four-valued bools would seem rather pointless (besides being terrible design practice).
Noldorin
@Noldorin: why don't you tell this to Jon Skeet (@jonskeet), as this example was taken from his book. just a thought
A: 

Nullable allows you to take a value type and make it like a reference type, in the sense that the value either exists or not (is null). Since a reference type is already nullable it is not allowed.

Quoted from MSDN:

The Nullable(T) structure supports using only a value type as a nullable type because reference types are nullable by design.

tvanfosson
it doesn't make it like a reference type, it just gives it an additional special value and compiler support for same. copy semantics remain (the major difference)
ShuggyCoUk
@Shuggy -- quoting again from the same reference: Represents an object whose underlying type is a value type that can also be assigned a null reference (Nothing in Visual Basic) like a reference type.
tvanfosson
that is the only way it is like it, with considerable compiler magic to make it happen. Your answer is misleading in that it implies it makes it a reference type, in fact it is the struct constraint that is special cased.
ShuggyCoUk
+14  A: 

This is because the struct constraint actually means 'not nullable' since Nullable, despite being a struct, is nullable (can accept the value null) the Nullable<int> is not a valid type parameter to the outer Nullable.

This is made explicit in the constraints documentation

where T: struct
The type argument must be a value type. Any value type except Nullable can be specified.
See Using Nullable Types (C# Programming Guide) for more information.

If you want the rationale for that you would need the actual language designer's comments on it which I can't find. However I would postulate that:

  1. the compiler and platform changes required to achieve Nullable in it's current form are quite extensive (and were a relatively last minute addition to the 2.0 release).
  2. They have several potentially confusing edge cases.

Allowing the equivalent of int?? would only confuse that since the language provides no way of distinguishing Nullable<Nullable<null>> and Nullable<null> nor any obvious solution to the following.

Nullable<Nullable<int>> x = null;
Nullable<int> y = null;
Console.WriteLine(x == null); // true
Console.WriteLine(y == null); // true
Console.WriteLine(x == y); // false or a compile time error!

Making that return true would be very complex and significant overhead on many operations involving the Nullable type.

Some types in the CLR are 'special', examples are strings and primitives in that the compiler and runtime know a lot about the implementation used by each other. Nullable is special in this way as well. Since it is already special cased in other areas special casing the where T : struct aspect is not such a big deal. The benefit of this is in dealing with structs in generic classes because none of them, apart from Nullable, can be compared against null. This means the jit can safely consider t == null to be false always.

Where languages are designed to allow two very different concepts to interact you tend to get weird, confusing or down right dangerous edge cases. As an example consider Nullable and the equality operators

int? x = null;
int? y = null;
Console.WriteLine(x == y); // true
Console.WriteLine(x >= y); // false!

By preventing Nullables when using struct generic constraint many nasty (and unclear) edge cases can be avoided.

As to the exact part of the specification that mandates this from section 25.7 (emphasis mine):

The value type constraint specifies that a type argument used for the type parameter must be a value type (§25.7.1). Any non-nullable struct type, enum type, or type parameter having the value type constraint satisfies this constraint. A type parameter having the value type constraint shall not also have the constructor-constraint. The System.Nullable type specifies the non-nullable value type constraint for T. Thus, recursively constructed types of the forms T?? and Nullable<Nullable<T>> are prohibited.

ShuggyCoUk
Would you succinctly describe why "Any value type except Nullable can be specified" is TRUE, or provide a reference?
why they made the decision I have expanded on. The specific 'You can't because we say so' part is in the already linked documetation on the struct constraint (link 2) the 25.7 section of the spec states this clearly
ShuggyCoUk
Your explanation for why x.ToString() does not throw an exception is not quite right; a boxed Nullable<int> is in fact null. Check the MSIL for what's going on. Other than that, this answer is spot on.
Curt Hagenlocher
oops - brain fart you're right. fixing now
ShuggyCoUk
hopefully a better example - more obviously bizarre too. thanks
ShuggyCoUk
+2  A: 

Nullable is special because there's explicit support for boxing and unboxing of Nullable types built into the CLR:

If you use the MSIL box instruction against a Nullable<T>, you will actually get a null as the result. There's no other value type which will produce a null when boxed.

There's similar and symmetrical support for unboxing.

Curt Hagenlocher
Cool, it is special, but it doesn't answer the question why it is allowed.
Why *which* is allowed? Nullable<Nullable<int>> is clearly *not* allowed, and it's because the "specialness" of Nullable is incompatible with doing so. MyNullable<Nullable<int>> is allowed because there's no conflict with the CLR's special handling.
Curt Hagenlocher