1

Closed

Variance not working for uncontrolled collection

description

Variance is not working if collection is registered as uncontrolled, see below example
public class ClassA {}

public class ClassB : ClassA {}

public interface IIndexBuilder<in TObject>
{
    void Build();
}

public class IndexBuilder1 : IIndexBuilder<ClassB>
{
    public void Build()
    {
        throw new NotImplementedException();
    }
}

// now register
var container = new Container();
container.RegisterAll(typeof(IIndexBuilder<ClassB>), new[] { new IndexBuilder1() });

var instances = container.ResolveAll<IIndexBuilder<ClassA>>();
instances.Count is 0 which is obviously failing!

comments

dot_NET_Junkie wrote Apr 16, 2015 at 10:05 AM

The reason you get no collections is for the same reason as this statement does not compile:
IIndexBuilder<ClassA> a = new IndexBuilder1();
This is because you use the in keyword, but resolving a class that implements IIndexBuilder<ClassB> as IIndexBuilder<ClassA> implies that you need the out keyword.

This works:
public interface IIndexBuilder<out TObject> { }

container.RegisterAll(typeof(IIndexBuilder<>), new[] { typeof(IndexBuilder1) });

var instances = container.GetAllInstances<IIndexBuilder<ClassA>>();

joshua0420 wrote Apr 16, 2015 at 3:53 PM

Sorry there was a typo error in my post.

Actually there is already a OUT keyword

public interface IIndexBuilder<out TObject> {}

so what works and what is not is below:
public class ClassA {}

public class ClassB : ClassA {}
public class ClassC : ClassA {}

public interface IIndexBuilder<out TObject> {}

public class IndexBuilderB : IIndexBuilder<ClassB> {}

public class IndexBuilderC : IIndexBuilder<ClassC> {}
FOLLOWING DOES NOT WORK:
container.RegisterAll(typeof(IIndexBuilder<ClassB>, new[] { new IndexBuilderB() });
container.RegisterAll(typeof(IIndexBuilder<ClassC>, new[] { new IndexBuilderC() });

container.GetAllInstances<IIndexBuilder<ClassA>>(); // returns empty collection
FOLLOWING WORKS:
container.RegisterAll(typeof(IIndexBuilder<ClassB>, new[] { typeof(IndexBuilderB) });
container.RegisterAll(typeof(IIndexBuilder<ClassC>, new[] { typeof(IndexBuilderC) });

container.GetAllInstances<IIndexBuilder<ClassA>>(); // returns collection with 2 elements
The thing is that I'm not registering index builders all at once, as you can see.

Your code snippet is registering using generic type definition (typeof(IIndexBuilder<>)), but mine is registering closed generics seperately.

So can you explain why first example works and later does not?

joshua0420 wrote Apr 16, 2015 at 3:58 PM

And important thing is I want to use RegisterAll(Type, IEnumerable) overload which registers uncontrolled collection. By doing so I can do some processing or filtering when enumerating my collection. Other overloads are working fine. Is this by design or a bug?

Thank you very much

dot_NET_Junkie wrote Apr 16, 2015 at 8:02 PM

Well.... I think it's partly an oversight. The reason this doesn't work is because uncontrolled collections didn't get much attention in the last few releases as the controlled collections did. There isn't any reason for me to believe that this can't be improved. In fact, I would argue that it would be good to improve the support, because the API is clearly unintuitive at this point. Things should "just work", but it clearly doesn't work here. As a matter of fact, I was confused by your code, because my first response was that it should work. It was only later that I noticed you are registering instances.

Do note though that in the case of the Register(Type, IEnumerable), Simple Injector has at registration time no idea what implementations are supplied, and because our design principles Simple Injector can't do runtime checks on the returned instances. This means that for the Register(Type, IEnumerable) overload, Simple Injector will only be able to return concatted enumerables for you.

I created a work item for this on Github, since we moved to Github ;-).

In the meantime, a workaround is to use the AppendToCollection extension method from the SimpleInjector.Advanced namespace. You can make your registrations as follows:
container.AppendToCollection(typeof(IIndexBuilder<>),
   Lifestyle.Singleton.CreateRegistration<IndexBuilderB>(() => new IndexBuilderB(), container));

container.AppendToCollection(typeof(IIndexBuilder<>),
   Lifestyle.Singleton.CreateRegistration<IndexBuilderC>(() => new IndexBuilderC(), container));

container.GetAllInstances<IIndexBuilder<ClassA>>(); // returns collection with 2 elements
Do note though that I think that in general it is much better to prevent injecting IEnumerable<T>s in your code base. Whether or not you have multiple implementations of a certain service is an implementation detail, and leaking this into a consumer means pushing complexity into the consumer, who gets responsiblee of iterating the collection. This can easily add up.

Instead, think about hiding this complexity and implementation details behind a composite. So inject of letting consumers depend upon IEnumerable<IIndexBuilder<ClassA>>, let them depend on IIndexBuilder<ClassA> and inject an IndexBuilderComposite<TObject> into them. A simple version might look like this:
public class IndexBuilderComposite<TObject> : IIndexBuilder<TObject>
{
    private readonly IEnumerable<IIndexBuilder<TObject>> builders;
    public IndexBuilderComposite(IEnumerable<IIndexBuilder<TObject>> builders) {
        this.builders = builders;
    }

    public void Build() {
        foreach (var builder in this.builders) {
            builder.Build();
        }
    }
}
Not only does this allow you to remove duplication that iterating the collection brings, this composite class is also a great place to apply that filtering you are talking about. This might even allow your builders to become transient and/or to have dependencies of their own. This means those builders don't have to be registered as uncontrolled collection anymore.

joshua0420 wrote Apr 17, 2015 at 4:30 AM

Thanks man, because of uncontrolled collections not getting variance support, I reverted back to using Composite which injects IEnumerable first and filters them according to certain criteria and clients inject the Composite. Thank you very much for your immediate support!