Nullable GetType Comparison
ID |
csharp.nullable_gettype_comparison |
Severity |
high |
Remediation Complexity |
trivial |
Remediation Risk |
low |
Remediation Effort |
low |
Resource |
Reliability |
Language |
CSharp |
Tags |
nullable, reflection, reliability, suspicious-comparison |
Description
Reports the runtime type returned by GetType() being compared with typeof of a nullable value
type, with == or != and in either operand order.
Three spellings of the nullable type are recognised: the language shorthand typeof(int?), the
constructed form typeof(Nullable<int>), and the open form typeof(Nullable<>).
A typeof(Nullable<…>) that is not compared with a GetType() call is ordinary reflection code and
is not reported.
Rationale
Nullable<T> never appears as a runtime type. GetType() is declared on object, so a nullable value
is boxed before the call, and boxing a nullable does one of two things: if it has a value, the box holds
the underlying value, so ((int?)1).GetType() is typeof(int); if it has none, boxing yields a null
reference and the call throws NullReferenceException.
The comparison therefore never holds. An == branch is dead code, an != branch is always taken, and
either way the expression may throw before it is even evaluated. Nothing in the source hints at the
cause — the test reads exactly like the correct one written against the underlying type.
using System;
public class TypeProbe
{
public bool IsNullableInt(object value)
{
return value.GetType() == typeof(int?); // FLAW
}
public bool IsNotNullableLong(object value)
{
return value.GetType() != typeof(long?); // FLAW
}
public bool Constructed(object value)
{
return value.GetType() == typeof(Nullable<decimal>); // FLAW
}
public bool IsInt(object value)
{
return value.GetType() == typeof(int); // OK, the underlying type
}
public bool IsAnyNullable(object value)
{
return Nullable.GetUnderlyingType(value.GetType()) != null; // OK
}
public Type OpenNullable()
{
return typeof(Nullable<>); // OK, not compared with GetType
}
}
Remediation
Decide which question the code is really asking.
To test whether a boxed value is an int, compare against the underlying type:
value.GetType() == typeof(int). A boxed int? that has a value satisfies this, which is what the
original test was reaching for.
To test whether a declared type is nullable, ask the reflection API rather than a runtime value:
Nullable.GetUnderlyingType(type) != null, where type comes from a field, property or parameter
declaration. That is the only place Nullable<T> survives as a type.
Guard the call as well when the receiver may be an empty nullable, or use a pattern match such as
value is int, which is null-safe and needs no boxing rules to be understood.