Alex Green
Alex Green

Reputation: 305

Static generic builder

I'm trying to implement Asp Net Core-like static builder for my class. Here's what I'm talking about. In Asp Net Core application in Startup method BuildWebHost class WebHost creates IWebHostBuilder:

public static IWebHost BuildWebHost(string[] args) =>
        WebHost.CreateDefaultBuilder(args)
            .UseStartup<Startup>()
            .Build();

I'm interested in UseStartup<Startup>() method, so I want to know about how does this method 'understands' what Startup class it must use? The signature for this method is:

public static IWebHostBuilder UseStartup<TStartup>(this IWebHostBuilder hostBuilder) 
where TStartup : class;

In my application I want to use the similar approach, for example here I have a simple empty interface ICleaner, which is implemented by some Cleaner classes, and then I have CleanerBuilder class, which is responsible for adding appropriate Cleaner classes to a List, configuring them and runs them all after the Build method. Here's my CleanerBuilder class. Here's my CleanerBuilder class:

public class CleanerBuilder
    {
        private List<ICleaner> _activeCleaners { get; set; }

        private CleanerBuilder() { }

        public static CleanerBuilder CreateDefaultBuilder(string[] args)
        {
            var cleanerBuilder = new CleanerBuilder();
            /*
             * ...
             */
            return cleanerBuilder;
        }

        public CleanerBuilder UseCleaner<ICleaner>()
        {
            _activeCleaners.Add(???);

            return this;
        }

        public void Build()
        {
            foreach(var cleaner in _activeCleaners)
            {
                cleaner.Run();
            }
        }
    }

And the usage is:

CleanerBuilder.CreateDefaultBuilder(args)
    .UseCleaner<TempCleaner>()
    .Build();

Right now I don't know what to do in UseCleaner method.

Upvotes: 1

Views: 1247

Answers (3)

GeirGrusom
GeirGrusom

Reputation: 1019

What you're trying to achieve is the builder pattern. The job of a builder is to gather dependencies, requirements and parameters for building a type.

The UseCleaner wouldn't normally, or necessarily do much except just store the type of ICleaner in a list for instantiation later on, likely as a result of the user calling the Build method. The simplest here is to require that ICleaner 's have a parameterless constructor.

private readonly List<Type> _activeCleaners;

public CleanerBuilder UseCleaner<ICleaner>()
   where ICleaner : class, new()
   // We now require that ICleaners has a parameterless constructor
{
    _activeCleaners.Add(typeof(ICleaner));
    return this;
}

public Cleaner Build()
{
   // Lazily create instances of the cleaners
   var cleaners = _activeCleaners.Select(Activator.CreateInstance);
   // Pass enumerator for instantiated cleaners to the object
   return new Cleaner(cleaners);
}

You can use Activator.CreateInstance with parameters as well, or you could use a dependency injection container if you don't want to require a parameterless constructor.

Upvotes: 1

Simply Ged
Simply Ged

Reputation: 8642

The WebHostBuilder class use reflection to create an instance of the startup class. If you want to do something similar then your List implementation needs to store a Type

private List<Type> _activeCleaners { get; set; }

Then in your Add method you could use:

public CleanerBuilder UseCleaner<TCleaner>() where TCleaner : ICleaner, class, new
{
    _activeCleaners.Add(typeof(TCleaner));

    return this;
}

But in this scenario you would have to research how to create the ICleaner instance when you require it. I think, in this case, nvoigt's answer might be better for you.

Upvotes: 1

nvoigt
nvoigt

Reputation: 77304

The simplest implementation would probably be this:

    public CleanerBuilder UseCleaner<TCleaner>() where TCleaner : ICleaner, class, new
    {
        _activeCleaners.Add(new TCleaner());

        return this;
    }

However, there is really nothing you gain here over:

public CleanerBuilder UseCleaner(ICleaner cleaner)
{
    _activeCleaners.Add(cleaner);

    return this;
}

The generic version is more restricted in this simple version, because it has to have an empty constructor. Not cool, maybe it hasn't. What Microsoft does with it's StartUp is to connect it to it's dependency injection container and try to construct it from there. That solves the problem of constructor parameters.

If you have Visual Studio configured to download sources, just press F12 on the UseStartup method to see what they do in there. Alternatively, you can look it up in the source.

Upvotes: 1

Related Questions