❀°。Deijin's Blog

The Solution to the Parameter Problem

A few years ago I made a post titled Dependency Injection: The Parameter Problem explaining the difficulty of dealing with variable parameters in dependency injection.

Last october I was once again conversing with my friend, lamenting the fact that the general advice is to not use parameter overrides, but without them you have to manually manage a factory and resolve everything, keeping it up-to-date whenever you change arguments or services. I say:

It just makes me sad that there really isn't a way to have your cake and eat it

It was at that moment when it hit me. What if we use a combination of attributes and code-generation to create the factories for you.

My original draft written clunkily in a message:

class MyClass
{
  public MyClass(
  [Param] string name,
  IService service)
}

// And it generates you a class factory:

class MyClassFactory(IService service)
{
  public MyClass Create(string name) {
  return new MyClass(name, service);
} 

To which my friend replied:

Why would you suggest this near bed time. I'm not gonna sleep now

A Most Elegant Solution

This solution is stunning in its simplicity and elegance. The sponge is that to make a factory, all you have to do is markup your class with a couple of attributes and immediately you've got a factory created. The icing is that whenever you change the parameters the factory updates without you needing to do anything. And the cherry on top is that due to being simple C# classes, you can easily register them for dependency injection using literally any container you like. Prism, DryIoc, Unity, Autofac, Ninject, it's truly universal.

But how do you actually do code generation?

I have to give a big thanks to Andrew Lock for his great articles going into detail on how to set up source generators. It's not obvious, but thanks to his articles I soon had a working prototype.

On particular issue I ran into was cross contamination of marker attributes, for which he already had an article explaining, he really is 10 steps ahead.

Hello FactoryGenie

After testing and solving the marker attribute issue, I'm finally happy enough with it to release it as a nuget. You can get it and use in your projects right now:

Here's how the final usage looks:

using FactoryGenie;

[GenerateFactory]
public class MyClass
{
    public MyClass(string stringParam, [FactoryParam] bool boolParam, double doubleParam)
    {

    }

    public MyClass(int intParam, string stringParam, [FactoryParam] float floatParam)
    {

    }
}

And here's what a factory it creates looks like:

public class MyClassFactory
{
    private readonly string stringParam;
    private readonly double doubleParam;
    private readonly int intParam;

    public MyClassFactory(string stringParam, double doubleParam, int intParam)
    {
        this.stringParam = stringParam;
        this.doubleParam = doubleParam;
        this.intParam = intParam;
    }

    public MyClass Create(bool boolParam)
    {
        return new MyClass(stringParam, boolParam, doubleParam);
    }

    public MyClass Create(float floatParam)
    {
        return new MyClass(intParam, stringParam, floatParam);
    }
}

In typical programmer fasion, the name took about as long to come up with than the implementation. He grants your wish to create a factory, and genie contains "gen" as in "codegen". Very amusing.

It's still early days, and there's already many edge cases I can think of, but it's working great for everything I've needed so far. I'm gonna continue using it and triage the most important things to make it more robust, but with the goal of keeping it as simple as possible.

Happy coding!

#csharp #dotnet #programming