12

I'm studying up on the unity containers and have a quick question on how to resolve a class's construction to multiple different implementations of an interface.

Here's my code:

public interface IRenderer
{
    void DrawSquare(Square square);
    void DrawCircle(Circle circle);
}

public interface IShape
{
    void Draw(IRenderer renderer);
}

public class Dx11Renderer : IRenderer
{
    public void DrawSquare(Square square)
    {
    }

    public void DrawCircle(Circle circle)
    {
    }
}

public class GlRenderer : IRenderer
{
    public void DrawSquare(Square square)
    {
    }

    public void DrawCircle(Circle circle)
    {
    }
}

public class Circle : IShape
{
    public void Draw(IRenderer renderer) { renderer.DrawCircle(this); }   
}

public class Square
{
    public void Draw(IRenderer renderer) { renderer.DrawSquare(this); }   
}

public class Canvas
{
    private readonly IRenderer _renderer;

    private List<Circle> _circles = new List<Circle>();
    private List<Square> _squares = new List<Square>(); 

    public Canvas(IRenderer renderer)
    {
        _renderer = renderer;
    }

    public void Draw()
    {
        foreach (Circle c in _circles)
        {
            c.Draw(_renderer);
        }

        foreach (Square s in _squares)
        {
            s.Draw(_renderer);
        }
    }
}

and to register/resolve

        // Create the container
        var container = new UnityContainer();

        // registration

        container.RegisterType<IRenderer, GlRenderer>("GL");
        container.RegisterType<IRenderer, Dx11Renderer>("DX11");

        Canvas canvas = container.Resolve<Canvas>("GL");

This throws a "ResolutionFailedException" so I must be using this incorrectly.

Can someone explain if this is bad practice, or how I can achieve this.

Thanks

UPDATE:

So what I have done is registered Canvas twice with each type of dependencies like so:

// Canvas with an OpenGL Renderer
container.RegisterType<Canvas>("GLCanvas", new InjectionConstructor(new ResolvedParameter<IRenderer>("GL")));
// Canvas with a DirectX Renderer
container.RegisterType<Canvas>("DXCanvas", new InjectionConstructor(new ResolvedParameter<IRenderer>("DX11")));

Canvas canvas = container.Resolve<Canvas>("GLCanvas");

This works well for me!

2
  • 1
    Canvas canvas = container.Resolve<Canvas>("GL"); Shouldn't ICanvas or an interface for Canvas type Commented May 15, 2014 at 5:10
  • See Dependency injection type-selection and Dependency Injection Unity - Conditional resolving. DI containers are for resolving object graphs at application startup, not for controlling runtime behavior. In case of the latter, you should use one or more design patterns to solve the problem as it is clearly not a DI problem, but an application design problem. Commented Jan 20, 2018 at 4:53

2 Answers 2

7

The problem is that you are resolving Canvas with the name "GL", but you have not registered Canvas in that way. Unity doesn't propagate the name to dependency resolution, so it won't use the name "GL" when resolving IRenderer.

There are several options to solve this already answered: Resolving named dependencies with Unity

Your question is whether this is a bad practice, or how you can achieve the same results. In my experience, trying to register and resolve multiple instances of the same interface usually leads to messy code. One alternative would be to use the Factory pattern to create instances of Canvas.

Do you need to use your container to resolve Canvas? If you don't have a reason not to, you could simply Resolve your IRenderer and new up a Canvas yourself:

new Canvas(container.Resolve<IRenderer>("GL"));

Remember that Unity is just a tool, if it doesn't seem to be capable of doing what you need, you may need a different kind of tool.

Sign up to request clarification or add additional context in comments.

2 Comments

Thanks, might have confused myself as to what the Unity containers should really be used for.
I've updated my question with a solution that's working for me.
0

There is a way to inject the right renderer in the canvas on startup time. If you know the render method on startup you can register only the right renderer like this:

var container = new UnityContainer();
container.RegisterType<ICanvas, Canvas>();
if (CheckIfItIsDx11)
{
    container.RegisterType<IRenderer, Dx11Renderer>();
}
else
{
    container.RegisterType<IRenderer, GlRenderer>();
}

when you want to resolve the canvas just use:

var canvas = container.Resolve<ICanvas>();

if you dont know the renderer on startup time there is a way to. Like this:

container.RegisterType<IRenderer, Dx11Renderer>("DX11");
container.RegisterType<IRenderer, GlRenderer>("GL");


var renderer = container.Resolve<IRenderer>("DX11");
var canvas = container.Resolve<ICanvas>(new ParameterOverride("renderer", renderer));

Canvas now has the right renderer injected. The canvas can use the renderer interface like this:

internal interface ICanvas
{
    void Draw();
}

public class Canvas : ICanvas
{
    private readonly IRenderer _renderer;

    private readonly List<Circle> _circles = new List<Circle>();
    private readonly List<Square> _squares = new List<Square>();

    public Canvas(IRenderer renderer)
    {
        _renderer = renderer;
    }

    public void Draw()
    {
        foreach (var circle in _circles)
        {
            _renderer.Draw(circle);
        }

        foreach (var square in _squares)
        {
            _renderer.Draw(square);
        }
    }
}

Also the renderer should not be drawing the shape. The shape is responsible for drawing itself. This way you keep your code at the same spot. If you keep adding shapes the renderer file get huge. and you need to search for some shapes if you want to change code. Now everything is in the right place where it should be. The code now should look something like this:

public interface IRenderer
{
    void Draw(IShape shape);
}

public interface IShape
{
    void Draw(IRenderer renderer);
}

public class Dx11Renderer : IRenderer
{
    public void Draw(IShape shape)
    {
        shape.Draw(this);
    }
}

public class GlRenderer : IRenderer
{
    public void Draw(IShape shape)
    {
        shape.Draw(this);
    }
}

public class Circle : IShape
{
    public void Draw(IRenderer renderer)
    {
        if (renderer.GetType() == typeof(Dx11Renderer))
        {
            Console.WriteLine("Draw circle with DX11");
        }

        if (renderer.GetType() == typeof(GlRenderer))
        {
            Console.WriteLine("Draw circle with GL");
        }
    }
}

public class Square : IShape
{
    public void Draw(IRenderer renderer)
    {
        if (renderer.GetType() == typeof(Dx11Renderer))
        {
            Console.WriteLine("Draw square with DX11");
        }

        if (renderer.GetType() == typeof(GlRenderer))
        {
            Console.WriteLine("Draw square with GL");
        }
    }
}

Hope this will help.

3 Comments

"Also the renderer should not be drawing the shape." - If you are taking the data driven approach then this is not necessarily true. The renderer should absolutely be drawing the shape.
I am not familiair with the data driven paradigm. I am going to look into this. Thanks for the feedback :)
The basic gist of it I believe is that you separate data from behaviour. So then an object like Circle becomes a pure data class (might have radius, color, collision data etc.). Then you have a class (or classes) that are responsible for performing operations on the data (draw, collide etc.) Have a look at entity & component systems in google.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.