Polymorphic DI framework or setup for .NET?

The situation that I have is the following and albeit quite simple, it seems not to be supported (at least out of the box) for 2 DI frameworks that I tried.

public interface IPlugin
{
    // misc
}

public class ConcretePlugin : IPlugin //more interfaces follow
{
    //implementation
}

class TestConcrete
{
    private ConcretePlugin plugin;

    public TestConcrete(ConcretePlugin plugin)
    {
        this.plugin = plugin;
    }
}

class TestList
{
    private IEnumerable<IPlugin> plugins;

    public TestList(IEnumerable<IPlugin> plugins)
    {
        this.plugins = plugins;
    }
}

Below I am trying to compose the objects as follows:

//literally, using Microsoft.Extensions.DependencyInjection;

ServiceCollection sc = new();
sc.AddSingleton(new ConcretePlugin(/*some arguments here*/)); // [1]
sc.AddSingleton<TestConcrete>();
sc.AddSingleton<TestList>();
ServiceProvider provider = sc.BuildServiceProvider();
provider.GetService<TestList>();

The above calls the TestList constructor with an empty IEnumerable. If I change [1] to sc.AddSingleton<IPlugin>(new ConcretePlugin(/*some arguments here*/)); then TestConcrete can’t be instantiated.

I could register for every super type:

var plugin = new ConcretePlugin(/*some arguments here*/);
sc.AddSingleton<ConcretePlugin>(plugin);
sc.AddSingleton<IPlugin>(plugin);
...

But it defeats the purpose of using a DI framework and also I have multiple interfaces implemented that I want to inject in different collections. That’s cumbersome and I’d like to avoid it as every time I add an interface implementation I’ll have to remember to register it to the DI.

It seems quite a straightforward use case to me. I’d guess I’m missing some configuration? Note that I tried the same setup with Simple Injector but the result was the same.

Addendum

Apparently this question created a lot of confusion and I will try to address most of the issues. That’s a pity given the fact that the concept is dead simple -IMHO.

First of all let’s see “why” and let’s examine the following example:

var plugin = new ConcretePlugin(/*some arguments here*/);
var new TestConcrete(plugin);
var new TestInterface(plugin); // accepts only a single IPlugin for simplicity

In the above example of pure DI, I don’t have to explicitly tell the type system that plugin is of type IPlugin. var new TestList((IPlugin)plugin); is redundant.

If I try the same thing with a DI framework, I find myself having the following imaginary (and simplified) dialog with system:

- Register an instance of ConcretePlugin.
- Duly received.
- Now register a TestConcrete, resolve the arguments.
- Done as well.
- Now register a TestInterface, resolve the arguments.
- Sorry, can't do: there is no instance of IPlugin to use.

But the instance is there. DI frameworks were meant to help us doing DI, not change the way we reason with it. The above example of pure DI is a valid example of polymorphism, hence the tag (that I had to reintroduce BTW).

Yes but how should it work?

Well that’s something that should be black-boxed, but in any case there are possible solutions. I’d expect the DI framework to lazily look if any of the registrations are subtypes of a requested unregistered type. “But that will have a performance impact” I hear you already protesting. Granted, I’m willing to measure and possibly pay the toll. By being lazy the container avoids registration of every parent interface or class.

what would happen if TestList implemented IList<Something>

I guess that what Panagiotis means here is that TestList could implement IPlugin and create a circular reference. Well the solution is simple: you can’t pass the object to itself. TestList cantDoThis = new (new IPluginp[] { cantDoThis } ); is wrong – besides being pointless. Why should it be considered when using DI?

6

I can suggest registering only ConcretePlugin, then use it to resolve eveything that might depend on it.

For example:

sc.AddSingleton<ConcretePlugin>();
// Resolve IPlugin using already registered instance.
sc.AddSingleton<IPlugin>(sp => sp.GetRequiredService<ConcretePlugin>());

Update

In order to register type as itself and as interface implementation for each implemented interface (and return one single object all the time), you can use such method:

public static class ServiceExtensions
{
    public static IServiceCollection AddTypeAsItselfAndInterfaces<T>(this IServiceCollection services)
        where T : class
    {
        var implementedInterfaces = typeof(T).GetInterfaces();
        services.AddSingleton<T>();
        foreach (var implementedInterface in implementedInterfaces)
        {
            services.AddSingleton(implementedInterface, sp => sp.GetRequiredService(typeof(T)));
        }
        return services;
    }
}

9

@MichałTurczyn already gave the answer for MS.DI, so I won’t repeat that. Here’s how to do it with Simple Injector:

var container = new Container();

var concrete = new ConcretePlugin(/*some arguments here*/);
container.Collection.AppendInstance<IPlugin>(concrete);
container.RegisterInstance(concrete);
container.Register<TestConcrete>(Lifestyle.Singleton);
container.Register<TestList>(Lifestyle.Singleton);

container.GetInstance<TestList>();

6

Autofac supports what I think you’re looking to do, but before I explain the answer, let me explain why I think you might be asking the wrong question.

Autofac, and the majority (maybe all?) of the .NET DI frameworks are built on the notion of separating the concepts of…

  • Component: This is the class that you’re registering, the body of code that has the executable bits.
  • Service: This is the interface, or the way that the component exposes its executable bits.

So when you register things in pretty much any DI framework in .NET, you’ll see a rough pattern of “register component X and expose it as service A.”

In the “pure DI” example, you – the human – are making a choice to say “I want to expose ConcretePlugin as two different services: as itself and as IPlugin. The type system allows that to happen, but the programmatic consideration of what services you expose and how was made (intentionally or unintentionally) by you.

The reason it seems “dead simple” is because you’re mentally skipping over the creation of the component-to-service mapping because that’s inherent in the type system.

So why don’t DI frameworks just “skip over that” as well, and let every component be exposed as every service? That’s probably a better question, and I think it’s a combination of:

  • Performance: If I have a strict map of component-to-service, I can do very fast lookups to see which components I have that can be injected into the dependencies of other components. If I need consider that component to be able to expose every service it could possibly expose (interfaces, base classes, etc.) then I’d need to build a cache of every possible thing it could fulfill (probably pretty sizable in a large system) and look that up for every parameter and property I inject, every time.
  • Brownfield projects: I may not actually own the code I’m working in, or may (due to various restrictions) not be allowed to change it. But I don’t want component X to be injected as a service A ever. Yeah, it may implement that interface, and I may need to inject it, but I don’t want it exposed using that interface for whatever reason.
  • Predictability: Generally the order of registration is the order in which the services will be picked to fill in dependencies. If the underlying code changes, that may have unpredictable effects on which component gets chosen to satisfy a dependency.

Let’s walk that latter one through an example. Say you have this:

// This is PSEUDOCODE. It's in Autofac-ish style, but
// we're pretending that registering a type will also
// expose it as all of its interfaces by default. That's
// not how Autofac works.
var builder = new ContainerBuilder();

// A class `First` that implements the `IFirst` interface.
builder.RegisterType<First>();

// A class `Second` that implements the `ISecond` interface.
builder.RegisterType<Second>();

// A class that takes, as dependencies, `IFirst` and `ISecond`.
builder.RegisterType<ConsumesIFirstAndISecond>();

Now let’s say this is a pretty large project. At some point, someone makes the choice to add the IFirst interface to the class Second. You know what happens now? The ConsumesIFirstAndISecond will get two copies of the class Second.

That’s not an impossible-to-debug issue, but if you’re working in a project with a team of folks of varying skill levels, it can be a non-zero-time thing to figure out. (At which point you’ll possibly file an issue or raise a StackOverflow question asking why DI frameworks don’t allow you to expressly specify the service during registration because that would have saved you a ton of time and this seems like a dead simple thing to allow).

Anyway, you see where I’m going there. DI isn’t a type system. I think the analogy of “here’s what no-DI vs. DI can do in a two line example” is maybe oversimplifying the issue. I mean, if what you want is the no-DI behavior, then… just use no DI, right?

But all that said, I did say I’d show you how Autofac can do this.

There are two things that Autofac has that you should be aware of:

  • AsSelf() – this exposes a class as itself and is the default unless other services are explicitly specified.
  • AsImplementedInterfaces() – this is basically the helper method that other answers have mentioned you might have to write. It goes through and registers a given thing as all of its interfaces.
  • ContravariantRegistrationSource – this basically “opts you in” to allowing you to resolve types in a contravariant context. It’s not helpful in this specific example, but since you’re interested in a similar area here, I figured I’d include it.

For what you want, AsSelf() and AsImplementedInterfaces() are the key.

var builder = new ContainerBuilder();
builder
  .RegisterInstance(new ConcretePlugin(/*some arguments here*/))
  .AsSelf()
  .AsImplementedInterfaces();
builder.RegisterType<TestConcrete>().SingleInstance();
builder.RegisterType<TestList>().SingleInstance();
var container = builder.Build();

// These will work.
container.Resolve<TestConcrete>();
container.Resolve<TestList>();

Anyway, I hope that all helps clarify a little. Whether it’s satisfying or not is another question, but maybe at least it explains it a bit.

1

Trang chủ Giới thiệu Sinh nhật bé trai Sinh nhật bé gái Tổ chức sự kiện Biểu diễn giải trí Dịch vụ khác Trang trí tiệc cưới Tổ chức khai trương Tư vấn dịch vụ Thư viện ảnh Tin tức - sự kiện Liên hệ Chú hề sinh nhật Trang trí YEAR END PARTY công ty Trang trí tất niên cuối năm Trang trí tất niên xu hướng mới nhất Trang trí sinh nhật bé trai Hải Đăng Trang trí sinh nhật bé Khánh Vân Trang trí sinh nhật Bích Ngân Trang trí sinh nhật bé Thanh Trang Thuê ông già Noel phát quà Biểu diễn xiếc khỉ Xiếc quay đĩa Dịch vụ tổ chức sự kiện 5 sao Thông tin về chúng tôi Dịch vụ sinh nhật bé trai Dịch vụ sinh nhật bé gái Sự kiện trọn gói Các tiết mục giải trí Dịch vụ bổ trợ Tiệc cưới sang trọng Dịch vụ khai trương Tư vấn tổ chức sự kiện Hình ảnh sự kiện Cập nhật tin tức Liên hệ ngay Thuê chú hề chuyên nghiệp Tiệc tất niên cho công ty Trang trí tiệc cuối năm Tiệc tất niên độc đáo Sinh nhật bé Hải Đăng Sinh nhật đáng yêu bé Khánh Vân Sinh nhật sang trọng Bích Ngân Tiệc sinh nhật bé Thanh Trang Dịch vụ ông già Noel Xiếc thú vui nhộn Biểu diễn xiếc quay đĩa Dịch vụ tổ chức tiệc uy tín Khám phá dịch vụ của chúng tôi Tiệc sinh nhật cho bé trai Trang trí tiệc cho bé gái Gói sự kiện chuyên nghiệp Chương trình giải trí hấp dẫn Dịch vụ hỗ trợ sự kiện Trang trí tiệc cưới đẹp Khởi đầu thành công với khai trương Chuyên gia tư vấn sự kiện Xem ảnh các sự kiện đẹp Tin mới về sự kiện Kết nối với đội ngũ chuyên gia Chú hề vui nhộn cho tiệc sinh nhật Ý tưởng tiệc cuối năm Tất niên độc đáo Trang trí tiệc hiện đại Tổ chức sinh nhật cho Hải Đăng Sinh nhật độc quyền Khánh Vân Phong cách tiệc Bích Ngân Trang trí tiệc bé Thanh Trang Thuê dịch vụ ông già Noel chuyên nghiệp Xem xiếc khỉ đặc sắc Xiếc quay đĩa thú vị
Trang chủ Giới thiệu Sinh nhật bé trai Sinh nhật bé gái Tổ chức sự kiện Biểu diễn giải trí Dịch vụ khác Trang trí tiệc cưới Tổ chức khai trương Tư vấn dịch vụ Thư viện ảnh Tin tức - sự kiện Liên hệ Chú hề sinh nhật Trang trí YEAR END PARTY công ty Trang trí tất niên cuối năm Trang trí tất niên xu hướng mới nhất Trang trí sinh nhật bé trai Hải Đăng Trang trí sinh nhật bé Khánh Vân Trang trí sinh nhật Bích Ngân Trang trí sinh nhật bé Thanh Trang Thuê ông già Noel phát quà Biểu diễn xiếc khỉ Xiếc quay đĩa
Thiết kế website Thiết kế website Thiết kế website Cách kháng tài khoản quảng cáo Mua bán Fanpage Facebook Dịch vụ SEO Tổ chức sinh nhật