Autor en Google+
Saltar al contenido

Artículos, tutoriales, trucos, curiosidades, reflexiones y links sobre programación web ASP.NET Core, MVC, Blazor, SignalR, Entity Framework, C#, Azure, Javascript... y lo que venga ;)

15 años online

el blog de José M. Aguilar

Inicio El autor Contactar

Artículos, tutoriales, trucos, curiosidades, reflexiones y links sobre programación web
ASP.NET Core, MVC, Blazor, SignalR, Entity Framework, C#, Azure, Javascript...

¡Microsoft MVP!
Mostrando entradas con la etiqueta mono. Mostrar todas las entradas
Mostrando entradas con la etiqueta mono. Mostrar todas las entradas
martes, 7 de octubre de 2008
A veces puede resultar interesante acceder desde una página de contenidos a propiedades de su página maestra. Hoy he encontrado un truco para conseguir este acceso con la seguridad que proporciona un tipado fuerte.

Como sabemos, la propiedad Master de las páginas de contenidos, a través de la cual es posible acceder a la página maestra, es por defecto del tipo MasterPage. Esto es así porque todas las masters que creamos heredan de esta clase, y es una abstracción bastante acertada la mayoría de las veces. De hecho, es perfectamente posible hacer un cast al tipo correcto desde el código de la página para acceder a alguna de las propiedades públicas que le hayamos definido, así:

protected void Page_Load(object sender, EventArgs e)
{
MaestraContenidos master = Master as MaestraContenidos;
master.Titulo = "Título";
}
 
Pues bien, mediante la directiva de página MasterType es posible indicar de qué tipo será esta propiedad Master, de forma que no será necesario hacer el cast. En la práctica, en el ejemplo anterior, podríamos hacer directamente Master.Titulo="Título", sin realizar la conversión previa.

La directiva podemos utilizarla en el archivo .ASPX, haciendo referencia al archivo donde está definida la página maestra cuyo tipo usaremos para la propiedad:

<%@ MasterType VirtualPath="~/site1.master" %>
 
O también podemos hacerlo indicando directamente el tipo (ojo, que hay que incluirlo con su espacio de nombres completo):

<%@ MasterType TypeName="ProyectoWeb.MaestraContenidos" %>
 

Por último, algunas contraindicaciones. Si váis a usar esta técnica, tened en cuenta que:
  • si decidís cambiar la página maestra en tiempo de ejecución, en cuanto intentéis acceder a la propiedad Master, vuestra aplicación reventará debido a que el tipo no es el correcto.

  • si cambiáis la maestra a la que está asociada una página de contenidos, tenéis que acordaros de cambiar también la directiva MasterType de la misma para que todo funcione bien.


Publicado en: www.variablenotfound.com.

Estos contenidos se publican bajo una licencia de Creative Commons Licencia Reconocimiento-No comercial-Compartir bajo la misma licencia 3.0 España de Creative Commons

sábado, 15 de marzo de 2008
Ayer se anunció oficialmente la aparición de la versión 1.0 a través de la web del Proyecto Mono, en el blog de Miguel de Icaza y por supuesto, en la web del equipo de desarrollo de MonoDevelop.

Se trata de un entorno de desarrollo integrado (IDE) para la plataforma .NET en el que el equipo del proyecto Mono anda trabajando desde hace varios años. Su objetivo inicial era facilitar una herramienta libre y gratuita para Linux, MacOS e incluso Windows, que permita la creación de aplicaciones en C# y otros lenguajes .NET, aunque después ha evolucionado hasta convertirse en una plataforma extensible sobre la que podría encajar cualquier tipo de herramienta de desarrollo.

Las principales características de la herramienta son:


  • Se trata de un entorno muy personalizable, permitiendo la creación de atajos de teclado, la redistribución de elementos en pantalla o inclusión de herramientas externas.
  • Soporta varios lenguajes, como C#, VB.Net o C/C++ o incluso Boo y Java (IKVM) a través de plugins externos.
  • Ayuda en la escritura del código (auto-completar, información de parámetros... al más puro estilo intellisense).
  • Incluye herramientas de refactorización, como renombrado de tipos y miembros, encapsulación de campos, sobreescritura de métodos o autoimplementación de interfaces.
  • Ayudas en la navegación a través del código, como saltar a las declaraciones de variables, o búsquedas de clases derivadas.
  • Diseñador de interfaces para aplicaciones GTK# y soporte para librerías de widgets.
  • Control de versiones integrado, con soporte para Subversion.
  • Pruebas unitarias integradas, utilizando NUnit.
  • Soporte para proyectos ASP.Net.
  • Servidor web integrado (XSP) para pruebas.
  • Explorador y editor de bases de datos integrados (aún en beta).
  • Integración con Monodoc, para la documentación de clases.
  • Soporte para makefiles, tanto para generación como para sincronización.
  • Soporte para formatos de proyecto Visual Studio 2005.
  • Sistema de empaquetado que genera tarballs, código fuente y binarios.
  • Línea de comandos para generar y gestionar proyectos.

  • Soporte para proyectos de localización.
  • Arquitectura extensible a través de plugins.

Información mucho más detallada, eso sí, en inglés, en la web del proyecto.

Publicado en: http://www.variablenotfound.com/.

Estos contenidos se publican bajo una licencia de Creative Commons Licencia Reconocimiento-No comercial-Compartir bajo la misma licencia 3.0 España de Creative Commons

jueves, 6 de diciembre de 2007
Por cortesía de Mergia, ya es posible ver en vivo el sitio web de demostración de NiftyDotNet, el componente que ha supuesto mi primera aportación al mundo del software libre.

NiftyDotNet, para el que no lo conozca, es un componente ASP.Net para las plataformas Mono y Microsoft, que encapsula la librería javascript Nifty Corners Cube para conseguir redondear las esquinas de elementos de una página web de una forma realmente sencilla. Basta con arrastrar los controles sobre un Webform, indicarles los elementos que se verán afectados y listo.

Ejemplo de uso de NiftyDotNet

Publicado en: Variable Not Found.

Estos contenidos se publican bajo una licencia de Creative Commons Licencia Reconocimiento-No comercial-Compartir bajo la misma licencia 3.0 España de Creative Commons

domingo, 11 de noviembre de 2007
Las primeras versiones de la plataforma .Net introdujeron el PostBack como el mecanismo de recarga de una página gracias al cual era posible la persistencia del estado de controles y la interacción con ellos de forma muy sencilla y potente en el lado del servidor, modificando la filosofía de programación de webs que habíamos usado hasta entonces (ASP clásico, CGIs...).

Sin embargo, si querías usar los controles de servidor en todo su esplendor te veías obligado a meter tareas completas dentro de una misma página; así, aquellas que presentaban funcionalidades relativamente complejas se convertían en batiburrillos donde se incluían formularios de entrada de datos, código de validaciones, formularios de salida de información, etc. Por ejemplo, podíamos tener en un mismo Webform paneles de introducción de criterios de búsqueda, paneles de información o errores, paneles con un grid para mostrar los resultados e incluso otro panel para mostrar una vista ampliada de un registro concreto, y jugar con las visibilidades para mostrar unos u otros en función de los pasos dados por el usuario.

Desde ASP.Net 2.0 es posible realizar los llamados "Cross Page PostBacks", o PostBacks hacia páginas distintas de la inicial, permitiéndonos separar funciones y hacer nuestro código un poco más legible, estructurado y reutilizable. El ejemplo anterior podría ser diseñado en tres páginas independientes, separando las distintas vistas, una solución mucho más limpia:
  • en la primera estaría el formulario de introducción de criterios.
  • en la segunda podríamos simplemente tomar los parámetros de la anterior y mostrar un listado con los resultados obtenidos, o bien un mensaje de información si no hay coincidencias.
  • en la tercera podríamos montar una ficha con los datos detallados de un elemento, que habría sido seleccionado por el usuario en la página anterior.
¿Y cómo se hace? Sencillo.

En primer lugar, hay que modificar el comportamiento de los botones de acción (o similar, de hecho también podría ser ImageButtons o LinkButtons) para que realicen el envío a otra página, indicándole su URL en la propiedad PostBackUrl. Eso hará que, tras su pulsación, el control sea enviado inmediatamente al archivo .aspx que se defina en la misma.

Un aspecto muy a tener en cuenta es el término "inmediatamente": el control es transferido a la nueva página sin ejecutar posibles validaciones en servidor, por lo que podrían llegar gazapos a la segunda. Sin embargo, si los validadores son en cliente, se ejecutarán con normalidad (si el usuario no tiene desactivado Javascript, of course!).

Ya en la página destino, hay varias opciones para recuperar el valor de los controles de la página invocadora. La primera de ellas, la más sencilla, consiste en usar la propiedad PreviousPage de la clase Page para obtener una referencia hacia la página de origen y obtener sus controles de la siguiente forma:

if (PreviousPage != null)
{
TextBox txt =
(TextBox)PreviousPage.FindControl("TextBox1");
Label1.Text = txt.Text; // Por ejemplo
}

Sin embargo, no es una solución aconsejable, puesto que un simple cambio de nombre del control en la página origen provocaría un error en tiempo de ejecución, puesto que FindControl no sería capaz de encontrarlo con el nombre "TextBox1".

Hay otra forma mucho más potente, que consiste en especificar en la página destino un tipado fuerte para la de origen usando en su archivo .aspx la siguiente directiva:

<%@ PreviousPageType VirtualPath="~/PaginaOrigen.aspx" %>

De esta forma, cualquier referencia a PreviousPage en la página destino se considerará automáticamente del tipo de la página de origen, pudiendo acceder a sus propiedades de forma directa, sin necesidad de castings ni nada parecido. Sin embargo, todo sea dicho, debido a que el nivel de visibilidad de los controles de un Webform son protected, no podemos acceder directamente a su contenido, debemos usar accesores o propiedades públicas para ello.

Por ejemplo, podríamos definir en la clase (codebehind) de la página de origen una propiedad como la siguiente como accesor al contenido de un control TextBox llamado txtTexto:

public string Texto
{
get {return txtTexto.Text; }
}

Y en la página destino, una vez introducida la directiva descrita anteriormente especificando la URL de la página de origen, podríamos hacer lo siguiente:

if (PreviousPage != null)
{
Label1.Text = PreviousPage.Texto; // Por ejemplo
}

En resumen, esta técnica permite estructurar mucho mejor nuestras aplicaciones web, aunque sea a cambio de realizar un sobreesfuerzo en el desarrollo. En mi opinión puede valer la pena su uso en páginas que generen demasiadas vistas diferentes para ser introducidas en un único Webform sin comprometer la mantenibilidad, o cuando resulte interesante de cara a la reutilización de páginas.

No es conveniente, a mi entender, su utilización indiscriminada en formularios simples, pues precisamente los PostBacks fueron introducidos en ASP.Net para evitar la proliferación innecesaria de páginas sueltas (.aspx) y facilitar el mantenimiento del estado en la inherentemente desconectada tecnología Web.

Estos contenidos se publican bajo una licencia de Creative Commons Licencia Reconocimiento-No comercial-Compartir bajo la misma licencia 3.0 España de Creative Commons

miércoles, 3 de octubre de 2007
Hasta hoy pensaba que la página maestra (MasterPage) de un .aspx era una propiedad que se definía en tiempo de diseño, concretamente en las directivas de página, y no era posible modificarlo de forma programática al encontrarse la declaración a un nivel tan bajo:

<%@ Page Language="C#" MasterPageFile="~/Site1.Master"
AutoEventWireup="true" CodeBehind="WebForm1.aspx.cs"
Inherits="PruebasVariadas.WebForm1"
Title="Página sin título" %>
 
Nada más lejos de la realidad. Aunque con algunas restricciones, es perfectamente posible alterar en tiempo de ejecución la MasterPage de la página, haciendo posible cosas como, por ejemplo, modificar completamente la apariencia y disposición de los elementos sobre la marcha.

Por ejemplo, el siguiente código hace que una página .aspx utilice una maestra u otra en función de si la hora actual del servidor es par:

// C#:
protected override void OnPreInit(EventArgs e)
{
if (DateTime.Now.Hour % 2 == 0)
this.MasterPageFile = "~/Maestra1.master";
else
this.MasterPageFile = "~/Maestra2.master";

base.OnPreInit(e);
}


' VB.NET:
Protected Overrides Sub OnPreInit(ByVal e As System.EventArgs)
If DateTime.Now.Second Mod 2 = 0 Then
Me.MasterPageFile = "~/Maestra1.master"
Else
Me.MasterPageFile = "~/Maestra2.master"
End If
MyBase.OnPreInit(e)
End Sub

 
Las restricciones a las que me refería antes están plasmadas en esta porción de código: sólo puede cambiarse en el evento PreInit o antes. A efectos prácticos, sobreescribir OnPreInit (como en el ejemplo anterior) es una buena solución.

Estos contenidos se publican bajo una licencia de Creative Commons Licencia Reconocimiento-No comercial-Compartir bajo la misma licencia 3.0 España de Creative Commons

jueves, 27 de septiembre de 2007
Los que vamos evolucionando a la vez que lo hace la plataforma .Net (bueno, unos cuantos pasos por detrás ;-)), a veces se nos hace difícil superar nuestras costumbres y adaptarnos a nuevos métodos o utilidades que, sin duda, vienen para facilitarnos la vida y ofrecen mejores soluciones a problemas comunes que las que utilizamos de forma habitual.

Un ejemplo lo tenemos con los métodos estáticos xxx.Parse(), útiles para convertir de un string a un tipo valor, como puede ser int32 o boolean. Desde el principio de los tiempos usamos, para obtener un entero desde su representación como cadena, construcciones de tipo:
string num = "123";
int i = 0;
try
{
i = int.Parse(num);
}
catch (Exception ex)
{
// TODO: hacer algo...
}

Sin embargo, la llegada de .NET Framework 2.0 supuso la inclusión del método estático TryParse() en la práctica totalidad de tipos primitivos numéricos (en la versión 1.1 sólo existe en el tipo double), cuya forma más simple de utilización (y también más usual) dejaría el bloque try/catch anterior en uno más compacto y legible:

if (!int.TryParse(num, out i))
// TODO: hacer algo...
 
El método TryParse devuelve true si se ha podido realizar la transformación con éxito, dejando en la variable de salida que le indiquemos el valor parseado, en este caso un int.

Sin embargo, además de para evitarnos dolores en las articulaciones de los dedos, existen otros motivos para utilizar xxx.TryParse() en vez de xxx.Parse(): el rendimiento, sobre todo si se prevé que el número de fallos de conversión será relativamente alto en un proceso. De hecho es el método recomendado en la actualidad para realizar estas conversiones.

Según la prueba de rendimiento de TryParse publicada Microsoft, ejecutada sobre su propia plataforma, la utilización de este método aventaja de forma considerable al modelo try-parse-catch. Por ejemplo, realizando varias simulaciones de parsing de enteros sobre un conjunto de 100 cadenas de las cuales 5 son erróneas, el tiempo de proceso llegar a ser mil veces superior usando este último patrón; si asumimos 20 incorrectas (80% de cadenas correctas), el modelo tradicional puede tardar 4.000 veces más que usando TryParse.

Aunque lo normal no es efectuar un gran número de transformaciones de este tipo y esta diferencia de rendimiento podría entenderse despreciable, podría ser un factor a tener en cuenta cuando estas operaciones se realicen en contextos de gran carga de usuarios o concurrencia, como en sitios web de alto tráfico. Ojo pues a esas aplicaciones (o componentes) que hemos tomado de .NET 1.x y estamos reutilizando en las versiones superiores.

Por cierto, todo lo dicho, aunque los ejemplos están codificados en C#, es válido para Visual Basic .NET y cualquier otro lenguaje que compile sobre la plaforma .NET de Microsoft. A ver si un día tengo un rato y compruebo si el comportamiento en Mono es el mismo.

Estos contenidos se publican bajo una licencia de Creative Commons Licencia Reconocimiento-No comercial-Compartir bajo la misma licencia 3.0 España de Creative Commons

lunes, 24 de septiembre de 2007
Hace unas semanas Miguel de Icaza publicaba en su blog personal un par de ejemplos del tipo de pruebas técnicas que realizan cuando contratan personal, en este caso para el proyecto Mono.

La filosofía es bastante simple, y la verdad es que no carece de sentido: los aspirantes reciben por correo electrónico el "enunciado" de la prueba, y disponen de dos semanas para responder. Aunque el tiempo de respuesta puede parecer excesivo, está pensado para ser realizado por los aspirantes cuando vuelven a casa tras sus jornadas de trabajo o estudio.

Según Miguel, esta técnica le da mejores resultados que las tradicionales entrevistas cara a cara, donde una lectura del currículum y varias preguntas rápidas no permiten conocer en profundidad al candidato. No hay nada como ver planteamientos y código para saber el tipo de desarrollador que tenemos delante.

La prueba para trabajar en el ámbito de Windows.Forms consistía en desarrollar un Control (Widget) y responder una pregunta, y decía más o menos esto (traducción libre):


Desarrollo de un control

Debes implementar un pequeño motor de renderizado para un lenguaje de marcas basado en XML. Este lenguaje acepta las siguientes etiquetas:

<p>...</p>, para definir el inicio y fin de párrafos.

Los párrafos pueden contener en su interior los siguientes tags:

  • <b>...</b>, para poner en negrita el texto
  • <i>...</i>, para poner el texto en cursiva

  • <link>...</link>, para que el texto sea renderizado como un hiperenlace.
  • <blink>...</blink> para que el texto parpadee
El control debe exponer la siguiente propiedad, que permitirá al desarrollador modificar el contenido (en este lenguaje de marcas) de forma programática.

  string Markup { get; set; }
 
Por ejemplo:

  m = new MarkupControl ();
m.Markup = "<p>Hello <b>World</b>!</p>";
 
El control también debe exponer un evento que permita notificar de los clicks del usuario sobre los enlaces (etiqueta <link>), algo así como:

  delegate void LinkClicked (object sender, string link_text);
 
De esta forma, será posible suscribirse al evento usando un código como el siguiente:

  m.LinkClicked += my_clicked;
...

void my_clicked (object sender, string link_text)
{
Console.WriteLine
("The link {0} was clicked", link_text);
}
 
Debes enviar una aplicación completa que se ejecute correctamente en Linux, y debe hacerse uso del evento y la propiedad citados con anterioridad. Por ejemplo, estaría bien tener algo como:

void my_clicked (object sender, string link_text)
{
Console.WriteLine ("Link {0} pulsado", link_text);
((MarkupControl) sender).Markup = "<p>Nuevo <b>texto</b></p>";
}
 
Puntos extra: al usar <blink> será necesario actualizar la vista, se valorará si se evita el parpadeo usando buffers dobles o repintando sólo el área modificada.

Quiero una pequeña y sucinta implementación, pero es tu oportunidad para demostrar que puedes escribir código robusto, así que impresióname.

Pregunta

En nuestra implementación de corlib, en System/DateTime.cs tenemos una implementación no óptima del método TryParse, básicamente invocamos a Parse dentro de un bloque try/catch.

Explica:
  • ¿Por qué digo que nuestra solución no es óptima?

  • ¿Qué debería tener para hacerla más eficiente?

  • ¿Por qué el desarrollador que escribió el código no realizó lo más eficiente?

La trick-question es: ¿por qué el proceso más rápido no se realizó en primer lugar? Explícalo.

¿Que os parece? ¿Podríais entrar a trabajar en el equipo de Mono?

Estos contenidos se publican bajo una licencia de Creative Commons Licencia Reconocimiento-No comercial-Compartir bajo la misma licencia 3.0 España de Creative Commons

miércoles, 12 de septiembre de 2007
Javascript, ese lenguaje tan de moda, nos permite manipular en cliente algunas propiedades de los utilísimos validadores de ASP.Net.

La función ValidatorEnable, proporcionada por la plataforma .Net en cliente y utilizable mediante scripting, nos permite habilitar o deshabilitar validadores de nuestros Webforms sin necesidad de hacer un postback. Un ejemplo de uso sería el siguiente:

function onOff(validatorId, activar)
{
var validator =
document.getElementById(validatorId);
ValidatorEnable(validator, activar);
}

El segundo parámetro sería el booleano (true o false) que indica si se desea activar o desactivar el validador. El primer parámetro es el ID en cliente del mismo, y podemos obtenerlo usando la propiedad ClientID del control; por ejemplo, imaginando que tenemos un validador de tipo RequiredFieldValidator llamado Rfv en nuestro Webform, una llamada a la función anterior sería algo así como:

function algo( )
{
onOff("<%= Rfv.ClientID %>", true);
}

 

Estos contenidos se publican bajo una licencia de Creative Commons Licencia Reconocimiento-No comercial-Compartir bajo la misma licencia 3.0 España de Creative Commons

miércoles, 5 de septiembre de 2007
Me acabo de encontrar en The Inquirer la siguiente noticia:


Microsoft anuncia Silverlight 1.0 ¡compatible con Linux!
La tecnología pretende competir con Adobe Flash ya ha llegado a su versión 1.0 y está disponible para su descarga y utilización en todo tipo de navegadores. Incluidos los de Linux a través del proyecto Moonlight y del acuerdo entre MS y Novell.

(Seguir leyendo...)



Parece que los chicos de Microsoft y los de Novell (desarrolladores principales de Mono) no se llevan nada mal.

Estos contenidos se publican bajo una licencia de Creative Commons Licencia Reconocimiento-No comercial-Compartir bajo la misma licencia 3.0 España de Creative Commons

domingo, 2 de septiembre de 2007
Hace unos días Miguel de Icaza hablaba en su blog de la inminente posibilidad de hacer funcionar ASP.Net Ajax sobre Mono, incluso comenta un mensaje de Onur Gumus a la lista de programadores de la plataforma explicando cómo hacerlo en la actualidad con versiones internas.

Todavía tendremos que esperar un poco para poder incorporarlo en producción, pero sin duda es una buena noticia para los que ya disfrutábamos de estas librerías en el framework de Microsoft y esperábamos ansiosamente su implementación en Mono.

Estos contenidos se publican bajo una licencia de Creative Commons Licencia Reconocimiento-No comercial-Compartir bajo la misma licencia 3.0 España de Creative Commons

jueves, 12 de abril de 2007
Resulta que leyendo la especificación de C# 2.0 he encontrado, en el apartado correspondiente a los tipos anulables un nuevo operador que me ha parecido de lo más interesante, para el tratamiento rápido de valores nulos, el Null Coalescing Operator (NCO) u Operador de Fusión de Nulos (traducción libre).

El operador en cuestión, expresado como ?? (dos cierres de interrogación), permite devolver un valor si no es nulo, o devolver otro valor alternativo ante la nulidad del primero. En otras palabras, un código como:
if (s!=null)
return s;
else
return "por defecto";

O también escrito de la forma, utilizando el operador ternario:

return (s!=null?s:"por defecto"); 

Quedaría, utilizando el nuevo operador de fusión, como:

return s ?? "por defecto"; 

Qué limpio, ¿no?

A primera vista puede parecer que salvo mejorar la legibilidad, no aporta demasiadas ventajas frente al operador ternario ?, pero fijaos en el siguiente código, en un método que retorna el nombre del algo cuyo Id le llega como parámetro:

string nombre = obtenerNombre(id);
if (nombre==null)
return "Desconocido";
else
return nombre;

Utilizando el operador ternario ? podríamos dejarlo en una única línea:

return obtenerNombre(id)==null?"Desconocido":obtenerNombre(id); 

Esto tiene una pega aparte de la dificultad de lectura: si el nombre del llamamos dos veces a obtenerNombre(id), lo cual podría tener efectos secundarios no deseados, o simplemente causar un problema de rendimiento. Para mejorarlo podríamos hacer esto, que mejora la legibilidad y llama sólo una vez al método, aunque es más largo de codificar, siendo muy similar al if inicial:

string p = obtenerNombre(id);
return p==null?"Desconocido":p;

Con el nuevo operador el código quedaría así de simple:

return obtenerNombre(id) ?? "Desconocido"; 

Esto se ejecuta de la siguiente forma: se llama a obtenerNombre(id) y si no es nulo se retorna el valor obtenido. Sólo si el resultado de la expresión ha sido nulo se retornaría el literal "Desconocido".

No sé a vosotros, pero a mí me ha parecido interesantísimo, y de lo más útil.

Estos contenidos se publican bajo una licencia de Creative Commons Licencia Reconocimiento-No comercial-Compartir bajo la misma licencia 3.0 España de Creative Commons

miércoles, 4 de abril de 2007
Indiscutiblemente, los nulos son una de las peores pesadillas del desarrollador, y causa de una gran cantidad de errores en tiempo de ejecución de aplicaciones. La falta de herramientas para tratarlos apropiadamente hacen a veces compleja la convivencia con la oveja negra del rebaño de los valores posibles a aplicar a una variable.

Y es que con poco que hayáis desarrollado, seguro que alguna vez habéis encontrado la difícil tarea de asignar un valor nulo a una variable de tipo valor (int, float, char...) donde no encaja más que usando artimañas difíciles de realizar, trazar y documentar. Por ejemplo, dada una clase Persona con un propiedad de tipo entero llamada Edad, ¿qué ocurre si cargamos un objeto de dicha clase desde una base de datos si en ésta el campo no era obligatorio?

A priori, fácil: al leer de la base de datos comprobamos si es nulo, y en ese caso le asignamos a la propiedad de la clase el valor -1. Buen parche, sin duda.

Sin embargo, optar de forma general por esta idea presenta varios inconvenientes. En primer lugar, para ser fieles a la realidad, si quisiéramos almacenar de nuevo este objeto en la base de datos, habría que realizar el cambio inverso, es decir, comprobar si la edad es -1 y en ese caso guardar en el campo un nulo.

En segundo lugar, fijaos que estamos llevando a la clase artificios que no tienen sentido en el dominio del problema a resolver, en la entidad a la que representa. Mirándolo un poco desde lejos, ¿qué sentido tiene una edad negativa en una entidad Persona? Ninguno.

En tercer lugar, existe un problema de coherencia en las consultas. Si tengo en memoria una colección de personas (realizada, por ejemplo usando generics ;-)) y quiero conocer las que tienen edad definida, debería comprobar por cada elemento si su propiedad Edad vale -1; sin embargo, al realizar la misma consulta en la base de datos debería preguntar por el valor NULL sobre el campo correspondiente.

Cierto es que podríamos llevar también a la base de datos el concepto "-1 significa nulo" en el campo Edad, pero... ¿no estaríamos salpicando a la estructura de datos con una particularidad (y limitación) del lenguaje de programación utilizado? Otra idea bizarra podría ser introducir la edad en un string y problema solucionado: las cadenas, al ser un tipo referencia pueden contener nulos sin problemas, lo que pasa es que las ordenaciones saldrían regular ;-)

Por último, ¿y si en vez de la edad, donde claramente no pueden existir negativos se tratase de una clase CuentaCorriente y su propiedad SaldoActual? Aquí sí que se ve claramente que esto no es una solución válida.

Soluciones, aparte de las comentada, hay para todos los gustos. Se podría, por ejemplo, añadir una propiedad booleana paralela que indicara si el campo Edad es nulo (un trabajazo extra, sobre todo sin son varias las propiedades que pueden tener estos valores), o encapsular la edad dentro de una clase que incorporara la lógica de tratamiento de este nulo.

En cualquier caso, las soluciones posibles son trabajosas, a veces complejas, y sobre todo, demasiado artificiales para tratarse de algo tan cotidiano como es un simple campo nulo.

Conscientes de ello, los diseñadores de C# han tenido en cuenta en su versión 2.0 una interesante característica: los nullables types, o tipos anulables (traducción libre), un mecanismo que permite introducir el nulo en nuestras vidas de forma no traumática.

La siguiente línea generaba un error en compilación, que decía, y no le faltaba razón, que "no se puede convertir null en 'int' porque es un tipo de valor":
int s = null; 

Ahora, en C# 2.0, es posible hacer lo siguiente:

int? s;
s = null;
s = 1;

Obsérvese la interrogación junto al tipo, que es el indicativo de que la variable s es de tipo entero, pero que admite también un valor nulo.

Por dentro, esto funciona de la siguiente forma: int? es un alias del tipo genérico System.Nullable<int>. De hecho, podríamos usar indistintamente cualquiera de las dos formas de expresarlo. Internamente se crea una estructura con dos propiedades de sólo lectura: HasValue, que retorna si la variable en cuestión tiene valor, y Value, que contiene el valor en sí.

Se entiende que una variable con HasValue igual a false contiene el valor nulo, y si intentamos acceder al mismo a través de Value, se lanzará una excepción.

Sin embargo, la principal ventaja que tienen es que se utilizan igual que si fuera un tipo valor tradicional. Los tipos nullables se comportan prácticamente como ellos y ofrecen los mismos operadores, aunque hay que tener en cuenta sus particularidades, como se aprecia en el siguiente código:

int? a = 1;
int? b = 2;
int? intNulo = null;
bool? si = true;
bool? no = false;
bool? niSiNiNo = null;
Console.WriteLine(a + b); // 3
Console.WriteLine(a + intNulo); // Nada, es nulo
Console.WriteLine(a * intNulo); // Nada, es nulo
Console.WriteLine(si & no); // false
Console.WriteLine(si & no); // true
Console.WriteLine(si & niSiNiNo); // Nada, es nulo
Console.WriteLine(no & niSiNiNo); // false
Console.WriteLine(si | niSiNiNo); // true
Console.WriteLine(no | niSiNiNo); // Nada, es nulo

Curioso en los booleanos, en los que el valor nulo se puede interpretar como un quizás. De esta forma es fácil prever el resultado de una operación lógica: "Verdad ó Quizás" resuelve como verdadero, ó "Falso y Quizás" es falso, por ejemplo.

Estos contenidos se publican bajo una licencia de Creative Commons Licencia Reconocimiento-No comercial-Compartir bajo la misma licencia 3.0 España de Creative Commons

sábado, 31 de marzo de 2007
Una particularidad interesante en la declaración de los generics en C# es la posibilidad de establecer limitaciones en los tipos utilizados como parámetros de las plantillas. Paradójico pero cierto, el propio lenguaje facilita la creación de clase genéricas que no lo sean tanto.

Para verle el sentido a esto, utilicemos el siguiente ejemplo, aparentemente correcto:

public class Seleccionador<Tipo>
{
public Tipo Mayor(Tipo x, Tipo y)
{
int result = ((IComparable)x).CompareTo(y);
if (result > 0)
return x;
else
return y;
}
}

Se trata de una clase genérica abierta, cuya única operación (Mayor(...)) permite obtener el objeto mayor de los dos que le pasemos como parámetros. El criterio comparativo lo pondrá la propia clase sobre la que se instancie la plantilla: los enteros serán según su valor, las cadenas según su orden alfabético, etc.

A continuación creamos dos instancias partiendo de la plantilla anterior, y concretando el generic a los tipos que nos hacen falta:

Seleccionador<int> selInt = new Seleccionador<int>();
Seleccionador<string> selStr = new Seleccionador<string>();

Estas dos instanciaciones son totalmente correctas, ¿verdad? Si después de ellas usamos el siguiente código:

Console.WriteLine(selInt.Mayor(3, 5));
Console.WriteLine(selStr.Mayor("X", "M"));

Obtendremos por consola un 5 y una X. Todo correcto, aparece, para cada llamada, la conversión a cadena del objeto mayor de los dos que le hayamos enviado como parámetros.

El problema aparece cuando instanciamos la clase genérica para un tipo que no implementa IComparable, que se utiliza en el método Mayor para determinar el objeto mayor de ambos. En este caso, se lanza una excepción en ejecución indicando que el cast hacia IComparable no puede ser realizado, abortando el proceso. Por ejemplo:

public class MiClase // No es comparable
{
}
[...]
Seleccionador<MiClase> sel = new Seleccionador<MiClase>();
MiClase x1 = new MiClase();
MiClase x2 = new MiClase();
Console.WriteLine(selString.Mayor(x1, x2)); // Excepción,
// no son
// comparables!


Una posible solución sería, antes del cast a IComparable en el método Mayor(), hacer una comprobación de tipos y realizar el cast sólo si es posible, pero, ¿y qué hacemos en caso contrario? ¿devolver un nulo? La pregunta no creo que tenga una respuesta sencilla, puesto que en cualquier caso, se estarían intentado comparar dos objetos que no pueden ser comparados.

La solución óptima, como casi siempre, es controlar en tiempo de compilación lo que podría ser una fuente de problemas en tiempo de ejecución. La especificación de C# 2.0 incluye la posibilidad de definir constraints o restricciones en la declaración de la clase genérica, limitando los tipos con los que el generic podrá ser instanciado. Y, además, de forma bastante simple, nada más que añadir en la declaración de la clase Seleccionador la siguiente cláusula where:


public class Seleccionador
where Tipo: IComparable // !Sólo comparables!
{
public Tipo Mayor(Tipo x, Tipo y)
[...]

Justo a partir de ese momento, el intento de instanciar el Seleccionador utilizando MiClase o cualquier otro tipo que no implemente IComparable, generará un error en tiempo de compilación.

Estos contenidos se publican bajo una licencia de Creative Commons Licencia Reconocimiento-No comercial-Compartir bajo la misma licencia 3.0 España de Creative Commons

sábado, 17 de marzo de 2007
Por poner alguna pega a los generics, he de decir que no me gusta (estéticamente hablando) el código resultante. En otras palabras, al menos hasta que me acostumbre, veo poco legible una expresión del tipo:
List<Algocomplejo> listaDeCosas = 
new List<Algocomplejo>();

Bueno, no pasa nada. Aunque la clase sea genérica, clase es. Esto implica que podemos hacer uso de la herencia para aclarar un poco el código:

public class ListaCompleja: List<Algocomplejo>
{
}

A partir de este momento, la instanciación quedaría más limpia:

ListaCompleja lista = new ListaCompleja();

A todos los efectos, este truco tan simple hace que podamos trabajar con clases genéricas con un código fuente similar al de siempre, sin "<" ni ">" que, por lo menos a mí, despistan tanto.

Estos contenidos se publican bajo una licencia de Creative Commons Licencia Reconocimiento-No comercial-Compartir bajo la misma licencia 3.0 España de Creative Commons

domingo, 11 de marzo de 2007
Los generics son el mecanismo de implementación de clases parametrizadas en C# introducido en la versión 2.0 del lenguaje. Una clase parametrizada es exactamente igual a una clase de las habituales, salvo por un pequeño detalle: su definición contiene algún elemento que depende de un parámetro que debe ser especificado en el momento de la declaración de un objeto de dicha clase.

Esto puede resultar extremadamente útil a la hora de programar clases genéricas, capaces de implementar un tipado fuerte sin necesidad de conocer a priori los tipos para los que serán utilizadas. ¿Confuso? Mejor lo vemos con un ejemplo.

Sabemos que un ArrayList es un magnífico contenedor de elementos y que, por suerte o por desgracia, según se vea, trabaja con el tipo base object. Esto hace que sea posible almacenar en él referencias a cualquier tipo de objeto descendiente de este tipo (o sea, todos), aunque esta ventaja se torna inconveniente cuando se trata de controlar los tipos de objeto permitidos. En otras palabras, nada impide lo siguiente:

al.Add("Siete caballos vienen de Bonanza...");
al.Add(7);
al.Add(new String('*', 25)); // 25 asteriscos

Esto puede provocar errores a la hora de recuperar los elementos de la lista, sobre todo si asumimos que los elementos deben ser de un determinado tipo. Y claro, el problema es que el error ocurriría en tiempo de ejecución, cuando muchas veces es demasiado tarde:

foreach (string s in al)
{
System.Console.WriteLine(s);
}

Efectivamente, se lanza una excepción indicando que "No se puede convertir un objeto de tipo 'System.Int32' al tipo 'System.String'". Lógico.

Algunos de los que hayáis sido capaces de superar el test FizzBuzz en menos de media hora ;-) podríais decir que eso se puede solucionar fácilmente, que existen al menos dos maneras de hacerlo: crear nuestra propia colección partiendo de CollectionBase o similares y mostrar métodos de acceso a los elementos con tipado fuerte, o bien, usando delegación, crear una clase de cero que implemente interfaces como IEnumerable en cuyo interior exista una colección que es la que realmente realiza el trabajo.

En cualquier caso, es un trabajazo. Por cada clase que queramos contemplar deberíamos crear un engendro como el descrito en el párrafo anterior.

Y aquí es donde los generics entran en escena. El siguiente código declara una lista de elementos de tipo AlgoComplejo:

List<AlgoComplejo> listaDeCosas =
new List<AlgoComplejo>();
listaDeCosas.Add(new AlgoComplejo());
listaDeCosas.Add("blah"); // Error en compilación

Con esta declaración, no será posible añadir a la lista objetos que no sean de la clase indicada, ni tampoco será necesario realizar un cast al obtener los elementos, pues serán directamente de ese tipo.

Es interesante ver la gran cantidad de clases genéricas para el tratamiento de colecciones que incorpora el Framework 2.0 en el namespace System.Collections.Generic.

¿Y si queremos nosotros crear una clase genérica? Pues muy sencillo. Vamos a desarrollar un ejemplo completo donde podamos ver las particularidades sintácticas y detalles a tener en cuenta. Crearemos la clase CustodiadorDeObjetos, cuya misión es almacenar un objeto genérico y permitirnos recuperarlo en cualquier momento. Básicamente, construiremos una clase con una variable de instancia y un getter y setter para acceder a la misma, pero usaremos los generics para asegurar que valga para cualquier tipo y que el objeto introducido sea siempre del mismo tipo que el que se extrae.

public class CustodiadorDeObjetos<Tipo>
{
private Tipo objeto;
public Tipo Objeto
{
get { return objeto; }
set { this.objeto = value; }
}
}

El siguiente código muestra la utilización de nuestra nueva clase:

CustodiadorDeObjetos<string> cs = new CustodiadorDeObjetos<string>();
cs.Objeto = "Hola"; // Asignamos directamente
string s = cs.Objeto; // No hace falta un cast,
// objeto es de tipo string
// en esta clase.
cs.Objeto = 12; // Error en compilación,
// no es string

Pero no sólo es eso, los generics dan mucho más de sí, pero de esto seguiremos hablando otro día, que ya es tarde.

Estos contenidos se publican bajo una licencia de Creative Commons Licencia Reconocimiento-No comercial-Compartir bajo la misma licencia 3.0 España de Creative Commons