Partially closed generic types

If you swallow enough of the generic pills, you may run into situations where a not-quite closed and a not-quite open generic type would be nice.  It’s in situations where decisions based on types are prevalent, such as in IoC containers.  An open generic type is simply a generic type whose type parameters have not been specified.  For example, IEnumerable<> is an open generic type, and IEnumerable<int> (or string or whatever) is a closed generic type, as its type parameter has been specified.

Recently I ran into a situation where I wanted my cake and eat it too, with a partially closed generic type.  First, I had a generic type I wanted to work with:

public interface IRequestHandler<TMessage>
{
    void Handle(TMessage message);
}

Nothing too exciting here, except I wanted to match up, using an IoC container, TMessage to handlers of that message (IRequestHandler<TMessage>).  Modern IoC containers provide easy ways of doing this, so something like StructureMap is something along the lines of:

cfg.ConnectImplementationsToTypesClosing(typeof(IRequestHandler<>));

This one line of code connects all implementations of IRequestHandler<> to the closed interfaces.  For example, the closed implementation AddCustomerHandler gets connected to IRequestHandler<AddCustomer> (assuming the class implements that interface).  This was working for me great, until I ran into a little more advanced scenario – messages to delete entities.  I only had one implementation of that, and the message itself was generic:

public interface IDeleteMessage<TEntity>
    where TEntity : Entity
{
    TEntity Entity { get; }
}

The handler could handle any delete message, by supplying an additional generic parameter:

public class DeleteRequestHandler<TEntity> : IRequestHandler<IDeleteMessage<TEntity>>
    where TEntity : Entity
{
    private readonly IRepository<TEntity> _repository;

    public DeleteRequestHandler(IRepository<TEntity> repository)
    {
        _repository = repository;
    }

    public void Handle(IDeleteMessage<TEntity> message)
    {
        // delete the message.Entity
    }
}

So this DeleteRequestHandler could handle all sorts of delete messages, so long as it was an IDeleteMessage<TEntity>.  It could be IDeleteMessage<Customer> or IDeleteMessage<Order>, and so on.  This proved to be a problem with StructureMap, since there was no concrete implementation defined for a delete message.  Instead, the open DeleteRequestHandler<> type needs to be closed for a specific entity type before I can use an implementation.

Partially closed weirdness

This led me to want to do this:

[Test]
public void Wont_compile()
{
    var type = typeof (IRequestHandler<IDeleteMessage<>>);
}

However, I got a compile error of:

error CS1031: Type expected

So the compiler does not let me close a generic type with another open generic type.  I want to define the type “IRequestHandler of IDeleteMessage of IDontCareRightNowJustYet”, maybe I can do this with type objects directly?  Here’s what I had in that vein:

[Test]
public void Compiles_but_useless()
{
    var openRequestType = typeof (IRequestHandler<>);
    var openMessageType = typeof (IDeleteMessage<>);

    var partiallyClosedType = openRequestType.MakeGenericType(openMessageType);

    Debug.WriteLine(partiallyClosedType);
}

The really interesting output is the name of the type:

PartiallyClosedGenerics.IRequestHandler`1[PartiallyClosedGenerics.IDeleteMessage`1[TEntity]]

It looks like a partially closed type, as you can see that IRequestHandler is closed type, but closed with an open generic type.  Unfortunately, you can’t actually do anything with this type, as Type.IsGenericTypeDefinition is false, meaning I can’t create a completely closed type.  Calling MakeGenericType with something like typeof(Customer) doesn’t work, and you can’t really poke around the generic type parameter to try and coerce it.  So although you can create partially closed generic Type objects, you can’t do anything with them.  Activator.CreateInstance fails, for example.

This is because a partially closed type is a fully open type.  If a type contains any unassigned type parameters, anywhere in its hierarchy, the type is an open constructed type.  Unfortunately, MakeGenericType only works with types that are generic type definitions, so I’ve created a descriptive, but useless type.  More information than I ever, ever cared to know about generics, but good to know that the CLR team really covered their bases when they implemented generics.

Related Articles:

Post Footer automatically generated by Add Post Footer Plugin for wordpress.

About Jimmy Bogard

I'm a technical architect with Headspring in Austin, TX. I focus on DDD, distributed systems, and any other acronym-centric design/architecture/methodology. I created AutoMapper and am a co-author of the ASP.NET MVC in Action books.
This entry was posted in C#. Bookmark the permalink. Follow any comments here with the RSS feed for this post.
  • http://www.chaindrug.com Gilligan

    I feel your pain. I ran into a similar issue a few months ago where I wanted to get something from the IoC container only if it met the generic constraints. I ended up writing a naming subsystem in Castle Windsor that checks the generic constraints to solve it, but I felt dirty the whole time, like I was tackling the problem the wrong way.

  • Arielr

    And if Gilligan was the ghost of generics past – I’m the ghost of generics future.
    I read your post when it came out and giggled a bit about your misfortune, but sure enough – Karma has now made this my problem too. :)

  • http://camsioe.freewhost.com/sitemap.html Globals

    all good things

  • Steven

    In your case I would say it’s time to switch to a different IoC container. That is, one that natively supports these kinds of constructs. Simple Injector allows handling any generic type: open, closed, partially closed, and with any nasty generic type constraint. And allows applying decorators conditionally based on generic type constraints. The reason Simple Injector supports this, is because it is actually very useful to work with partially open-generic types and complex type constraints.