I ran into a very strange problem tonight. Here's my scenario:
- I have a value which comes in as a string (from a URL parameter)
- I need to convert this string into a Nullable<T> where I don't know T at compile time (but I do at runtime).
This first thing I tried was the direct approach: call Convert.ChangeType() to convert directly into the Nullable<T>. That fails, with convert throwing an exception that it doesn't know how to convert from string to Nullable<T>. This felt kinda strange, since Convert.ChangeType() works just fine when I pass T; you'd think that it would be easy to convert T into Nullable<T>.
Strike one.
Okay, I think, let's leverage Convert.ChangeType() to get it into T, and then use Activator.CreateInstance(). The constructor for Nullable<T> takes a T for its value. Here's what the code essentially looks like, in a nicely compact system:
using System; public class MainClass { public static void Main() { string original = "1"; PrintValueAndType("original", original); object converted = Convert.ChangeType(original, typeof(int)); PrintValueAndType("converted", converted); Type type = typeof(Nullable<>).MakeGenericType(typeof(int)); object result = Activator.CreateInstance(type, converted); PrintValueAndType("result", result); } static void PrintValueAndType(string name, object obj) { Console.WriteLine("{0} = {1} ({2})", name, obj, obj.GetType().FullName); } }
This code prints out the values at all three stages, including the type. What do you expect to see when you run this? This is what you get:
original = 1 (System.String)
converted = 1 (System.Int32)
result = 1 (System.Int32)
I'd expected the result type to be Nullable<T>, not T.
Strike two.
Thinking maybe Activator was the problem, I resorted to reflection. I called GetConstructor() on the nullable type, and then called Invoke on the resulting ConstructorInfo. Same result.
Strike three. Time for a blog post.
What exactly is going on here?
I'm going to have to make some educated guesses, but this was the clue. This is my updated code:
using System; public class MainClass { public static void Main() { string original = "1"; PrintValueAndType("original", original); object converted = Convert.ChangeType(original, typeof(int)); PrintValueAndType("converted", converted); Type type = typeof(Nullable<>).MakeGenericType(typeof(int)); object result = Activator.CreateInstance(type, converted); PrintValueAndType("result", result); Nullable<int> x = 1; PrintValueAndType("x", x); } static void PrintValueAndType<T>(string name, T obj) { Console.WriteLine("{0} = {1} ({2}, {3})", name, obj, obj.GetType().FullName, typeof(T).FullName); } }
Now when I run it, this is what I get:
original = 1 (System.String, System.String)
converted = 1 (System.Int32, System.Object)
result = 1 (System.Int32, System.Object)
x = 1 (System.Int32, System.Nullable`1[[System.Int32, ...
With the generic version of the method, you'd expect obj.GetType() and typeof(T) would be the same thing, but they're not. I suspect the reason is that obj gets boxed for the call to GetType(), and the CLR's behavior when boxing a Nullable<T> is to box the value as type T, throwing away its nullability.
What makes this maddening is that, if you don't know the T ahead of time, there's essentially no way to actually store Nullable<T>. Since the result of both Activator.CreateInstance() and the ConstructorInfo.InvokeCall is object, even though you actually had a Nullable<T> for a very short time, it immediately got boxed and the nullability was thrown away.
Oops, strike four. Now what!?
I found a work-around for my specific case, but I think I'm still stuck with the notion that programmatically creating Nullable<T> for an arbitrary T is just not possible.
In my case, I was programmatically modifying a LINQ expression, adding a Where clause. In essence, I'm writing code to do this:
query = query.Where<T>(x => x.PropertyName == value);
when I don't know T, PropertyName or value until runtime. (For what it's worth, modifying LINQ queries like this feels a lot like using CodeDOM to generate code at runtime, but less brain bending.)
When you are putting together the where expression programmatically, you use System.Linq.Expression.Constant() to generate the "value" part of the expression. I had been using this:
var valueExpr = Expression.Constant(value);
Luckily, there is an overload which takes the type that you want the constant to be:
var valueExpr = Expression.Constant(value, myNullableType);
Even though Convert.ChangeType() can't change T into Nullable<T>, LINQ constant expressions don't appear to have any problems doing it.
I like to have blog posts that end in answers rather than questions, but unfortunately I'm still a little bit stumped by this behavior and how to overcome it. I was lucky to have found an appropriate work-around for LINQ expressions, but I could still be stuck if I hadn't.
Is it possible that you simply can't create Nullable<T> programmatically for an arbitrary T? Is Nullable<T> a Heisenberg-Type, where observing it changes it fundamentally?