wired_in
wired_in

Reputation: 2613

Multiple bindings error for generic interface

I'm using Ninject v2.2.1.4, so I'm not sure if this is how the newer versions of ninject work.

I'm going to use a classic example of inheritance, Animal. So I have a base abstract Animal class and then countless implementations of Animal.

public abstract class Animal { }
public class Tiger : Animal { }

Now I have an IValidator for Animal that makes sure the properties are set correctly.

public interface IValidator<T>
{
    bool Validate(T object);
}

For every implementation of Animal (Tiger, Cat, Dog, etc..) I may or may not have an IValidator<T> implementation. When there isn't a validator for a specific animal, I want to use a default validator that validates the base Animal properties (IValidator<Animal>).

So here are a couple implementations of IValidator<T>, including the default implementation:

// Contains default logic for base Animal inherited by all validators
public abstract class AbstractAnimalValidator<TAnimal> : IValidator<TAnimal> where TAnimal : Animal { }
// Default implementation for IValidator when the animal doesn't have a validator. 
//This just calls the base abstract validator logic.
public DefaultAnimalValidator : AbstractAnimalValidator<Animal> { }
// Validator for a Tiger. It calls the base validate method and then validates properties specific to a Tiger.
public TigerValidator : AbstractAnimalValidator<Tiger> { }

Let's just assume that the Tiger is the only animal with its own validator. I would have these two bindings:

Bind<IValidator<Animal>>().To<DefaultAnimalValidator>();
Bind<IValidator<Tiger>>().To<TigerValidator>();

So now I will have logic that tries to inject a validator for the specific animal, by attempting to get an implementation, such as injecting IValidator<Tiger>. If it doesn't exist, it injects the default validator using IValidator<Animal>.

The problem is that when trying to inject IValidator<Tiger>, both of the above bindings match, so I get a multiple bindings found error. This is because Tiger inherits from Animal, so Tiger is both a Tiger and an Animal. I've come up with some hacky solutions around this, but is there something I'm missing?

Upvotes: 1

Views: 131

Answers (1)

Jakub Lortz
Jakub Lortz

Reputation: 14904

Can you modify the IValidator<T> interface? If yes, you can mark the type parameter as contravariant:

public interface IValidator<in T>
{
    bool Validate(T o);
}

It will allow you to assign an IValidator<Animal> to IValidator<Tiger>. Then you can specify all bindings explicitly:

Bind<IValidator<Cat>>().To<DefaultAnimalValidator>();
Bind<IValidator<Dog>>().To<DefaultAnimalValidator>();
Bind<IValidator<Tiger>>().To<TigerValidator>();

This approach has also one big advantage over implicit fallback to IValidator<Animal>. If you add another class derived from Animal with it's own validator and forget to add a binding, you will get a run-time error. If you use Bind<IValidator<Animal>>().To<DefaultAnimalValidator>(), you will get a hard to trace bug.

Upvotes: 1

Related Questions