lunes, noviembre 17, 2008

Genericidad en vez de typeof

Permítame un breve descanso entre tantos disparates. He estado usando últimamente una técnica sencilla y conocida para identificar posibilidades de simplificación en código heredado de .NET v1.1. La técnica consiste en buscar métodos, en el código fuente, que reciban un parámetro de tipo System.Type, para considerar la posibilidad de convertir dicho método en un método genérico.
El caso típico se encuentra casi siempre en el "gestor" de ventanas:
public interface IFormManager
{
Form ShowForm(Type formType);
}
ShowForm recibe un tipo de ventana como parámetro, y debe encontrar una ventana abierta cuyo tipo sea exactamente el especificado. Si la encuentra, debe activarla y pasarla a primer plano. En caso contrario, debe crear una ventana de este tipo, y mostrarla. La regla que he mencionado establece que el método anterior debería transformarse en algo parecido a lo siguiente:
public interface IFormManager
{
T ShowForm<T>()
where T: Form, new();
}
Puntos a destacar:
  1. El parámetro de tipo T se ha restringido de dos maneras. En primer lugar, debe ser una clase descendiente de Form. Usted, naturalmente, puede exigir de sus ventanas que desciendan de alguna clase más concreta, o que implementen algún tipo de interfaz general, o ambas cosas a la vez.
  2. Por otra parte, he exigido que las ventanas tengan un constructor sin parámetros. Este es un requisito opcional, que no valdrá en todos los casos, pero que he incluido aquí para simplificar un poco el código.
  3. Observe que será imposible, al utilizar este método, que el compilador pueda "inferir" el parámetro de tipo. Si no ha trabajado aún con la inferencia de tipos en llamadas a métodos genéricos, traduzca la oración anterior así: "siempre tendremos que indicar explícitamente el tipo de ventana" (algo bastante razonable).
¿Qué hemos ganado con esta transformación? Antes, para mostrar una ventana, teníamos que escribir código como el siguiente:
// "manager" implementa "IFormManager"
MiVentana v = manager.ShowForm(
typeof(MiVentana)) as MiVentana;
Si no efectuamos la conversión de tipos, obtendremos como resultado un valor de tipo Form, sin más adornos. En cambio, con la versión genérica obtendremos el tipo originalmente deseado:
MiVentana v = manager.ShowForm<MiVentana>();
Para cerrar el círculo, le mostraré una sencilla implementación inicial del tipo de interfaz:
public class FormManager : IFormManager
{
private List<Form> forms = new List<Form>();
 
public T ShowForm<T>()
where T: Form, new()
{
foreach (var f in forms)
if (f.GetType() == typeof(T))
{
f.Show();
f.BringToFront();
return f as T;
}
var frm = new T() { Visible = true };
frm.FormClosed += (sender, e) =>
forms.Remove((Form)sender);
forms.Add(frm);
return frm;
}
}
Creo que la primera parte, la que ejecuta el bucle, es fácil de comprender. Luego he complicado un poco las cosas: observe que puedo ejecutar el constructor sin parámetros del tipo T gracias a las restricciones establecidas sobre el parámetro genérico. Si mis ventanas no se pudiesen construir de esa manera, sino que requiriesen parámetros, entonces tendría que utilizar un poco de reflexión. Tenga presente, no obstante, que la ejecución de un constructor sin parámetros sobre un parámetro de tipo genérico es implementada por el compilador como una llamada al API de reflexión (lo sé porque Freya hace exactamente lo mismo).
¿Ve esas llaves tras la llamada al constructor? Se trata simplemente de una asignación a la propiedad Visible del formulario recién creado. Podía haberla sustituido por una llamada al método Show, pero quise mostrar un ejemplo de inicialización de objetos. Luego, resolvemos un problema más serio: FormManager lleva una lista de las ventanas abiertas (o abiertas e invisibles). Las ventanas se añaden al ser creadas, pero ¿cuándo se eliminan de esta lista? Para ello, asociamos un manejador al evento FormClosing de cada nueva ventana. En vez de crear el manejador como un método independiente, he preferido utilizar una expresión lambda.

Comentarios adicionales: ¿para qué hace falta una interfaz IFormManager? ¿No sería más sencillo diseñar ShowForm como un método estático, dentro de una clase estática? La clave del uso de IFormManager está en la posibilidad de usar diferentes implementaciones según nos convenga. La manía de recurrir a métodos estáticos a la primera es una manifestación de lo que yo llamo "la fiebre del singleton". Es cierto que, al final, nuestro gestor de ventana tendrá que proveer algún singleton, como la variable "manager" de mi ejemplo de uso. Pero la aparición del singleton debe retrasarse todo lo que podamos.

Etiquetas: , ,

5 Comments:

Blogger Andrés Ortíz said...

Hola Ian

Interesante ejemplo.

Hablando de estas buenas prácticas, o como se le llame a este tipo de ejemplos, queria preguntarte o que me dieras tú opinión sobre FXCOP.

Utilizandolo sobre tú ejemplo anterior, lo único extraño que reporta es esto:

Warning 4 CA1004 : Microsoft.Design : Consider a design where 'FormManager.ShowForm T()' doesn't require explicit type parameter 'T' in any call to it. D:\DotNet\Ian1\Ian1\Form1.cs 33 Ian1

Alguna idea o explicación qué significa eso?

Estoy muy interesado en utilizar FXCOP en mi equipo de desarrollo, porque creo que aporta valiosos análisis, bajo buenas prácticas que vale la pena en determinados momentos del desarrollo.

Por cierto, coloca ejemplos completos, sería genial poner a correr esto y verlo funcionando. Hace rato que no tienes ninguna entrada de pildoras, los libros .NET que muestras ya están bastante obsoletos. Es muy bueno el comentario que haces de libros para tomar buenas desiciones de compra. No te parace que estan saliendo una tonelada de libros terriblemente malos?

Cuando nos ofreces un artículo en DotNetMania, MSDN o alguna cosa de esas. Que bueno sería leerte por ahi. Sería geníal ver a Freya en un buen sample, o grabate algo y subelo a YouTube, ahi para que tú ejercicio de aportar conocimiento sea cada día más interactivo!!

Un abrazo

domingo, noviembre 23, 2008 2:14:00 a. m.  
Blogger Ian Marteens said...

Alguna idea o explicación qué significa eso?

FXCop ha detectado que el método no puede aprovechar la "inferencia de tipos". Pero eso es algo normal, y hay montones de métodos en el CLR que tampoco pueden. Este es uno de esos casos en el que puedes apagar esa verificación en particular para esa clase. Microsoft lo hace, además, en las clases que genera con sus herramientas para Visual Studio (échale un vistazo al código de un typed dataset: no se trata de advertencias sobre parámetros genéricos, de todos modos).

Cuando nos ofreces un artículo en DotNetMania, MSDN o alguna cosa de esas.

Me he tirado un año programando "en serio": si no lo hiciera de vez en cuando, la "obsolescencia técnica" acabaría conmigo. Es un riesgo que corre todo aquel que se dedica a dar cursos. Ahora es el momento de sacarle partido a lo vivido durante ese año.

domingo, noviembre 23, 2008 3:40:00 p. m.  
Blogger Ian Marteens said...

Cuando nos ofreces un artículo en DotNetMania, MSDN o alguna cosa de esas

... pues la verdad, y para serte sincero, no me apetece demasiado. Es cierto que DotNetMania paga más que decentemente las colaboraciones, pero en comparación con la productividad de impartir un curso presencial, para no mencionar ya un curso a distancia, no merece la pena.

Ya sé que la "comunidad" se ha acostumbrado a pillar información regalada de aquí y de allá, sin que le cueste un duro. Me parece un deseo natural. Pero tan natural como lo es el deseo de COBRAR POR EL TRABAJO DE UNO.

Al fin y al cabo, que yo sepa, ninguno de los presentes estamos curando el cáncer o eliminando el hambre del mundo.

lunes, noviembre 24, 2008 9:32:00 a. m.  
Blogger Andrés Ortíz said...

Jajaja ok sigamos con el blog, pero eso si no te pierdas tanto que tus apuntes son bastante buenos. Y sacate un buen libro, ojala ese que comentabas de cosas más empresariales, arquitectura, una cosa de esas que sirvan un buen tiempo, los libros técnicos están teniendo ese grave problema, C# 1.0 ahora c# 4.0, mejor dejemosle eso a MSDN.

viernes, noviembre 28, 2008 9:19:00 p. m.  
Blogger Ian Marteens said...

ahora c# 4.0

La verdad es que, si se limitan a los anuncios, no habrá grandes diferencias entre el lenguaje de la 3.0 y la 4.0. Los cambios afectan a zonas muy concretas de funcionalidad, sobre todo lo de los lenguajes dinámicos, y lo de la co/contravariancia es más teórico que cualquier otra cosa (sobre todo porque la parte útil, que es la covariancia, se limita a delegados y tipos genéricos).

sábado, noviembre 29, 2008 12:58:00 p. m.  

Publicar un comentario

<< Home