Can't add null to list of nullables [duplicate]
Possible Duplicate:
Adding null to a List<bool?> cast as an IList throwing an exception.
List<int?> listONullables = new List<int?>();
IList degenericed = listONullables;
// This works fine
listONullables.Add(null);
// Run time exception:
// "The value "" is not of type "System.Nullable`1[System.Int32]"
// and cannot be used in this generic collection. Parameter name: value"
degenericed.Add(null);
// Also does not work. Same exception
degenericed.Add((int?)null);
// Also does not work
// EDIT: I was mistaken, this does work
degenericed.Add((int?)1);
// Also does not work
// EDIT: I was mistaken, this does work
degenericed.Add(1);
See the comments in the above code.
I sort of understand the reasons for this (when you cast away the generics the runtime does the best it can with limited information). I'm just wondering if there's a way around this, even if it's a bit of a hack.
The problem sprang up when I tried having the generic version of a function use the same private implementation as a non generic version, so I can work around it if necessary (have two very similar implementations), but obviously it's better if I can figure t开发者_StackOverflowhis out.
EDIT: The last two entries I have above do NOT fail like I originally said. But the first two do. I've added comments to that effect in the code above.
To elaborate on the discussion in the comments, it seems that in List<T>.IList.Add
in 4.0, there is:
ThrowHelper.IfNullAndNullsAreIllegalThenThrow<T>(item, ExceptionArgument.item);
try
{
this.Add((T) item);
}
catch (InvalidCastException)
{
ThrowHelper.ThrowWrongValueTypeArgumentException(item, typeof(T));
}
And 2.0 has VerifyValueType which simply checks the IsCompatibleObject method:
VerifyValueType(item);
...
private static bool IsCompatibleObject(object value) {
if( (value is T) || ( value == null && !typeof(T).IsValueType) ) {
return true;
}
return false;
}
The latter is written in a simplistic fashion. value
is not T (because null is not the same as Nullable<int>.HasValue = false
). Also, as @LBushkin notes, typeof(T).IsValueType will return true for Nullable<int>
and so the right-hand-side also evaluates to false.
This is a bug in the 3.5 framework (and probably earlier versions too). The rest of this answer relates to .NET 3.5, although the comments suggest that the bug has been fixed in version 4 of the framework...
When you pass a value-type to the IList.Add
method it will be boxed as an object
due to the IList
interface being non-generic. The one exception to this rule are null
nullable types which are converted (not boxed) to plain null
.
The IList.Add
method on the List<T>
class checks that the type you're trying to add is actually a T
, but the compatibility check doesn't take null
nullable types into account:
When you pass null
, the compatibility check knows that your list is a List<int?>
and knows that int?
is a value-type, but -- here's the bug -- throws an error because it also "knows" that value-types cannot possibly be null
, ergo the null
that you passed cannot possibly be an int?
.
This works in .NET 4.0 with the introduction of Covariance and contravariance.
Since you are not in 4.0 (obviously due to the runtime error) you can work around it by passing default(int) to get a null value
UPDATE: Don't listen to me default(int) = 0 NOT null. I am retarded :(
This works for null:
degenericed.Add(default(int));
The add call works correctly for me though?
degenericed.Add(1);
try to change the line:
IList degenericed = listONullables;
by this:
IList<int?> degenericed = listONullables;
精彩评论