次の方法で共有


.NET Avanzado: Closures

Voy a ponerlo lo más simple que pueda:

Un Closure es una entidad de código que encapsula un comportamiento dado teniendo acceso al contexto en que fue definido; en cristiano, es como una clase, pero no con tanta flexibilidad (solo admite una acción dada) y su estado no puede ser cambiado luego de ser inicializado. En síntesis, una seudoclase más especializada, que consume menos recursos. Y que es más felxible que una estructura, dado que se constuye con delegados, que pueden apuntar a distintos métodos.

Cómo se construye?

Tomar el framework 2.0 en adelante, usar sus delegados y métodos anónimos, generar un productor de delegados con un parámetro de configuración y se obtendrá un closure.

Ha tenido por ejemplo que calcular un impuesto como el IVA que puede variar según el artículo?
Por ejemplo para una crema dental es de 16% pero para un auto es 25%.

Entonces el programador bien juicioso se hace el siguiente método:

double IvaCalc(double tax, double amount);

Así pues siempre que se vaya a llamar al método se han de pasar ambos parámetros:

double imp1 = IvaCalc(25, 25800);
double imp2 = IvaCalc(16, 4);
dpuble imp3 = IvaCalc(16, 5800);

etcétera

Entonces se puede optar por hacer dos métodos; uno por cada tipo de IVA.
Pero si son 10 tipos de IVA habrán 10 métodos?

Así que se opta por hacer una clase calculadora de IVA:

Código clase calculadora de impuestos en C#

De esta manera basta con instanciar un objeto por cada tipo de IVA y ponerlo a trabajar; esto minimiza la cantidad de código escrita, y es bastante claro:

IvaCalculator autoCalc=new IvaCalculator(25);
IvaCalculator prodCalc=new IvaCalculator(16);
double imp1 = autoCalc(25800d);
double imp2 = prodCalc(4d);
double imp3 = prodCalc(5800d);

Pero de nuevo... si son diez tipos distintos de IVA, creará diez instancias de objeto? Es justo tanto empleo de memoria al tener un objeto completo solo para hacer una operación?
No lo creo... precísamente para solventar esta situación son útiles los closures.(Entre otras)

Un closure permite reflejar la simple funcionalidad de la pequeña clase que diseñamos anteriormente, sin incurrir en el overhead del objeto como tal. Para lograrlo, se requiere una forma de mantener un estado (en este caso, la tasa del impuesto) para no tener que estar pasando el parámetro de configuración en cada llamado. Además se requiere una operación sobre ese estado. Cómo lograrlo sin tener que hacer un objeto?

La operación como tal, se logra con un simple método. Pero este método debe ser configurable en su creación de manera que tenga un estado.

Para que se pueda configurar es necesario que exista una variable en un ambiente léxico (scope) superior al del método, de manera que esta variable pueda ser inicializada sin necesidad de llamar al método. Esta, será pues la variable que indica el "estado" del método. Pero igual, eso está dentro de una clase. De hecho en C# todo lo que ejecute, ha de estar dentro de una clase. La ventaja ahora, es que vamos a trabajar dentro de la misma clase de ejecución de nuestro flujo. No tendremos que crear más instancias.

Tanto el parámetro como la operación deben quedar encapsulados dentro de un mismo ambiente léxico dentro de la clase, para que tengamos la unidad requerida para poder generar instancias (pero no de una clase que es lo que queremos instanciar, sino de algo más liviano que llamaremos "enclosure"). Para esto es necesario poder hacer referencia al método dado.

En .NET qué nos permite hacer referencia a métodos? Si... correcto: los delegados. Entonces generamos un ambiente léxico que tenga al delegado referenciando al método y al parámetro de configuración. Obviamente si estamos dentro de una clase y no queremos generar otra, ese ambiente léxico es un nuevo método. Este método deberá retornar el delegado apuntando a la operación ya configurada como es debido, pero además el método al que apunte el delegado deberá estar declarado inmediatamente, de manera que el parámetro de configuración del mismo sea accesible a éste, desde el método que lo contiene.

En teoría suena bien. Pero cómo lograr que un delegado apunte a un método que se declara "en línea" junto con éste?

Ahí es donde entran los métodos anónimos. Son precisamente eso: Métodos declarados en línea donde son requeridos por los delegados. Generalmente, los delegados se han inicializado con el nombre de un método que cumpla el contrato de su firma. Ahora (Framework 2.0 en adelante), no es necesario declarar el método aparte para poderlo referenciar luego por un delegado (lo que impediría acceder al dichoso parámetro de configuración) sino en línea junto con la declaración del delegado:

delegate int MyDelegate(int a, int b);
MyDelegate myDel = delegate (int a, int b)
{
   .....
   return ....
};

No olvide el ";".

Ya con este conocimiento, podemos generar el método que queremos:

//Este delegado que permitira
delegate double IvaCalculator_(double tax);

IvaCalculator_ ivaCalcProducer(double tax)
{
return delegate(double amount)
   {
      return amount*tax;
   };
}

Exótico no?

Pero a pesar de ello, logramos lo deseado: El método toma una variable de configuración. Así tenemos estado y manipulación de estado. Lo que haría una clase. Entonces ahora cómo se operaría es muy parecido al uso de clases anterior; solo que ahora no hay overhead por creación de nuevos objetos. Lo que se crean son nuevos delegados. Uno por cada tipo de tasa en este caso. Ellos "cuestan" mucho menos que un objeto.

IvaCalc_ ivaCalc16=ivaCalcProducer(16);
IvaCalc_ ivaCalc25=ivaCalcProducer(20);
double imp1 = ivaCalc25(25800);
double imp2 = ivaCalc16(4);
double imp3 = ivaCalc16(5800);

Descargar Ejemplo Completo (Aplicación de consola en C# mostrando el uso de Closures)

Y listo. Así se construyen y usan los closures. Ahora ya puede ir y alardear un poco con su equipo de desarrollo acerca del misterioso "Closure"!!

 Open mind 4 a different Coding!!!