Monday, July 27, 2009

Generic method type parameter as Type parameter

Generics is another mechanism offered by the common language runtime (CLR) and programming languages that provides one more form of code re-use: algorithm re-use.
Jeffrey Richter, CLR via C# 2nd

However algorithm abstraction from data types it operates on is not the only usage scenario. Type parameter of a generic method can be treated as a parameter of Type. Consider the following example

// T IUnityContainer.Resolve<T>()
IService service = unityContainer.Resolve<IService>();

Unity container implementation uses supplied type parameter to resolve an instance of requested type from the container. It is beneficial as it allows us to read code naturally and it avoids unnecessary type casts.

It is quite often used when building fluent interfaces (in particular Method Chaining). Consider autofac container configuration example:

var builder = new ContainerBuilder();
builder.Register<Straight6TwinTurbo>().As<IEngine>().FactoryScoped();

The configuration can be read as a natural language sentence.

However it is only half of the truth. There are several scenarios where this approach doesn’t work so well.

Not all languages support generics, others may have syntax that makes them hard to read. For example the same thing written in VB.NET doesn’t have the same readability as in C#:

Builder.Register(Of Straight6TwinTurbo)().As(Of IEngine)().FactoryScoped();

On the other hand it is beneficial when type parameter is known at compile time and it may complicate more dynamic scenarios when it is deferred until runtime.

For example, Unity container has an Extension called StaticFactoryExtension. The purpose of this extension is to add the ability to register types within the container while deferring the instantiation of the type to a factory method. The extension is configured through IStaticFactoryConfiguration:

public interface IStaticFactoryConfiguration : IUnityContainerExtensionConfigurator
{
    IStaticFactoryConfiguration RegisterFactory<TTypeToBuild>(FactoryDelegate factoryMethod);
    IStaticFactoryConfiguration RegisterFactory<TTypeToBuild>(string name, FactoryDelegate factoryMethod);
}

It is used like this:

container.AddNewExtension<StaticFactoryExtension>();
IStaticFactoryConfiguration config = container.Configure<IStaticFactoryConfiguration>();
config.RegisterFactory<IProductService>(ProductServiceFactory.Create);

Note however that the factory registration API includes only generic methods. It makes harder to implement scenarios were calling code wants to enumerate types at runtime and register particular factory for them.

In order to solve this problem the API should provide non generic version in addition to generic one. For example, Unity container provides both (generic and non-generic) versions for Resolve method:

public interface IUnityContainer
{
    T Resolve<T>();
    object Resolve(Type t);

    //... other members elided for clarity
}

Summary:

  • CONSIDER audience of your API
  • CONSIDER providing non generic version of your API taking into account your audience and usage scenarios

1 comment:

Ihar Voitka said...

Good point regarding scenarios when decision is made at runtime.