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.

@wcabus
Copy link

wcabus commented Jun 19, 2015

I think it's a great idea, I would use the var keyword instead of any though. Just because that's currently being used for type-safe variables which the compiler figures out the type for.

@smudge202
Copy link

But placing such power in the wrong hands... imagine the kind of mess people would get into with such a construct.

I love that the new OWIN practices don't force the user into writing any service location, and totally understand why an interface wasn't possible. Is it really that confusing for people to work by convention as opposed to an interface? If it is, should we be empowering them with tools like your any keyword?

@flq
Copy link

flq commented Jun 19, 2015

  • It's a pretty specific use case - What more uses could one think of?
  • How could you ensure that the Configure is actually an implementation of the interface definition?

@Indomitable
Copy link

Or maybe triple point style from C++ is good idea too. void Configure(IApplicationBuilder app, ...);

@andersborum
Copy link

Sounds like a good feature. I'm wondering if this approach could solve the lack of covariant return types as well?

@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