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

Why is not cyclic dependency detected at verification? #634

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

Comments

2 participants
@voroninp

voroninp commented Nov 8, 2018

static void Main(string[] args)
{
    var container = new Container();
    container.Register(typeof(IFoo<>), typeof(NeedFoo<>), Lifestyle.Singleton);
    container.Verify(VerificationOption.VerifyAndDiagnose);
	
    // This throws, but Verify - does not.
    container.GetInstance(typeof(NeedFoo<int>));
}

public interface IFoo<T> {}

public class NeedFoo<T>: IFoo<T>
{
	public NeedFoo(IFoo<T> foo){}
}
@voroninp

This comment has been minimized.

voroninp commented Nov 8, 2018

And this code should probably throw:

static void Main(string[] args)
{
    var container = new Container();
    container.Register(typeof(IFoo<>), typeof(Foo<>), Lifestyle.Singleton);
    container.Register(typeof(NeedFoo<>));
    container.Verify(VerificationOption.VerifyAndDiagnose);
}

public interface IFoo<T> { }
public class Foo<T>: IFoo<T> where T: class {}

public class NeedFoo<T> where T:struct
{
    public NeedFoo(IFoo<T> foo){}
}

Here NeedFoo can only have implementations closed with a value type, however the only registration for IFoo<T> is Foo<T> with T restricted to reference types.

@dotnetjunkie

This comment has been minimized.

Collaborator

dotnetjunkie commented Nov 8, 2018

This is by design. The How To page states:

If any of your root types are generic you should explicitly register each required closed-generic version of the type instead of making a single open-generic registration per generic type. Simple Injector will not be able to guess the closed types that could be resolved (root types are not referenced by other types and there can be endless permutations of closed-generic types) and as such open generic registrations are skipped by Simple Injector’s verification system. Making an explicit registration for each closed-generic root type allows Simple Injector to verify and diagnose those registrations.

To be able to do the verification, Simple Injector needs either a non-generic or closed-generic type, but NeedFoo<T> is open-generic, and in no consumer is depending on a closed version of NeedFoo<T>. This disallows Simple Injector from verifying the dependency graph, as the way Simple Injector detect dependency cycles is by actually invoking the constructor. Simple Injector, however, cannot guess which generic type arguments would be valid to apply to NeedFoo<T> in order to correctly resolve that class.

In other words, Simple Injector does the analysis at runtime by constructing object graphs, but doesn't have enough information to construct a closed version of NeedFoo<T>.

Note that even if we would redesign the verification system to make use of static type information, available during registration, instead of depending on the creation of types, it would still not be possible to get reliable verification. For example, take a look at the following configuration:

// Definitions
interface IBar<T> { }
interface IBuzz<T> { }
class IntBar : IBar<int> { }
class DoubleBar : IBar<double> { }
class Bar<T> : IBar<T> { public Bar(IBuzz<T> fuzz) { } }
class Buzz<T> : IBuzz<T> { public Buzz(IBar<T> bar) { } }

// Registration
var container = new Container();

container.Register<IBar<int>, IntBar>();
container.Register<IBar<double>, DoubleBar>();
container.RegisterConditional(typeof(IBar<>), typeof(Bar<>), c => !c.Handled);
container.Register(typeof(IBuzz<>), typeof(Buzz<>));
            
container.Verify();

// This throws, but Verify - does not.
container.GetInstance(typeof(IBar<int>)); // Succeeds
container.GetInstance(typeof(IBar<double>)); // Succeeds
container.GetInstance(typeof(IBar<string>)); // Cyclic dependency exception

In this example, the dependency cycle only exists for a subset of all IBar<T>, which is in part determined by the predicate supplied to RegisterConditional. The predicate, however, can only be tested by calling it, but it might return a different value per closed version of Bar<T>.

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