czwartek, 30 lipca 2009

C# generics – free but ugly Dependency Injection

I must admit that: I’m obsessed with generics for some time.

 

Some time ago my way to decouple code was to use IServiceProvider. It was great but required from me and my coworkers to pass it to each newly created class.

In the mean time other developers used different IoC containers which solved this problem by using static methods to register and use specific objects. For example:

IOC.Register<ISomething>(new Something());

 

And later in the code it was used like this:

 

IOC.Resolve<ISomething>().DoSomething();

As I said it solved one problem but I had still other problems with that:

  • Because of static methods I can have only one configuration for the whole application
  • Even if I tried to create my version of IoC container that can be nested like this

    using(new Service<IService>(new ServiceImplementation()))

    {

        //some code

        //and later

        using(new Service<IService>(new AnotherServiceImplementation()))

        {

            //some other code

        }

    }


    I failed to create a version that worked correctly between multiple threads, because I was forced to use static fields and I was unable to force CallContext to not overwrite current Thread configuration when EndInvoke() was called.
  • If I want to write component that may be used by someone else the only way to inform him about required dependencies is documentation, or runtime errors.
  • I must agree with Joel Spolsky that code that uses IoC containers is less readable

Aside from above I’m using static language C#. Why should my dependencies be resolved at runtime. I had never written application that required dynamic dependencies. All of them was known to me while I was writing my applications. Why not use compiler as my dependency resolver.

So, I want to write decoupled and reusable methods and classes.  This can be achived partially by parameter or property Dependency Injection.

Great, but after a while you come to the moment when you must create new object that will be passed to this method or property.

What now, how can I create instance of class without coupling to concrete type, and not using IServiceProvider or static IoC Container (or worse: use reflection). Then it hit me: we can make new() constraint on generic type. That way I can write my method/class in a way that it will create instances without knowledge of concrete type. We can create really decoupled/reusable code using  generics.

 

After few tests I also created short rule:

Every time you must call new() – do it on generic type. (It doesn’t matter if it’s method or class scope generic type)

 

There is one problem with this rule – I must use default constructor - which means – I must inject all already instantiated dependencies by properties or method parameters.

Last month I had small project to create application to manage users in database where data are encrypted. It was separate, small exe file – so I thought that I test my thoughts about generics. As example of decoupled code lets see fragment of my UserCreator class:

public class UserCreator<TRepository, TCertificateStore, TCertificate, TUserLoginQuestion, TPasswordGenerator> : IUserCreator<TRepository>

    where TRepository : IRepository

    where TCertificateStore : ICertificateStore<TCertificate>, new()

    where TCertificate : ICertificate

    where TUserLoginQuestion : IQuestion, new()

    where TPasswordGenerator : IPasswordGenerator, new()

{

    public TRepository Repository { get; set; }

 

    public void CreateFirstUser()

    {

        var store = new TCertificateStore();

        var adminCert = store.CreateAdministratorCertificate();

        store.SaveAdministratorCertificate();

        var login = new TUserLoginQuestion().Ask();

        var userCert = store.CreateUserCertificate(login);

        store.SaveUserCertificate(userCert);

        var userCertBytes = userCert.ToBytes();

        Repository.CreateNewUser(login, userCert.Encrypt(Encoding.ASCII.GetBytes(new TPasswordGenerator().Generate())), userCertBytes);

        Repository.CreateNewAdministrator(userCertBytes, adminCert.Sign(userCertBytes));

    }

}

Uhh, little ugly – but (almost) completly decoupled (Encoding.ASCII.GetBytes is not important dependency in that case). To make this class more usable I created also default version

public class UserCreator : UserCreator<RepositoryDataContext, CertificateStore, Certificate, AskUserEmailForm, PasswordGenerator>{}

As you can see I can change everything this class uses to whatever implementation I want. What blows my mind is – how such small method requires so much dependencies. Below you have example of usage:

public static void Main<TRepository, TAdministratorTable, TAdministrator, TUserCreator, TLogin, TApplication>(TRepository repository)

    where TRepository : IRepository, IAdministratorRepository<TAdministratorTable, TAdministrator>

    where TAdministratorTable : IQueryable<TAdministrator>, ITable

    where TAdministrator : IAdministrator, new()

    where TUserCreator : IUserCreator<TRepository>, new()

    where TLogin : ILogin, new()

    where TApplication : IApplication, new()

{

    try

    {

        if (repository.Administrators.Count() == 0)

            new TUserCreator{Repository = repository}.CreateFirstUser();

        var login = new TLogin();

        login.Login();

        if (login.UserAuthenticated)

            new TApplication().Start();

    }

    catch(CancelException)

    {}

}

In line

            new TUserCreator{Repository = repository}.CreateFirstUser();

you can see that I must pass repository as property. It will be great if I can make a constraint for constructor other than default. For example

    where TUserCreator : IUserCreator<TRepository>, new(TRepository)

but right now I must live with a tools I have (c# 3.0). I don’t have strong opinion which is better default constructor + properties or specific constructor.

 

I was afraid that when my codebase will grow the list of dependencies will grow, especially in Main() method. What I observed instead of this was that each method require max 6-10 dependencies. Not so scary.

 

OK time for some summary:
Pros:

  • Completly decoupled/reusable code
  • Completly testable code
  • Best performance possible (no boxing for value types)
  • Free – you already have all tools – I mean you already have one tool – compiler :-)
  • All dependencies are resolved by compiler
  • User of your code knows all dependencies at compile time (using only intellisense)
  • Because dependencies are static, they will not change in other thread (until you say so)

Cons:

  • Little ugly method/class definition (I’m sure that better type inference would help here, but I’m not sure how much better is possible)
  • Only constraint for default constructor is possible
  • No possibility to use static methods (MS please add static virtual methods)

If anyone isintrested I’ll try to write in short time how I test such generic methods/classes. Especially how I test newly created instances. All I can say right now is that I use t4 script and Rhino Mocks.

Brak komentarzy: