New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

How do I check lifestyles equality? #635

Open
voroninp opened this Issue Nov 8, 2018 · 12 comments

Comments

2 participants
@voroninp

voroninp commented Nov 8, 2018

I am trying to register collection of dependencies reusing existing registrations of concrete types.
I have a subtask where I need to check whether two lifestyles are the same. How can I do it?

static void Main(string[] args)
{
    var container = new Container();
    container.Options.DefaultScopedLifestyle = new AsyncScopedLifestyle();

    var registration = Lifestyle.Scoped.CreateRegistration(typeof(Foo), container);
    var lifestyle = registration.Lifestyle;
	
    Console.WriteLine(lifestyle.GetType());
    Console.WriteLine(Lifestyle.Scoped.GetType());
    Console.WriteLine("Compatible types? {0}", Lifestyle.Scoped.GetType().IsAssignableFrom(lifestyle.GetType()));
    // Outputs: Compatible types? False
}

public class Foo { }
@dotnetjunkie

This comment has been minimized.

Collaborator

dotnetjunkie commented Nov 8, 2018

It's unclear to me what you are trying to accomplish. What problem are you trying to solve? Why do you need to compare lifestyles?

@voroninp

This comment has been minimized.

voroninp commented Nov 8, 2018

private static void RegisterCollectionReusingExistingRegistrations(Container container, Type serviceType, Type[] typesOfAssembliesToScan, Lifestyle lifestyle)
{
    Guard.ArgumentNotNull(container, nameof(container));
    Guard.ArgumentNotNull(serviceType, nameof(serviceType));
    Guard.ArgumentNotNull(typesOfAssembliesToScan, nameof(typesOfAssembliesToScan));
    Guard.ArgumentNotNull(lifestyle, nameof(lifestyle));

    var assemblies = new HashSet<Assembly>(
        typesOfAssembliesToScan
            .Where(t => !(t is null))
            .Select(t => t.Assembly));

    // May throw if multiple registrations per same implementation type are possible in Simple Injector.
    var registrationsByType = container.GetCurrentRegistrations()
        .Select(p => p.Registration)
        .Where(r => r.Lifestyle == lifestyle) // <= HERE !!!
        .Distinct()
        .ToDictionary(r => r.ImplementationType);

    var registrations = assemblies
        .SelectMany(a => a.GetExportedTypes())
        .Where(t => !t.IsAbstract && !t.IsInterface && !t.IsValueType)
        .Where(t => t.GetConstructors().Any(c => c.IsPublic))
        .Where(t => !serviceType.IsGenericTypeDefinition && serviceType.IsAssignableFrom(t)
                    || serviceType.IsGenericTypeDefinition &&
                    IsCompatibleWithGenericTypeDefinition(t, serviceType))
        .Select(type =>
        {
            if (!registrationsByType.TryGetValue(type, out var registration))
            {
                registration = lifestyle.CreateRegistration(type, container);
            }

            return registration;
        })
        .ToArray();

    container.Collection.Register(serviceType, registrations);
}

private static bool IsCompatibleWithGenericTypeDefinition(Type typeToCheck, Type genericTypeDefinition)
{
    Guard.ArgumentNotNull(typeToCheck, nameof(typeToCheck));
    Guard.ArgumentNotNull(genericTypeDefinition, nameof(genericTypeDefinition));

    if (!genericTypeDefinition.IsGenericTypeDefinition)
    {
        var msg = $"Type '{genericTypeDefinition.AssemblyQualifiedName}' is not a generic type definition.";
        throw new ArgumentException(msg, nameof(genericTypeDefinition));
    }

    var type = typeToCheck;
    while (! (type is null))
    {
        if (type.IsGenericTypeDefinition)
        {
            // TODO: this branch needs special handling for generic type definitions
            // E.g. Foo<T>: IFoo<T>, Bar1<T>: Foo<T> and Bar2<T>: Foo<int>.
            // If we close Bar1<T> with int we can use Bar1<int> where IFoo<int> is required.
            // However, Bar2<T> needs to be closed with additional type parameter which cannot be deduced from dependency IFoo<int>.
            // Things become even more complex, if type constraints introduced.
            return false;
        }
        else 
        {
            if (type.IsGenericType && type.GetGenericTypeDefinition() == genericTypeDefinition)
            {
                return true;
            }

            var hasCompatibleInterface =
                type.GetInterfaces()
                    .Any(i => IsCompatibleWithGenericTypeDefinition(i, genericTypeDefinition));

            if (hasCompatibleInterface)
            {
                return true;
            }

            type = type.BaseType;
        }
    }

    return false;
}

In the first method where I get registrations by type, I want to filer out by lifestyle.

@dotnetjunkie

This comment has been minimized.

Collaborator

dotnetjunkie commented Nov 8, 2018

Can you please explain in words what problem you are trying to solve?

@voroninp

This comment has been minimized.

voroninp commented Nov 8, 2018

I want to find all types which implement serviceType and register them without recreating registration of theses types, if existing registration of concrete type exists and has same lifestyle.

@dotnetjunkie

This comment has been minimized.

Collaborator

dotnetjunkie commented Nov 8, 2018

I'm sorry, but I'm still unsure what you are trying to do here. Could you be more verbose. It's unclear what it means to "register them without recreating registration of theses types, if existing registration of concrete type exists and has same lifestyle" and why this matters.

@voroninp

This comment has been minimized.

voroninp commented Nov 8, 2018

Consider class SomeService:

class SomeService: ISomeService, IEventHandler<SomeEvent> {}

My assumption is that if I call:

container.Register<ISomeSerivce, SomeService>(Lifestyle.Scoped);
container.Register<IEventHandler<SomeEvent>, SomeService>(Lifestyle.Scoped);

I will end up with two different instances of SomeService class.

On the other hand, if I call:

var registration = CreateRegistration(typeof(SomeService), container);
container.AddRegistration(typeof(ISomeSerivce), registration);
container.AddRegistration(typeof(IEventHandler<SomeEvent>), registration);

there will be only one instance of SomeService per scope.

So, when I scan assembly for type which implement IEventHandler<TEvent> I want to reuse its registration. Otherwise, if this type was already registered as implementation of another interface, I'll get new instance.

@dotnetjunkie

This comment has been minimized.

Collaborator

dotnetjunkie commented Nov 8, 2018

Ah, now I see. You are trying to prevent the lifestyle to become torn.

I will end up with two different instances of SomeService class.

No, you will not. Simple Injector will automatically de-duplicate those registrations and will ensure that there is only one instance of SomeService per Scope:

With the introduction of Simple Injector 4, the container will prevent the creation of multiple Registration instances for the same concrete type in most cases. [...] Torn lifestyles will only happen when a custom lifestyle circumvents the caching behavior of the Lifestyle.CreateRegistration overloads.

@voroninp

This comment has been minimized.

voroninp commented Nov 8, 2018

Thanks, things became a lot easier. =)

@voroninp

This comment has been minimized.

voroninp commented Nov 8, 2018

Can we have an overload for registering collection with explicitly provided lifestyle?

@dotnetjunkie

This comment has been minimized.

Collaborator

dotnetjunkie commented Nov 8, 2018

So why do you want your IEventHandler<T> implementations to have a different lifestyle than Transient? It might be easier if you just make everything Scoped by default by setting the Container.Options.DefaultLifestyle to your Scoped lifestyle.

@voroninp

This comment has been minimized.

voroninp commented Nov 8, 2018

Because each entity is a potential source of multiple events generated within single transaction: Renamed, Reassigned, etc.

There’s a small chance that handler of multiple events will need statefullness within scope of web request.

@dotnetjunkie

This comment has been minimized.

Collaborator

dotnetjunkie commented Nov 8, 2018

As a rule of thumb, I always advise application components to be stateless and immutable.

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