views:

184

answers:

2

StructureMap doesn't like passing in Nullable types as constructor arguments. Is there a reason for this? Is there a way to get this to work?

[TestMethod]
public void Demo()
{
    ObjectFactory.Initialize(x => x.ForRequestedType<TestClass>()
            .TheDefault.Is.OfConcreteType<TestClass>()
            .WithCtorArg("param1").EqualTo((byte?)3));

//This fails, but works if it's non-nullable
    var result = ObjectFactory.GetInstance<TestClass>();
}

public class TestClass
{
    public TestClass(byte? param1)
    { }
}
A: 

I found this code in the StructureMap source. Looks like it's not including nullable types.

protected internal bool IsSimple(Type type)
{
    return type.IsPrimitive || IsString(type) || IsEnum(type);
}
Jason Young
Turns out this code is not responsible, but I still don't know why this isn't supported.
Jason Young
+3  A: 

The underlying issue is that there's no difference, from the CLR's perspective, between a boxed (converted to Object) instance of a nullable type, and an (unboxed) instance of the equivalent non-nullable type. Similarly, when you call GetType() on a nullable type like int?, the Type returned is indistinguishable from a regular int. See http://msdn.microsoft.com/en-us/library/ms366789.aspx more info about this.

This behavior is a recipe for disaster for code like StructureMap which interrogates types using GetType() on an Object-typed parameter. Since StructureMap doesn't know whether your byte? is actually nullable, when StructureMap code-gens the constructor call, it code-gens it as a regular byte, which bombs at runtime since StructureMap is passing the wrong type into the constructor call.

It'd be possible for StructureMap to work around this, but the changes would be non-trivial. I tried a few tweaks to the StructureMap source code (e.g. changing from using Object and GetType() to instead using generic methods which accepted a generic parameter type, which could then be interrogated to see if it was a nullable type or not. But there were more changes required (including, AFAIK, in the IL generation required to make the constructor call) and so I gave up.

You might want to bring this up with the Structure Map team itself who knows the code best. The StructureMap Google Group is a reasonable place to start. Note that your question has been asked before (see end of this post) so I'm not sure how responsive the Google Group is.

But, barring a fix in StructureMap itself, if I were you I'd consider wrapping your class in a simple wrapper which removes the need for a nullable parameter in the constructor.

Or if you're feeling brave, you can try to fix this by getting very familiar with the StructureMap source code. :-)

BTW, here's one example where the issue occurs in the StructureMap source:

    /// <summary>
    /// Sets the value of the constructor argument
    /// </summary>
    /// <param name="propertyValue"></param>
    /// <returns></returns>
    public T EqualTo(object propertyValue)
    {
        if(propertyValue.GetType().IsSimple())
            _instance.SetProperty(_propertyName, propertyValue.ToString());
        else
        {
            _instance.SetChild(_propertyName,new LiteralInstance(propertyValue));
        }
        return (T) _instance;
    }

By converting the propertyValue argument to an object, it's impossible for the method to know that it was a nullable type, since byte? and byte are indistinguishable once converted to Object.

Justin Grant