I read the C# spec for the new nullable operators last year. I forgot a couple of surprises until I reread the spec today.
First, the null coalescing operator (a ?? b), which returns the second operand if the first operand is null, also works on reference types, not just nullable value types. So, stringVariable ?? “default” returns “default” if stringVariable is null.
Second, a bool? is the C# equivalent of a ternary boolean, which can take three values—true, false or null. As such, it is treated differently from all other nullable variable types in order to mimic the behavior of database nulls. The “&” operator will still return false, if any operand is false, and the “|” operators will still return true, if any of the operands are true, even in presence of nulls. Normally, a null operand anywhere causes the entire expression to become null. Also, a bool? is accepted in any expression that normally takes a bool—if and while, for example.
Remaining Nullable Questions
In other nullable news, I was wondering what the impact of certain operations on Nullable<T> would be after the latest DCR(Design Change Request)?
The DCR causes the following invariant to fail: new T?(value).GetType() returns typeof(T) rather than typeof(T?). Even stranger, new T?().GetType()looks like it would return a null reference exception, because the default constructor produces a null value.
It also seems that Nullable<T> would produce a different result for GetHashCode() and ToString(), depending on whether it was boxed or not… In boxed form, we should get a null reference exception for a null value used in either of the two functions; in unboxed form from Beta 2, we would get zero for the hashcode value and an empty string for ToString().
Will “x is T?” always return true whenever “x is T”? It seems that since boxed objects can no longer internally store the type“Nullable<T>”, that “x is T?” should return whatever value “x is T” would return.
It also seems that “x as T?” operation should now be possible even though the “as” operator normally doesn’t work with value types; I hope the C# team doesn’t miss that.
Lastly, Is DBNullValue obsolete, now? What about the SQLInt32 and other System.Data SQLxxx type wrappers?
Interestingly, it is not possible to call any methods on Nullable<T> via reflection (except possibly by using the obscure technique of using TypedReferences.)
Hi Wes,
Here are some answers to a few of your questions.
>> new T?(value).GetType() returns typeof(T) rather than typeof(T?)
>> new T?().GetType() looks like it would return a null reference exception
Correct, in order to invoke GetType the compiler inserts a box operation which transforms the Nullable. We considered newslotting a GetType method on Nullable such that compilers could bind directly to that. And of course it would have merely returned typeof(T?). But many people thought this was too hackish.
>> Nullable would produce a different result for GetHashCode() and ToString(), depending on whether it was boxed or not
Because both GetHashCode and ToString are overridden virtual functions, the compiler can simply emit a constrained virtual call without boxing. The Nullable BCL type still implements Beta2 behavior, i.e. returning 0 for GetHashCode and String.Empty for ToString. My personal belief is that the descrepancy between this and GetType is ugly. I believe we should have done the hack for GetType (or thrown for these two functions). But there were some relatively strong and highly thought of opinions contrary to that belief.
>> Will “x is T?” always return true whenever “x is T”?
Yes, we also modified the 'isinst' instruction to add this success case. In other words, it echoes back the top of the stack when (1) the type token is a T? and (2) the top of the stack's runtime type is either a T or a T?. The reverse works automatically because the only form that boxed T?'s take is that of a boxed T, using the existing unmodified logic.
>> It also seems that “x as T?” operation should now be possible even though the “as” operator normally doesn’t work with value types
I don't recall trying that out. But the CLR certainly permits it. I have not checked whether the compiler explicitly relaxes its rules. As of Beta2, it doesn't (but that was before the DCR).
Posted by: Joe Duffy | August 27, 2005 at 12:07 PM
>>Interestingly, it is not possible to call any methods on Nullable via reflection
That's not true, at least in Beta 2. I've written a simple VM to help debug code generation for a domain-specific language (a language with total SQL null semantics) we have at our company (the normal codegen uses LCG). These methods:
_nullableType = typeof(Nullable<>).MakeGenericType(valueType.AsNativeType);
_nullValue = Activator.CreateInstance(_nullableType);
_getValue = _nullableType.GetProperty("Value").GetGetMethod();
_hasValue = _nullableType.GetProperty("HasValue").GetGetMethod();
_constructor = _nullableType.GetConstructor(new Type[] { valueType.AsNativeType });
are all callable via reflection, and I've got the code to prove it!
Posted by: | August 29, 2005 at 02:48 AM
Actually, it is not possible to call this under the DCR, which is post beta-2... because a nullable type is not representable in boxed form.
Posted by: Wesner Moise | August 29, 2005 at 03:35 AM
Hi Wesner Moise,
>> are all callable via reflection, and I've got the code to prove it!
for those methods you are right, but what about assign a new value to the instance of nullable class? I solved this by using static class Nullable and its method Wrap(...). But this is not correct solution for reflection :(
Posted by: Alexandr Goida | November 08, 2005 at 02:17 AM