I am puzzled by the following behavior of Nullable
types:
class TestClass {
public int? value = 0;
}
TestClass test = new TestClass();
Now, Nullable.GetUnderlyingType(test.value)
returns the underlying Nullable
type, which is int
.
However, if I try to obtain the field type like this
FieldInfo field = typeof(TestClass).GetFields(BindingFlags.Instance | BindingFlags.Public)[0];
and I invoke
Nullable.GetUnderlyingType(field.FieldType).ToString()
it returns a System.Nullable[System.Int32]
type. So that means the method Nullable.GetUnderlyingType()
has a different behavior depending on how you obtain the member type. Why is that so? If I simply use test.value
how can I tell that it's Nullable
without using reflection?
First of all,
Nullable.GetUnderlyingType(test.value)
in your example will not compile. Ortypeof(test.value)
as I have seen in comments.Lets answer this by the following modified code:
When you do a
GetType()
on a field, you will get the type of the data inside, not the type of the field itself. For example:In this example, when you call
GetType()
, you also will getSystem.Int32
, and notSystem.Object
. Because in your your start with different types before callingGetUnderylingType
, you end up with different results.Nullable types are a little bit weird. However, at least their behavior is well documented.
From the C# programming guide on MSDN, "How to: Identify Nullable Types"at http://msdn.microsoft.com/en-us/library/ms366789(VS.80).aspx :
It's worth pointing out that the distinction in your headline is inaccurate. The type behavior is not distinct based on local variables or properties, but on whether the type is accessed via a runtime object or by reflection (or use of the typeof operator). Your inference is understandable, since the types of local variables are usually only accessed by means of a runtime object, however, it is flawed, because if you access a nullable object at runtime via a property accessor then its behavior will be equivalent to that of a local variable.
Also, to answer the last part of your question explicitly: the only way to tell that test.value is nullable without using reflection would be to access it and get a NullReferenceException (which, of course, can only happen if test.value is null. As written, in your example, the value is not null, so determining this without reflection would be impossible
smartcaveman's answer is the best one here so far in that it actually identifies the section of the documentation that describes this behaviour.
The behaviour is undesirable and unfortunate; it is due to the behaviour in combination of three features which, by themselves, behave reasonably.
The three features are:
GetType
is a non-virtual method; it cannot be overridden. This should make sense; an object doesn't get to decide what its type is reported as. By making it non-virtual, the method is guaranteed to tell the truth.The
this
value passed to a non-virtual method declared onobject
must be converted toobject
; therefore in the case of objects of value type, the receiver of the call toGetType()
is boxed toobject
.Nullable value types have no boxed form; when you box a nullable int, you either get a boxed int or you get a null reference. You never get a valid reference to a boxed nullable int.
Each feature is reasonable on its own but in combination the result is undesirable: when you call
GetType
on a valid nullable int, the runtime boxes the nullable int to a boxed int and then passes that as thethis
ofobject.GetType
which of course reportsint
. If the value is a null nullable int, the runtime boxes to null and then invokesGetType
on a null reference and crashes. Neither of these behaviours are desirable, but we're stuck with them.Method GetNulllableUnderlyingType returns Underlying type for nullable types, for another types returns null;
The problem is that you're assuming that the
Type
oftest.value
is the same as theType
of thefield.FieldType
ofvalue
, and it's not. Getting theType
oftest.value
actually gets theType
of what is stored in the field, in this case a 0.Type t = test.value.GetType()
is the same (in your example) as
Type t = 0.GetType()
To demonstrate, initialize
value
to null andtest.value.GetType()
will throw aNullReferenceException