Escribir un contenedor IoC mínimo en C#

Carlos Blanco | marzo 01, 2021

Ser mentor de nuevos ingenieros y desarrolladores es una parte emocionante de trabajar en Encora.

 

¿Cuál es la diferencia entre Inversión de Control e Inyección de Dependencia? Este tipo de confusión en torno a los dos términos surge con frecuencia cuando se trabaja con nuevos ingenieros o becarios. Como mentor, es una gran oportunidad para aclarar y compartir atajos. 

Aunque en un principio puedan parecer confusos, escribir un contenedor mínimo es una buena manera de ilustrar las diferencias de estos dos conceptos. 

Es más fácil entender la Inversión de Control (IoC) y la Inyección de Dependencia (DI) escribiendo un contenedor IoC lo más pequeño posible. El proceso de escribir el contenedor IoC hace obvio por qué la inyección de dependencia es útil.

Título

Aunque están relacionados, IoC es más amplio que DI. IoC significa que un código llama a otro; DI va más allá e implementa IoC utilizando la composición.
En IoC, un marco de trabajo controla cómo se instancian y gestionan los módulos.
DI resuelve el problema de la independencia de una aplicación con respecto a sus objetos. Esto es muy útil cuando se necesita una aplicación configurable o cuando las pruebas unitarias implican objetos simulados. DI ayuda a modificar una aplicación más fácilmente a medida que crece en tamaño y complejidad.
Un contenedor DI o IoC necesita instanciar objetos (dependencias) y proporcionarlos a la aplicación. Para ello, debe ocuparse de
Inyección de constructores
Inyección del Setter
Inyección en la interfaz
El contenedor necesita instanciar automáticamente objetos de tipos específicos y pasarlos en los constructores, métodos setter o interfaces a las clases. La inyección del constructor será el foco aquí ya que es la inyección más común.
El contenedor soportará el registro de tipos concretos de interfaces y los resolverá cuando el usuario lo solicite. También resolverá automáticamente los parámetros del constructor.

Un ejemplo

Se escribió una interfaz llamada IWriter, que incluye un método Write. La interfaz IWriter es implementada por una clase concreta Writer. Se registra en el contenedor al inicializar la aplicación. Posteriormente, la aplicación puede solicitar una instancia de IWriter al contenedor.

container.Register<IWriter, Writer>(); … … Writer writer = container.Create();

En un artículo, Ayende escribió de forma impresionante un contenedor IoC en solo 15 líneas de código. 

Sin embargo, esta publicación es antigua y se puede mejorar con la ayuda de los genéricos y la reflexión. El código de Ayende se modificó y dio como resultado un contenedor IoC en 11 líneas de código. No utiliza delegados para instanciar objetos.

Este contenedor mínimo de IoC realiza una comprobación de tipos y se asegura de que los tipos registrados implementan las interfaces/clases con las que están registrados. Para ello se utilizan los genéricos.

También instala automáticamente objetos para los tipos registrados. Resuelve los parámetros de los constructores y los instala automáticamente al resolver los tipos.

 


public class MinimalContainer
{
	private readonly Dictionary<Type, Type> types = new Dictionary<Type, Type>();
    
    public void Register<TInterface, TImplementation>() where TImplementation : TInterface
    {
    	types[typeof(TInterface)] = typeof(TImplementation);
    }
    
    public TInterface Create()
    {
    	return (TInterface)Create(typeof(TInterface));
    }
    
    private object Create(Type type)
    {
    	//Find a default constructor using reflection
        var concreteType = types[type];
        var defaultConstructor = concreteType.GetConstructors()[0];
        //Verify if the default constructor requires params
        var defaultParams = defaultCtor.GetParameters();
        //Instantiate all constructor parameters using recursion
        var parameters = defaultParams.Select(param => Create(param.ParameterType)).ToArray();
        
        return defaultConstructor.Invoke(parameters);
    }
    
}

 

Así es como se utiliza el contenedor.

 


var container = new MinimalContainer();

container.Register<IWelcomer, Welcomer>();
container.Register<IWriter, ConsoleWriter>();

var welcomer = container.Create();
welcomer.SayHelloTo("World");

 

Aquí están las clases utilizadas por el ejemplo anterior,

 


public interface IWelcomer {
	void SayHelloTo(string name);
}

public class Welcomer : IWelcomer
{
    private IWriter writer;

    public Wey(IWriter writer) {
        this.writer = writer;
    }

    public void SayHelloTo(string name)
    {
        writer.Write($"Hello {name}!");            
    }
}

public interface IWriter {
    void Write(string s);
}

public class ConsoleWriter : IWriter
{
    public void Write(string s)
    {
        Console.WriteLine(s);
    }
}

Esto NO está listo para la producción, obviamente. Tenga en cuenta que no hay ningún archivo de configuración: este contenedor solo admite la inyección de constructores y el registro en el código. Un contenedor IoC completo soportaría más funciones y otros tipos de inyecciones, el registro en archivos de configuración y la gestión del ciclo de vida de los objetos.

Se recomiendan contenedores completos como AutoFac, Ninject o StructureMap cuando se escribe el código de producción. Este ejercicio es una manera fácil de enseñar a un desarrollador menos experimentado la diferencia entre IoC y DI de una manera sencilla.

¡Manténgase al día!

Aquí puede encontrar los problemas cotidianos que resuelven los desarrolladores, las mejores prácticas de pruebas y muchas publicaciones sobre nuestra cultura única.

Contáctenos

Puntos clave para tener en cuenta:

  • Es más fácil entender la Inversión de Control (IoC) y la Inyección de Dependencia (DI) escribiendo un contenedor IoC lo más pequeño posible. El proceso de escribir el contenedor IoC hace obvio por qué la inyección de dependencia es útil.
  • Aunque están relacionados, IoC es más amplio que DI. IoC significa que un código llama a otro; DI va más allá e implementa IoC utilizando la composición.
  • Un contenedor DI o IoC necesita instanciar objetos (dependencias) y proporcionarlos a la aplicación. Para ello, debe ocuparse de la inyección de constructores, de la inyección de emisores y de la inyección de interfaces. 
  • El contenedor soportará el registro de tipos concretos de interfaces y los resolverá cuando el usuario lo solicite. También resolverá automáticamente los parámetros del constructor.

Acerca de Encora

En Encora, creamos ventajas competitivas para los clientes asociados a través de la innovación tecnológica acelerada. Estaremos encantados de acompañarlo en su viaje.

Por quéEncora

  • Somos globales: Con más de 20 oficinas y laboratorios de innovación en 12 países, Encora está dispersa por todo el mundo. Como operamos en diferentes zonas horarias, siempre hay alguien dispuesto a ayudarlo.
  • Somos un servicio completo: La innovación tecnológica abarca un enorme rango de temas, habilidades y estrategias. Ofrecemos un concepto de servicio completo, cada componente adaptado según sus necesidades, para presentar una solución completa. 
  • Nos dedicamos a ello: Nuestro creciente equipo de más de 4.000 programadores, desarrolladores, ingenieros y pensadores estratégicos cualificados y dedicados es la razón por la que Encora se ha convertido en una empresa tecnológica galardonada con una reputación envidiable.
  • Tenemos experiencia: Nos asociamos principalmente con empresas tecnológicas de rápido crecimiento que impulsan la innovación y el crecimiento en sus sectores. Nuestro modelo de entrega único, nuestra metodología ágil y nuestra calidad inigualable han contribuido a nuestro crecimiento constante.

Contenido

Compartir Artículo

Artículos Destacados