Skip to content

Instantly share code, notes, and snippets.

@davidfowl
Last active December 14, 2015 01:55
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save davidfowl/700097cca459c1e78c86 to your computer and use it in GitHub Desktop.
Save davidfowl/700097cca459c1e78c86 to your computer and use it in GitHub Desktop.
Typesafe params

In the new ASP.NET 5 stack dependency injection is pervasive. We let users wire up their dependencies in the Startup class:

public class Startup
{
    public void ConfigureServices(IServiceCollection services)
    {
        services.AddTransient<IFoo, Foo>();
    }
    
    public void Configure(IApplicationBuilder app, ILoggerFactory loggerFactory, IFoo foo)
    {
        loggerFactory.AddConsole();
        
        app.UseMvc();
    }
}

In the above snippet, the Configure method has a single required parameter, all other parameters are optional. Let's say we wanted to define an interface for this class so that we get type safety and the benefits of refactoring:

public interface IStartup
{
    void ConfigureServices(IServiceCollection services);
    void Configure(IApplicationBuilder app, object params[] args);
}

The problem with this is that the implemenation of the interface can't get ILoggerFactory and IFoo without casting from object.

Enter type safe params:

public interface IStartup
{
    void ConfigureServices(IServiceCollection services);
    void Configure(IApplicationBuilder app, any params[] args);
}
public class Startup : IStartup
{
    public void ConfigureServices(IServiceCollection services)
    {
    }
    
    public void Configure(IApplicationBuilder app, ILoggerFactory loggerFactory, IFoo environment)
    {
        loggerFactory.AddConsole();
        
        app.UseMvc();
    }
}

The compiler would generate:

public class Startup : IStartup
{
    public void ConfigureServices(IServiceCollection services)
    {
    }

    [CompilerGenerated]
    void IStartup.Configure(IApplicationBuilder app, params object[] args)
    {
        Configure(app, (ILoggerFactory)args[0], (IFoo)args[1]);
    }

    // User defined method
    public void Configure(IApplicationBuilder app, ILoggerFactory loggerFactory, IFoo environment)
    {
        loggerFactory.AddConsole();

        app.UseMvc();
    }
}

This only helps the user implementing the interface, the caller is likely still using reflection to call the user method.

@Garciat
Copy link

Garciat commented Jun 19, 2015

How would you handle competing overloads of Configure that match the interface method signature?

@moozzyk
Copy link

moozzyk commented Jun 19, 2015

Variadic templates...

@victorhurdugaci
Copy link

Use attributes to specify what schema you want to comply with. Similar to the import statement in Typescript. That way you don't need the compiler to remove the interface and you can change the contract without breaking existing apps. Adding a method to the interface would be breaking change

@mdekrey
Copy link

mdekrey commented Jul 6, 2015

👍 I highly approve, especially given the SO question @davidfowl linked. Reminds me of TypeScript's ...params: any[] usage in an interface, which is used with JavaScript DI frameworks, such as AngularJS's.

Would it work for delegate types? I would be adding this to my DI usage so fast...

delegate void InjectableAction(any params[] args);

interface IDependencyInjector 
{
    void Invoke(InjectableAction target);
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment