Nullable properties vs. Nullable local variables

2019-04-28 16:15发布

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?

5条回答
萌系小妹纸
2楼-- · 2019-04-28 16:30

First of all, Nullable.GetUnderlyingType(test.value) in your example will not compile. Or typeof(test.value) as I have seen in comments.

Lets answer this by the following modified code:

TestClass test = new TestClass();
Type type1 = test.value.GetType(); // Gets the type of the data inside the field ==> System.Int32.
Type underlyingType1 = Nullable.GetUnderlyingType(type1); // Gets the underlying type of System.Int32 ==> null.

FieldInfo field = typeof(TestClass).GetFields(BindingFlags.Instance | BindingFlags.Public)[0];
Type type2 = field.FieldType; // Now the type is retrieved of the field, not the data inside. ==> System.Int32?
Type underlyingType2 = Nullable.GetUnderlyingType(type2); // Gets the underlying type of System.Int32? ==> System.Int32.

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:

object o = 1;
Type type = o.GetType();

In this example, when you call GetType(), you also will get System.Int32, and not System.Object. Because in your your start with different types before calling GetUnderylingType, you end up with different results.

查看更多
手持菜刀,她持情操
3楼-- · 2019-04-28 16:39

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 :

You can also use the classes and methods of the System.Reflection namespace to generate Type objects that represent Nullable types. However, if you attempt to obtain type information from Nullable variables at runtime using the GetType method or the is operator, the result is a Type object that represents the underlying type, not the Nullable type itself. 

Calling GetType on a Nullable type causes a boxing operation to be performed when the type is implicitly converted to Object. Therefore GetType always returns a Type object that represents the underlying type, not the Nullable type.

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

查看更多
三岁会撩人
4楼-- · 2019-04-28 16:47

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 on object must be converted to object; therefore in the case of objects of value type, the receiver of the call to GetType() is boxed to object.

  • 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 the this of object.GetType which of course reports int. If the value is a null nullable int, the runtime boxes to null and then invokes GetType on a null reference and crashes. Neither of these behaviours are desirable, but we're stuck with them.

查看更多
相关推荐>>
5楼-- · 2019-04-28 16:52

Method GetNulllableUnderlyingType returns Underlying type for nullable types, for another types returns null;

class Program
{
    static void Main(string[] args)
    {
        Console.WriteLine(GetNulllableUnderlyingType(new TestClass().value));
        Console.WriteLine(GetNulllableUnderlyingType(new TestClass()));
        Console.ReadKey();
    }

    public class TestClass
    {
        public int? value;
    }

    public static Type GetNulllableUnderlyingType<T>(T value)
    {
        Type result =  Nullable.GetUnderlyingType(typeof (T));
        return result;
    }
}
查看更多
Lonely孤独者°
6楼-- · 2019-04-28 16:53

The problem is that you're assuming that the Type of test.value is the same as the Type of the field.FieldType of value, and it's not. Getting the Type of test.value actually gets the Type 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 and test.value.GetType() will throw a NullReferenceException

查看更多
登录 后发表回答