miércoles, 1 de octubre de 2014

Receta C# No. 4-16: Cómo Terminar un Proceso

Índice

0. Introducción
1. Problema
2. Solución
3. Discusión de la Solución
3.1 Básicos sobre la terminación de procesos
3.2 Método CloseMainWindow
3.3 Método Kill
4. Práctica: Código C#
5. Artefactos
6. Conclusiones
7. Glosario
8. Literatura & Enlaces

0. Introducción

En la receta C# número 4-15 aprendimos cómo iniciar un nuevo proceso, ahora tendremos la ocasión para comprender los básicos para realizar la operación de finalización de un proceso en ejecución utilizando varios de los elementos de programa disponibles en la clase Process (N:System.Diagnostics). Uno de los casos básicos de ejemplo de uso a los que recurriremos consiste en la iniciación del Bloc de notas de Windows y después de transcurrido cierta cantidad de tiempo terminar su ejecución abruptamente con el método Kill.

1. Problema

Después de haber iniciado un proceso o servicio en el sistema, requerimos también entender y usar un método para su finalización.

2. Solución

La clase System.Diagnostics.Process además de proveer los métodos de iniciación de un proceso, también facilita un conjunto de métodos para la finalización de un proceso o servicio. Como:
  • CloseMainWindow, y 
  • Kill
En la siguiente sección comprenderemos cómo podemos usar estos dos métodos para finalizar una aplicación que contiene una ventana principal, y para la terminación abrupta de procesos y servicios, respectivamente.

3. Discusión de la Solución

3.1 Básicos sobre la terminación de procesos

En la receta C# 4-15 (cfrCómo Iniciar un Nuevo Proceso) discutimos cómo iniciar un proceso utilizando varias de las versiones sobrecargadas del método Start. A partir de aquí debemos tener claro que la obtención de una instancia de proceso representada como un objeto Process nos facilitará a través de código administrado (cfrLiberación de Recursos y Finalizadores) su finalización normal o abrupta (en el peor de los casos).

Para lograr lo anterior, la clase Process [5] provee un conjunto de métodos para la obtención de los procesos o aplicaciones en ejecución. Estos métodos los podemos enumerar en la Figura 1 [1]:
Métodos de Process para obtener el conjunto de procesos en ejecución
Figura 1. Métodos de Process para obtener el conjunto de procesos en ejecución [1].
Luego, los pasos a seguir una vez que obtenemos la instancia (o representación lógica) del proceso, debemos seguir las siguientes estas acciones para terminar su ejecución:
  1. Invocar el método CloseMainWindow o Kill.
  2. En el caso de invocar al método CloseMainWindow, podemos validar que la finalización de la aplicación ha sido correcta a través del valor de tipo de retorno de este método (true o false).
  3. Si hemos invocado al método Kill lo que hará es terminar de forma forzada o abrupta el proceso o servicio.
Veamos con más detenimiento estos métodos de Process para comprender su uso y utilidad.

3.2 Método CloseMainWindow

El método CloseMainWindow [6] como mencionamos arriba finaliza un proceso que posee interfaz de usuario. El mensaje de terminación es enviado a la ventana principal de la aplicación. Esta es su firma:

public bool CloseMainWindow()

Descripción puntual:
  • Tipo de retorno:
    • bool: true si el mensaje de finalización fue enviado satisfactoriamente (esto no garantiza que el proceso asociado a la aplicación haya finalizado de forma correcta); false, si el proceso no posee una ventana principal o si la ventana principal ha sido deshabilitada (diálogo modal habilitado o en primer plano).
Los procesos o servicios en ejecución ejecutan un conjunto de rutinas que permiten al sistema operativo conocer su estado. Estas rutinas se invocan cada vez que un mensaje del sistema operativo Windows envía una señal al proceso. La invocación de CloseMainWindow causa que la aplicación solicite al usuario su finalización (por ejemplo si tiene un editor de texto en el que no se han guardado los cambios de edición). El usuario puede optar por continuar usando la aplicación y omitir el cierre solicitado por el método CloseMainWindow. Para evitar lo anterior tendremos que usar el método Kill (que discutiremos en la siguiente sub-sección) para la finalización forzada de la aplicación.

Comprendamos mejor cómo funciona este método recurriendo al siguiente ejemplo de uso (adaptado de [6]):

Dentro del bloque try (líneas 15-43) ocurren las siguientes acciones:
  • Línea 17: definimos una variable de tipo Process.
  • Línea 19: Iniciamos un nuevo proceso que ejecuta el Bloc de notas de Windows (notepad.exe).
  • Líneas 23-43: Ciclo de 5 iteraciones para comprobar la memoria física en uso del proceso recién creado.
  • Línea 29: Invocación al método Refresh [7] para descartar cualquier información de caché relacionada con el proceso.
  • Línea 32: Muestra en la salida estándar el consumo actual de memoria física por parte del proceso.
Continuando, en la línea 46 invocamos al método CloseMainWindow para solicitar el cierre de la ventana principal del Bloc de notas. Finalmente, con la invocación del método Close (línea 49) liberamos los recursos de sistema ocupados por el proceso recién terminado.

Compilación:


  1. csc /target:exe UsoCloseMainWindow.cs

Ejecución assembly:


  1. .\UsoCloseMainWindow.exe

> Prueba de ejecución:
Ejecución assembly UsoCloseMainWindow.exe
Figura 2. Ejecución assembly UsoCloseMainWindow.exe.

3.3 Método Kill

El método Kill [8] detiene de forma abrupta el proceso asociado a una instancia de Process. Esta es su firma:

public void Kill()


Como se manifiesta en [8], este método forza la terminación de un proceso, mientras que la invocación de CloseMainWindow (sección 3.2) únicamente solicita la terminación de la aplicación. Forzar la terminación de una aplicación puede causar la pérdida de datos de usuario (e.g., documento de texto con cambios en la edición) o de aplicación (e.g., pérdida de preferencias de aplicación, configuración, perfil de usuario).



Notas a tener en cuenta respecto a Kill:
Invocación asincrónica del método Kill
Figura 3. Invocación asincrónica del método Kill [8].

Generación de la excepción Win32Exception
Figura 4. Generación de la excepción Win32Exception [8].
Introduzcamos un ejemplo de uso para demostrar cómo podemos realizar la finalización abrupta del Bloc de notas mientras editamos un archivo de texto plano:

Archivo C# UsoKill.cs [enlace alternativo]:

En la línea 12 creamos un nuevo proceso para la ejecución del Bloc de notas de Windows. Más adelante, línea 15, realizamos una espera de 3 segundos para dejar que el proceso se ejecute en la transición de este tiempo, finalmente (línea 18) invocamos el método Kill para terminar abruptamente la aplicación.

Compilación:


  1. csc /target:exe UsoKill.cs

Ejecución assembly:


  1. .\UsoKill.exe

> Prueba de ejecución:
Ejecución assembly UsoKill.exe
Figura 5. Ejecución assembly UsoKill.exe.

En la animación (Figura 5) podemos observar que a pesar que tenemos datos texto editados en el documento de Bloc de notas, la invocación del método Kill no toma medidas en el guardado de datos, lo que se traduce en pérdida de datos de usuario.

4. Práctica: Código C#

Adaptemos el ejemplo encontrado en [1] para afianzar los conceptos presentandos en la sección anterior.

En este ejemplo creamos un proceso para la ejecución de una instancia del Bloc de notas de Windows. Pasados 5 segundos terminamos su ejecución. El primer intento de terminación de ejecución del Bloc de notas consiste en la invocación del método CloseMainWindow. Si la invocación previa retorna false, se debe a que el Bloc de notas aún se haya en ejecución. Consecuentemente, invocamos al método Kill para la terminación definitiva (abrupta) del Bloc de notas.

En la línea 15 creamos un proceso para la ejecución del archivo notepad.exe y la apertura de un archivo de texto plano llamado ArchivoTextoPlano.txt. En la línea 20 invocamos al método Sleep para esperar que el Bloc de notas finalice su ejecución. Con la sentencia if (línea 26) validamos que la invocación del método CloseMainWindow no ha sido satisfactoria, se ser así, entonces se invoca el método Kill (línea 32) para la terminación abrupta de la aplicación. En caso contrario, líneas 34-47, con una una sentencia if invocamos al método WaitForExit para dar un tiempo de gracia (2 segundos) a la terminación del Bloc de notas; si esto no funciona invocamos al método Kill (línea 45).

Compilación:


  1. csc /target:exe TerminacionBlocNotas.cs

Ejecución assembly:


  1. .\TerminacionBlocNotas.exe

> Prueba de ejecución:
Ejecución assembly TerminacionBlocNotas.exe
Figura 6. Ejecución assembly TerminacionBlocNotas.exe.

5. Artefactos

Conjunto de artefactos producidos en la preparación de esta receta C#:

6. Conclusiones

Hemos preparado esta receta con el propósito de entender los básicos para la terminación de un proceso o servicio. Demostramos el uso de los métodos CloseMainWindow (1) y el método Kill (2). Estos dos métodos permiten: (1) solicitud de terminación de la ventana principal de aplicación con interfaz gráfica, (2) terminar de forma forzada (abrupta) un proceso o servicio. Estos nuevos conocimientos serán parte de nuestra caja de herramienta y nos ayudarán terminar aplicaciones apropiados en contextos de construcción de software que ameritan su aplicación y uso. Antes de pasar a la serie de recetas dedicadas a Archivo, Directorio, y Entrada/Salida, escribiremos la última receta C# de Threads, Procesos, y Sincronización que consistirá en asegurar la ejecución concurrente de una única de instancia de una aplicación.

7. Glosario

  • Aplicación
  • Bloc de notas
  • Caché
  • Finalización
  • Proceso
  • Servicio
  • Terminación
  • Windows

8. Literatura & Enlaces

[1]: Visual C# 2010 Recipes by Allen Jones and Adam Freeman. Copyright 2010 Allen Jones and Adam Freeman, 978-1-4302-2525-6.
[2]: Receta C# No. 4-15: Cómo Iniciar un Nuevo Proceso | OrtizOL - Experiencias Construcción Software (xCSw) - http://ortizol.blogspot.com/2014/09/receta-csharp-no-4-15-como-iniciar-un-nuevo-proceso.html
[3]: Process Kill - CodeProject - http://www.codeproject.com/Articles/344488/Process-Kill
[4]: Liberación de Recursos y Finalizadores en C# | OrtizOLón Software (xCSw) - http://ortizol.blogspot.com/2013/10/liberacion-de-recursos-y-finalizadores.html
[5]: Process Class (System.Diagnostics) - http://msdn.microsoft.com/en-us/library/System.Diagnostics.Process(v=vs.110).aspx
[6]: Process.CloseMainWindow Method (System.Diagnostics) - http://msdn.microsoft.com/en-us/library/system.diagnostics.process.closemainwindow(v=vs.110).aspx
[7]: Process.Refresh Method (System.Diagnostics) - http://msdn.microsoft.com/en-us/library/system.diagnostics.process.refresh(v=vs.110).aspx
[8]: Process.Kill Method (System.Diagnostics) - http://msdn.microsoft.com/en-us/library/system.diagnostics.process.kill(v=vs.110).aspx


M

martes, 30 de septiembre de 2014

Receta T-SQL No. 2.2: Recuperar un Valor de la Base de Datos y Almacenarlo en una Variable

Índice

0. Introducción
1. Problema
2. Solución
3. Discusión de la Solución
4. Práctica: Código T-SQL
5. Conclusiones
6. Glosario
7. Literatura & Enlaces

0. Introducción

En esta ocasión conoceremos cómo recuperar un valor desde la base de datos y guardarlo en una variable. Los valores obtenidos nos podrá servir, por ejemplo, para realizar otra consulta para filtrar resultados en una cláusula WHERE. Veremos que una de las formas más naturales de obtención estos valores de interés consiste en la especificación de una cláusula que tenga como parte del predicado un valor de llave primaria o de una llave única. De forma rápida introduciremos el uso de la cláusula IF para validar que una consulta retorne únicamente un registro. ¡Empecemos!

1. Problema

Requerimos establecer el valor de una variable a partir de los valores recuperados de un registro.

2. Solución

El primer paso de la solución consiste en declarar una variable y a partir de una consulta recuperar el valor de una columna y almacenarlo en la variable de interés.

3. Discusión de la Solución

Ahora entremos a ver con más detalle los pasos a seguir para realizar para la obtención de un valor generado desde una consulta:

1. Declarar la variable:

DECLARE @NombreVariable Tipo_Dato;

2. Diseñar la consulta para recuperar el valor y asignarlo en la variable

SELECT @NombreVariable = Columna [,...]
FROM Tabla
WHERE ID_UNICO = 'Valor';

3. Usar el valor de la variable:

SELECT @NombreVariable [AS 'Nuevo Nombre Columna'];

En el primer paso lo único que hemos hecho es declarar la variable a través de la palabra DECLARE [2], especificamos el identificador de la variable -@NombreVariable-, seguidamente la asociación de un tipo de dato. En el segundo paso, usamos la variable recién declarada sobre la cláusula SELECT para asignar el valor recuperado por la consulta. Aquí hay que tener en cuenta que el predicado de la cláusula WHERE comprueba un valor contra una columna de llave primaria ID_UNICO. En el tercer paso, volvemos a usar una cláusula SELECT para mostrar el valor almacenado en la variable NombreVariable.



Respecto al segundo paso es importante tener esta advertencia dada en [1]:
Nota acerca del uso de llave primara o llave única
Figura 1. Nota acerca del uso de llave primara o llave única [1].

4. Práctica: Código T-SQL

En este primer ejemplo declararemos dos variables para contener las dos direcciones (columnas AddressLine1, y AddressLine2) de un una persona (tabla Dirección) con ID de dirección 53 (AddressID):

DECLARE @Direccion1 nvarchar(64);
DECLARE @Direccion2 nvarchar(64);

SELECT @Direccion1 = AddressLine1, @Direccion2 = AddressLine2
FROM Person.Address
WHERE AddressID = 53;

SELECT @Direccion1 AS 'Dirección No 1.', @Direccion2 AS 'Dirección No. 2';


Declaramos las variables @Direccion1, y @Direccion2; a continuación escribimos la consulta T-SQL para recuperar la dirección con ID igual a 53. Del registro recuperado, usamos los valores de las columnas AddressLine2, y AddressLine2 para almacenarlos en las variables @Direccion1, y @Direccion2, respectivamente. Al final, a través de la cláusula SELECT, mostramos los valores almacenados en las variables mencionadas. En la Figura 2 se muestran los resultados de esta consulta:
Resultado consulta valores de variables
Figura 2. Resultado consulta valores de variables.
Para este segundo ejemplo, usaremos la variable global @@ROWCOUNT [3] para validar que la consulta retorna solo un registro. Adicional a este caso, asignaremos valores iniciales a las variables en caso de que la consulta no retornara ningún valor:

DECLARE @Direccion1 nvarchar(64) = '5379 Treasure Island Way';
DECLARE @Direccion2 nvarchar(64) = '# 14';

SELECT @Direccion1 = AddressLine1, @Direccion2 = AddressLine2
FROM Person.Address
WHERE AddressID = 49861;

IF @@ROWCOUNT = 1
SELECT @Direccion1 AS 'Dirección No 1.', @Direccion2 AS 'Dirección No. 2'
ELSE
SELECT 'La consulta no retorno ningún registro o una cantidad superior a 1.';


A las variables @Direccion1, y @Direccion2 asignamos valores de inicialización. En la consulta pretendemos encontrar los datos de las dos direcciones para el ID de dirección -AddressID- igual a 49861. Más adelante, con la sentencia IF comprobamos que el número de registros devueltos por la consulta previa sea igual a 1. De ser así, se mostrarán los valores de las variables @Direccion1, y @Direccion2; en caso contrario, se mostrará el mensaje de advertencia en la sentencia ELSE. Resultado de estas operaciones en la Figura 3:
Resultado consulta valores de variables y validación de número de registros
Figura 3. Resultado consulta valores de variables y validación de número de registros.

5. Conclusiones

Hemos comprendido los básicos para la declaración de variables y asignación de valores por defecto. Estas variables pueden ser usadas como contenedores de valores de columnas generadas por una consulta. Para validar el número de registros que retorna una consulta, usamos la variable global @@ROWCOUNT en conjugación con la sentencia IF. En la próxima receta T-SQL, conoceremos más acerca de la sentencia IF...THEN...ELSE.

6. Glosario

  • Columna
  • Consulta
  • Llave primaria
  • Llave única
  • Sentencia
  • T-SQL
  • Variable

7. Literatura & Enlaces

[1]: SQL Server 2012 T-SQL Recipes - A Problem-Solucion Approach by Jason Brimhall, David Dye, Jonathan Gennick, Andy Roberts, and Wayne Sheffield. Copyright 2012 Jason Brimhall, David Dye, Jonathan Gennick, Andy Roberts, and Wayne Sheffield, 978-1-4302-4200-0.
[2]: DECLARE @local_variable (Transact-SQL) - http://msdn.microsoft.com/en-us/library/ms188927.aspx
[3]: @@ROWCOUNT (Transact-SQL) - http://msdn.microsoft.com/en-us/library/ms187316.aspx
[4]: Receta T-SQL No. 2-1: Declaración de Variables | OrtizOL - Experiencias Construcción Software (xCSw) - http://ortizol.blogspot.com/2014/09/receta-t-sql-no-2-1-declaracion-de-variables.html


J

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