lunes, 29 de septiembre de 2014

Métodos de Extensión en C#

Índice

0. Introducción
1. Métodos de Extensión
2. Encadenamiento de Métodos de Extensión
3. Resolución y Ambigüedad
3.1 Resolución
3.2 Métodos de extensión vs. métodos de instancia
3.3 Métodos de extensión vs. métodos de extensión
4. LINQ y Métodos de Extensión
5. Enumeraciones y Métodos de Extensión
6. Métodos de Extensión en IntelliSense de Visual Studio
7. Conclusiones
8. Glosario
9. Literatura & Enlaces

0. Introducción

A través de este nuevo artículo C#, estudiaremos los métodos de extensión. Comprenderemos su utilidad a través de ejemplos prácticos y simples. Veremos, en un principio que este tipo de extensión de miembros de un tipo de la biblioteca base de clases (BCL) o de los tipos definidos por el programador, comprenden una vía idónea para la agregación de funcionalidad extra a un tipo sin necesidad de definir un subtipo en una jerarquía de herencia. Además, estudiaremos los casos de encadenamiento de métodos de extensión, resolución y ambigüedad de métodos de extensión, y la agregación de funcionalidad a una enumeración a través de este tipo de extensión de funcionalidad de tipos.

1. Métodos de Extensión

La definición simple y directa de un método de extensión, consiste en un miembro método aislado (es decir, que reside en un archivo de definición de tipo y de miembros diferente al original) que extiende la funcionalidad que provee un tipo definido en la biblioteca base de clases de Microsoft .NET Framework y de cualquier otro tipo definido por el programador.

Lo anterior, quiere decir que al encontrarse la definición del método de extensión aislada en un archivo de definición de tipos de C# no se requiere la modificación del tipo objetivo a extender su funcionalidad. (Como dato histórico de versionamiento de C#, los métodos de extensión fueron agregados a partir de la versión 3.0 [1].)

Continuando, más técnicamente, se define a un método de extensión como un miembro static dentro de una clase la misma naturaleza; además, el primer parámetro de la firma del método de extensión DEBE seguir la siguiente sintaxis:

this Tipo_A_Extender Identificador_Paramentro

En términos generales de definición del tipo y de (los) método(s) de extensión, tendríamos el siguiente ejemplo:

[modificador_acceso] static class Nombre_Clase_Estatica
{
[modificador_acceso] static bool Nombre_Metodo(this Tipo_A_Extender nombre [,otros_parametros]
{
// Implementación
}
}

Un ejemplo particular de esta sintaxis de formación de métodos de extensión consiste en la definición del método EstaCapitalizada que determina si una cadena de caracteres representada como un objeto String sigue el modo de capitalización de primera letra en mayúscula:

A partir de esta definición, desde código cliente es posible invocar el método EstaCapitalizada de igual modo que cualquier otro método static:

Console.WriteLine ("Blog xCSw".EstaCapitalizada());

De acuerdo con [1], la compilación de la clase anterior, producirá una llamada al método estático usando el operador . (punto) para acceder al miembro estático de la clase AyudanteString:

Console.WriteLine(AyudanteString.EstaCapitalizada ("Blog xCSw"))


En general la traducción de métodos de extensión a llamadas de miembros estáticos de un tipo, funciona del siguiente modo [1]:

arg0.MetodoExtension(arg1, arg2, ...);  // Llamada a método de extensión

ClaseEstatica.Metodo(arg0, arg1, arg2, ...); // Llamada a método estático


Además de poder extender los métodos de clases, también es posible realizar esta operación interfaces. Como sigue:

public static T Primero<T>(this IEnumerable<T> secuencia)
{
   foreach (T elemento in secuencia)
        return elemento

   throw new InvalidOperationException ("No hay elementos.")
}

Como es evidente, también es mandatorio especificar la palabra clave this antepuesta al tipo de interfaz a extender, en este caso particular, IEnumerable<T>. Ahora desde una clase que implemente esta interfaz genérica, como String podemos hacer una llamada al método estático Primero:


Console.WriteLine ("xCSw".Primero()); // Salida: x

2. Encadenamiento de Métodos de Extensión

Para empezar podemos recordar que el proceso de encadenamiento de métodos consiste en realizar llamadas consecutivas a métodos a través del operador . (punto) sobre una misma sentencia:

objeto.Metodo1().Metodo2().MetodoN()


Las llamadas a métodos empiezan en el orden izquierda a derecha, es decir, que primero se ejecuta el enésimo método MetodoN, luego con el resultado generado por éste, se invoca Metodo2, y así sucesivamente.


Podemos poner en práctica este concepto agregando otro método de extensión para pluralizar una cadena de caracteres: Pluralizar.

Archivo C# AyudanteString.cs [enlace alternativo]:


Con los métodos de extensión:
  • EstaCapitalizada, y
  • Pluralizar
Podemos hacer invocaciones encadenadas de la la siguiente manera:

string str1 = "cadena".Pluralizar().Capitalizar();
string str2 = AyudanteString.Capitalizar (AyudanteString.Pluralizar("cadena"))


Aquí vale resaltar las siguientes diferencias entre las expresiones para capitalizar y pluralizar una cadena de caracteres:
  • La primera expresión invoca los métodos de extensión para la clase String.
  • La segunda expresión invoca explícitamente métodos estáticos de la clase estática AyudanteString.

3. Resolución y Ambigüedad

3.1 Resolución

Antes de que podamos usar un método de extensión es necesario que incluyamos dentro de las directivas de importación de namespaces la ubicación de la clase estática que incluye los métodos de extensión para un tipo de dato particular.

Ttomemos como referencia los métodos de extensión definidos en la clase AyudanteString, e incluyamoslos en una aplicación de prueba:

En la línea 2 se explicita la inclusión de los artefactos de namespace Articulos.CSharp.MetodosExtension. Luego, sobre la línea 8, sobre la literal de cadena de caracteres "cadena" invocamos de forma encadenada los métodos Pluralizar y Capitalizar (orden de llamada).

Compilación:


1. Creación de la librería a partir del archivo AyudanteString.cs de la sección anterior:


  1. csc /target:library /out:AyudanteString_2.dll AyudanteString_2.cs

2. Compilación de la clase de prueba:


  1. csc /target:exe /r:AyudanteString_2.dll PruebaAyudanteString.cs

Ejecución assembly:


  1. .\PruebaAyudanteString.exe

> Prueba de ejecución:
Ejecución assembly PruebaAyudanteString.exe
Figura 1. Ejecución assembly PruebaAyudanteString.exe.

Incluir el nombre de espacio con una sentencia using es mandatario para poder utilizar los métodos Pluralizar y Capitalizar, de lo contrario se generará el error de compilación CS1061 [3]:
Intento de compilación PruebaAyudanteString.cs
Figura 2. Intento de compilación de PruebaAyudanteString.cs.

3.2 Métodos de extensión vs métodos de instancia

Los métodos miembro de instancia o estáticos de un tipo de dato tienen mayor precedencia que cualquier de los métodos de extensión definidos en clases estáticas. Si por ejemplo definimos la clase ClaseA la cual tiene definido el método de instancia MetodoA, cualquier método de extensión homónimo (sobrecargado) tendrá menor precedencia en invocaciones desde código cliente. Expresemos esto con código fuente C#:

class ClaseA
{
    public void MetodoA(object x)
    { 
        // Implementación...
    }
}

La clase estática ExtensionesClaseA con un método de extensión:

static class ExtensionesClaseA
{
    public static void MetodoA(this ClaseA param1, int x)
    {
        // Implementación...
    }
}


Tal como hemos dicho, el método MetodoA definido en la clase concreta ClaseA tendrá mayor precedencia sobre el definido en la clase static ExtensionesClaseA.


En [1] nos advierten que la única manera de acceder al método de extensión MetodoA es usar la siguiente sintaxis:

ExtensionesClaseA.MetodoA(...)

3.3 Métodos de extensión v.s. métodos de extensión

Asumamos que contamos con dos clases estáticas que contienen métodos de extensión homónimos, luego para desambiguar la invocación o llamadas a estos métodos desde código cliente se debe utilizar la sintaxis:

ClaseEstatica_1.MetodoHomonimo(...)

ClaseEstatica_2.MetodoHomonimo(...)


Sin embargo, hay que tener en consideración que sino usamos el método de desambiguación propuesto, el compilador ejercerá precedencia sobre el método con los argumentos más específicos.


Este ejemplo propuesto y adaptado de [1] nos permitirá observar mejor el comportamiento descrito:

public class AyudanteString
{
    public static bool EstaCapitalizada(this String s) {...}
}

public class AyudanteObject
{
    public static bool EstaCapitalizada(this object s) {...}
}


La invocación del método de extensión EstaCapitalizada se hace de forma implícita como cualquier otro método de la clase String:


bool prueba1 = "xCSw".EstaCapitalizada();


En contraste con el método EstaCapitalizada de la clase estática AyudanteObject, se debe explicitar el tipo (i.e., AyudanteObject) y la llamada al método correspondiente:


bool prueba2 = (AyudanteObject.EstaCapitalizada("xCSw"));


E inclusive [1]:
«Classes and structs are considered more specific than interfaces.»

4. LINQ y Métodos de Extensión

En la biblioteca base de clases de .NET los métodos de extensión se usan extensivamente sobre los operadores de consultas de LINQ [4]. Las clases que se ven beneficiadas por esta característica de adicionamiento funcional son las interfaces:
  • System.Collections.IEnumerable [5], y 
  • System.Collections.IEnumerable<T> [6]
Como ya mencionamos, la resolución de métodos de extensión se lleva a cabo con la inclusión del (o los) nombre de espacio con la directiva using; en este para poder hacer uso de los métodos de extensión que provee el nombre de espacio de LINQ tendremos que hacer obligatoriamente esto:

using System.Linq;

Veamos un ejemplo en donde ordenamos los elementos de un arreglo a través de la invocación del método de extensión OrderBy. El método OrderBy hace uso de una expresión lambda (Expresiones Lambda) para ordenar de mayor a menor los elementos contenidos en el arreglo:

En la línea 2 importamos los elementos de programa del nombre de espacio System.Linq. En la línea 11 declaramos e inicializamos el arreglo de enteros de 32 bits (int) con 7 elementos desordenados. Sobre la línea 14 invocamos al método de extensión OrderBy al que le pasamos como argumento el predicado a modo de una expresión lambda que simplemente se traduce en una iteración por lo elementos del arreglo para su ordenamiento. El resultado de la operación anterior lo localizamos en la variable anónima (var) arregloOrdenado. Con el ciclo foreach (líneas 18-21) mostramos en la salida estándar los elementos del arreglo ordenados de mayor a menor.

Compilación:


  1. csc /target:exe MetodoExtensionOrderBy.cs

Ejecución assembly:


  1. .\MetodoExtensionOrderBy.exe

> Prueba de ejecución (ideone.com).

> Prueba de ejecución (local):
Ejecución assembly MetodoExtensionOrderBy.exe
Figura 3. Ejecución assembly MetodoExtensionOrderBy.exe.

5. Enumeraciones y Métodos de Extensión

Podemos definir métodos de extensión para las enumeraciones (Enumeraciones) que son parte de la BCL o las propias definiciones hechas por el programador. Esto lo podemos demostrar a través del siguiente ejemplo en el que extenderemos la funcionalidad de la enumeración Nota:

En las líneas 6-13 definimos la enumeración Notas con las constantes que representan las notas de un curso. Con la clase static ExtensionesNotas (líneas 17-28) definimos el método de extensión Paso (líneas 24-27) para determinar si una nota de un examen dada igual o supera el mínimo para pasar el examen. En el código cliente de Main (líneas 32-38) definimos dos variables de la enumeración Notas y comprobamos a través del método de extensión Paso si las dos notas de exámenes de ejemplo ameritan superar el examen.

Compilación:


  1. csc /target:exe EnumeracionMetodosExtension.cs

Ejecución assembly:


  1. .\EnumeracionMetodosExtension.exe

> Prueba de ejecución (ideone.com).

> Prueba de ejecución (local):
Ejecución assembly EnumeracionMetodosExtension.exe
Figura 4. Ejecución assembly EnumeracionMetodosExtension.exe.

6. Métodos de Extensión en IntelliSense de Visual Studio co

En Visual Studio contamos con la funcionalidad en IntelliSense para detectar los métodos de extensión disponibles para un determinado tipo de dato. En el Figura 5 se muestra la detección automática por parte de IntelliSense para el método OrderBy (además de su descripción de firma) para el arreglo enteros (ver ejemplo de la sección 4).
IntelliSense - Reconocimiento de métodos de extensión
Figura 5. IntelliSense - Reconocimiento de métodos de extensión.

7. Conclusiones

El desarrollo de este artículo nos ha permitido conocer la gran utilidad que nos brinda los métodos de extensión para extender la funcionalidad de tipos definidos en la BCL de .NET Framework, y también para los tipos definidos por el propio programador. Escribimos varios ejemplos en C# que nos afianzó el conocimiento práctico sintáctico. En futuras entregas de artículos LINQ usaremos de manera intensiva cada uno de estos métodos, de este modo tendremos a disposición más herramientas programáticas para resolver problemas de forma más efectiva y elegante. En el próximo artículo C# estudiaremos los aspectos avanzados de los tipos anónimos.

8. Glosario

  • BCL
  • Clase estática
  • IDE
  • IntelliSense
  • LINQ
  • Método
  • Método de extensión
  • Namespace
  • Nombre de espacio
  • Visual Studio

9. Literatura & Enlaces

[1]: C# 5.0 in a Nutshell by Joseph Albahari and Ben Albahari. Copyright 2012 Joseph Albahari and Ben Albahari, 978-1-449-32010-2.
[2]: Extension Methods (C# Programming Guide) - http://msdn.microsoft.com/en-us/library/bb383977.aspx
[3]: Compiler Error CS1061 - http://msdn.microsoft.com/en-us/library/bb383961.aspx
[4]: LINQ (Language-Integrated Query) - http://msdn.microsoft.com/en-us/library/bb397926.aspx
[5]: IEnumerable Interface (System.Collections) - http://msdn.microsoft.com/en-us/library/system.collections.ienumerable(v=vs.110).aspx
[6]: IEnumerable(T) Interface (System.Collections.Generic) - http://msdn.microsoft.com/en-us/library/9eekhta0(v=vs.110).aspx
[7]: Expresiones Lambda en C# - Parte 1: Introducción a las Expresiones Lambda | OrtizOL - Experiencias Construcción Software (xCSw) - http://ortizol.blogspot.com/2014/06/expresiones-lambda-en-csharp-parte-1-introduccion-a-las-expresiones-lambda.html
[8]: OrtizOL - Experiencias Construcción Software (xCSw): Expresiones Lambda en C# - Parte 1: Introducción a las Expresiones Lambda - http://ortizol.blogspot.com/2013/09/var-variables-locales-de-tipo-deducido.html
[9]: Enumeraciones en C# - Parte 1 | OrtizOL - Experiencias Construcción Software (xCSw) - http://ortizol.blogspot.com/2014/05/enumeraciones-en-c-parte-1.html


J

No hay comentarios:

Publicar un comentario

Envíe sus comentarios, dudas, sugerencias, críticas. Gracias.