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 (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).  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 (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.  It could be IDeleteMessage or IDeleteMessage, 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.

My favorite NHibernate exception