khellang / Scrutor

Assembly scanning and decoration extensions for Microsoft.Extensions.DependencyInjection
MIT License
3.63k stars 239 forks source link

Difference between AsSelfWithInterfaces and AsImplementedInterfaces? #152

Closed vdurante closed 1 month ago

vdurante commented 3 years ago

I am trying to understand how Scrutor works and I got quite confused regarding the differences between AsSelfWithInterfaces and AsImplementedInterfaces. Besides the Self part, I noticed AsImplementedInterfaces registers the interfaces directly to a ImplementationType, such as IRepository -> Repository, but the AsSelfWithInterfaces doesn't, since it registers the interfaces to the class using a ImplementationFactory.

I understand de implementation differences between using a factory and a type, but I don't seem to understand the actual difference between both, specially regarding use cases. When should I use each?

Would appreciate if someone could help me out. Thanks!

Mariachi1231 commented 3 years ago

Hi,

Most likely, these methods do what they actually say.

Consider next pseudo-code:

#region fakes

public interface IA { } 
public interface IB { } 
public class AB : IA, IB { }

#endrigion

#region main

var sp1 = new SeviceCollection()
    .Scan(x => x.FromAssemblyOf<AB>()
                        .AddClasses(classes => classes.AssignableTo<AB>())
                        .AsImplementedInterfaces()
                        .WithTransientLifetime())
    .BuildServiceProvider();

var a = sp1.GetService<IA>(); // NOT NULL
var b = sp1.GetSerivce<IB>(); // NOT NULL
var ab= sp1.GetService<AB>(); // NULL

var sp2 = new SeviceCollection()
    .Scan(x => x.FromAssemblyOf<AB>()
                        .AddClasses(classes => classes.AssignableTo<AB>())
                        .AsSelfWithInterfaces()
                        .WithTransientLifetime())
    .BuildServiceProvider();

a = sp2.GetService<IA>(); // NOT NULL
b = sp2.GetSerivce<IB>(); // NOT NULL
ab= sp2.GetService<AB>(); // NOT NULL

#endregion 

So, AsSelfWithInterfaces means that every type that satisfies your predicate in AddClasses will be registered as every interface that it implements + as itself. In other words, without the usages of Scrutor these can look like:

sp2 = new ServiceCollection()
    .AddTransient<AB>()
    .AddTransient<IA>(sp => sp.GetService<AB>())
    .AddTransient<IB>(sp => sp.GetService<AB>())
    .BuildServiceProvider();

a = sp2.GetService<IA>(); // NOT NULL
b = sp2.GetSerivce<IB>(); // NOT NULL
ab= sp2.GetService<AB>(); // NOT NULL
khellang commented 3 years ago

If I remember correctly there's a difference in instance reuse for longer lifetimes as well.

With AsImplementedInterfaces, if you have singleton registrations for IA and IB where both are implemented by AB, there will be two separate singletons - one for each registration.

But with AsSelfWithInterfaces, the interface registrations will resolve the self-registered singleton, which means each interface registration will share one singleton instance.

Does that make sense?

Mariachi1231 commented 3 years ago

Yeah, seems according to ScanShouldCreateSeparateRegistrationsPerInterface and AsSelfWithInterfacesShouldForwardRegistrationsToClass you remember correctly 😅

I think, from one point of view it does, from another no. Mostly because it works slightly differently in comparison with scanning in other DI containers. Since in past I worked mostly with Autofac, their AsImplementedInterfaces works as your AsSelfWithInterfaces (for sure, if we talk about types lifecycle), the same in the SimpleInjector, and if I correctly remember in the Windsor as well.

BTW, from Microsoft.Extensions.DependencyInejction standpoint it makes sense.

khellang commented 3 years ago

Right. As I look at it, this is a limitation in the MS.Ext.DI container 😞