可以将文章内容翻译成中文,广告屏蔽插件可能会导致该功能失效(如失效,请关闭广告屏蔽插件后再试):
问题:
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?
回答1:
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
回答2:
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.
回答3:
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
回答4:
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;
}
}
回答5:
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.