I'm creating a system that turns a small script into a dll. I'm encountering a problem when I try to take a nullable value class and make it the default value of a parameter. The problem is that I need to create an instance of user selected nullable within the compiler and set it as the constant.
Unfortunately, whenever I use Activator.CreateInstance(NullableType)
(where NullableType is a created type from user input) I get null
as the return value. For example, simply executing:
object Test = Activator.CreateInstance(typeof(Nullable<int>));
Console.WriteLine(Test == null);
returns true. This ONLY happens with Nullables. A struct created in C#, even a generic one, is created fine. Heck, if I copy & paste nullable from DotNetReflector (and remove TypeDependencyAttribute, TargetPatchingOptOutAttribute, and ThrowHelper calls since I don't have access to those), it shows False above.
What is going on? Is there any other possible way to create a nullable when I don't know the generic parameters until run-time?
From this MSDN blog:
Activator.CreateInstance could never be expected to return null before; with this DCR, it will return null when creating instance of type Nullable but not providing non-null T value. For example, Activator.CreateInstance(typeof(Int32?)) returns null.
The problem has to do with how variables of Nullable<T>
are boxed as explained by the blog post.
The problem is in ParameterBuilder.SetConstant
, not Activator.CreateInstance
. SetConstant
is a function for defining the default value of an optional parameter, and requires a concrete value be provided as the constant. For ref classes, null
is a valid concrete value, but for value classes prior to the creation of Nullable<>
, null
was not a valid value. How do you unbox null
into an int
, for example? So SetConstant
checked value types to see if the concrete value passed in as the constant was null
and throws an ArgumentException
as a more descriptive error than the NullReferenceException
you'd get for unboxing a null into a value class.
In the case of a Nullable<>
, null
is now a valid concrete value for a value class. Nullable<>
itself is a value class, as seen with Activator.CreateInstance
, and unboxing null
into a Nullable<int>
has meaning. This is where SetConstant
has a bug: it fails to take this into account and throws a 'descriptive' error for what isn't actually an error. In lieu of a bugfix from Microsoft, any call to SetConstant
with a null Nullable<>
will have to implement the behavior that's guarded by the incorrect conditional. This means digging into ParameterBuilder
's private methods and fields using reflection. Here's the code I made to handle just this case. The standard SetConstant
function should be used in situations that don't manifest the bug.
//ModuleBuilder module : a parameter passed into the containing function, the module which is being built (which contains the method that has the optional parameter we are setting the constant for)
//ParameterBuilder optParam : A parameter passed into the containing function, the parameter that is being built which is to have the null default value.
MethodInfo method = typeof(TypeBuilder).GetMethods(BindingFlags.Static | BindingFlags.NonPublic)
.Where(m => m.Name == "SetConstantValue" && m.GetParameters().Length > 0 && m.GetParameters()[0].ParameterType.Name == "RuntimeModule")
.Single();
var RuntimeHandle = typeof(ModuleBuilder).GetMethod("GetNativeHandle", BindingFlags.NonPublic | BindingFlags.Instance);
method.Invoke(null, new object[]
{
RuntimeHandle.Invoke(module, new object[]{}),
optParam.GetToken().Token,
0x12,
null
});
I have reported the bug to Microsoft. They responded that it wouldn't be fixed in .NET 3.5, but it was added to the internal bug database.
UPDATE:
The bug has been fixed in .NET 4.0. ParameterBuilder.SetConstant
now has a branch to the constant == null
conditional that checks to see if a value type is a generic derived from Nullable<>
and only throws the exception if it is not.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With