DEV Community

Javier Sanjuan
Javier Sanjuan

Posted on • Updated on

Resolución dinámica de tipos en .NET Core

Resolución dinámica de tipos en tiempo de ejecución en el contenedor de IoC de .NET Core

En ocasiones necesitamos resolver, en tiempo de ejecución, diferentes implementaciones de una misma interfaz.

Por ejemplo, podemos contar con una abstracción declarada a través de la interfaz IShape que es implementada por las clases Rectangle y Circle. Para ello podemos registrar los dos tipos en el contenedor de inyección de dependencias (IoC) -en adelante contenedor- y esperar que dicho contenedor nos devuelva el tipo adecuado cuando lo necesitemos.

services.AddTransient<IShape, Rectangle>();
services.AddTransient<IShape, Circle>();
Enter fullscreen mode Exit fullscreen mode

Sin embargo, cuando el contenedor se encuentre con una dependencia de IShape no sabrá qué tipo debe resolver, si Rectangle o Circle.

private readonly IShape _rectangle;
private readonly IShape _circle;

public WindowManager(IShape rectangle, IShape circle)
{
    this._rectangle = rectangle ?? throw new ArgumentNullException(nameof(rectangle));
    this._circle = cirlce ?? throw new ArgumentNullException(nameof(circle));
}
Enter fullscreen mode Exit fullscreen mode

Al resolver las dependencias de WindowManager, el contenedor nos devolverá siempre el mismo tipo.

Algunos autores proponen soluciones que utilizan factorías para la resolución de los tipos, sin embargo, dicha solución conlleva la necesidad de modificar la factoría cada vez que necesitamos registrar un nuevo tipo, y dependiendo de cómo construyamos la factoría corremos el riego de romper el principio Open-Closed además de añadir complejidad a nuestra solución.

Al utilizar factorías podríamos romper el principio Open-Closed y añadir complejidad nuestro código.

Como anotación, cabe mencionar que previo a las versiones .NET Core, era habitual utilizar el contenedor de Ioc Unity de Microsoft, en el que se podían registrar tipos “nombrados” mediante un string para poder hacer referencia al tipo concreto cuando se necesitase:

//Registro de tipos nombrados en el IoC Unity
container.RegisterType<IShape, Rectangle>("Rectangle");
container.RegisterType<IShape, Circle>("Circle");
Enter fullscreen mode Exit fullscreen mode

Sin embargo en el contenedor incluido por defecto en .NET Core no contamos con semejante posibilidad.

Para solventar esta situación, necesitamos un mecanismo que indique al contenedor cómo resolver el tipo adecuado en las dependencias que lo necesiten. Una forma sencilla pasa por utilizar una interfaz genérica.

public interface IShape<T> where T : class {}
Enter fullscreen mode Exit fullscreen mode

Las diferentes clases, en nuestro ejemplo Rectangle y Circle, implementarán ahora dicha interfaz genérica:

public class Rectangle : IShape<Rectangle>
{
    public Rectangle() { }
}
Enter fullscreen mode Exit fullscreen mode

Como podemos ver la interfaz especifica aquí el mismo tipo que la utiliza; En este caso la clase Rectangle. Este es el truco que utilizaremos para registrar los tipos en el contenedor. De esta manera podemos indicar el tipo tanto en el registro como en la dependencia.

services.AddTransient<IShape<Rectangle>, Rectangle>();
services.AddTransient<IShape<Circle>, Circle>();
Enter fullscreen mode Exit fullscreen mode

Ahora ya podemos declarar las dependencias utilizando la interfaz genérica y el contenedor resolverá el tipo adecuado.

private readonly IShape<Rectangle> _rectangle;
private readonly IShape<Circle> _circle;

public WindowManager(IShape<Rectangle> rectangle, IShape<Circle> circle)
{
    this._rectangle = rectangle ?? throw new ArgumentNullException(nameof(rectangle));
    this._circle = circle ?? throw new ArgumentNullException(nameof(circle));
}
Enter fullscreen mode Exit fullscreen mode

Conclusión

Como decíamos existen otras posibles formas de solucionar este problema, sin embargo, se suele hacer uso de factorías que añaden complejidad y corren el riesgo de romper el principio Open-Closed. El uso de interfaces genéricas en este caso es posiblemente la solución más sencilla.

Código de ejemplo

En el siguiente enlace se puede descargar el código de ejemplo descrito en el artículo: Código ejemplo.

Referencias

Artículo originalmente publicado en Medium

Top comments (1)

Collapse
 
javiersanjuan profile image
Javier Sanjuan

La versión 8 de .Net resuelve este tema con lo que han llamado "keyed services".