
En la plataforma .NET existen distintas formas de hacer que una llamada a un método sea omitida bajo determinadas circunstancias. Por ejemplo, los métodos parciales permiten, en C# 3.0 y VB 9.0, que el compilador omita la llamada a funciones no implementadas. También existe la posibilidad de utilizar las clásicas directivas (como #if… #endif
) para incluir código cuando existan constantes de compilación.
Es menos conocida, sin embargo, la existencia del atributo ConditionalAttribute
, que aplicado a un método hace que las llamadas a éste no sean incluidas en el ensamblado si no existe la constante de compilación cuyo nombre se indica en el parámetro.
Por ejemplo, explorando un poco el espacio de nombres System.Diagnostics
, vemos que todos los métodos de la clase Debug
, están adornados por este atributo así:
1: [Conditional("DEBUG")]
2: public static void WriteLine(string message)
3: {
4: TraceInternal.WriteLine(message);
5: }
Ahora, imaginad que tenemos un código que usa esta clase, por ejemplo de la siguiente manera:
1: public static void Main(string[] args)
2: {
3: for(int i=0; i < 1000; i++)
4: {
5: Debug.WriteLine("Iteración " + i);
6: ProcesaAlgoComplejo(i);
7: int j = ProcesaOtraCosa(i);
8: Debug.WriteLine("Obtenido " + j);
9: // ...
10: }
11: }
Si compilamos en modo “debug” (o simplemente está definida la constante de compilación con dicho nombre), las llamadas a estos métodos serán omitidas en el ensamblado resultante, por lo que el código finalmente generado será totalmente equivalente a:
1: public static void Main(string[] args)
2: {
3: for(int i=0; i < 1000; i++)
4: {
5: ProcesaAlgoComplejo(i);
6: int j = ProcesaOtraCosa(i);
7: // ...
8: }
9: }
Pero ojo, que se omiten tanto las llamadas al método como la evaluación de sus parámetros, y esto puede provocar errores difíciles de detectar. Por ejemplo, el siguiente código daría lugar a un bucle infinito de los de toda la vida ;-) si compilamos en modo “release”; sin embargo, compilando en “debug” funcionaría correctamente:
1: int j = 0;
2: while (j < 100)
3: {
4: Console.WriteLine("Hey " + j);
5: Debug.WriteLine("Procesado " + (j++)); // <-- Esta línea desaparece cuando
6: // compilamos en modo release!
7: }
Aunque en el anterior ejemplo estamos jugando con la constante predefinida DEBUG
, este atributo podemos utilizarlo con otras constantes, tanto existentes como personalizadas. Como muestra, podéis echar un vistazo a la definición de la clase System.Diagnostics.Trace
, que vincula el uso de sus métodos a la existencia de la constante TRACE
:
1: public sealed class Trace
2: {
3: // ...
4:
5: private Trace();
6: [Conditional("TRACE")]
7: public static void Assert(bool condition);
8: [Conditional("TRACE")]
9: public static void Assert(bool condition, string message);
10: [Conditional("TRACE")]
11: public static void Assert(bool condition, string message, string detailMessage);
12: [Conditional("TRACE")]
13: public static void Close();
14: [Conditional("TRACE")]
15: public static void Fail(string message);
16: [Conditional("TRACE")]
17: public static void Fail(string message, string detailMessage);
18: [Conditional("TRACE")]
19: public static void Flush();
20: [Conditional("TRACE")]
21:
22: //...
23: }
Si vais a utilizar el atributo sobre vuestros métodos, condicionándolos a la existencia de constantes de compilación personalizadas, recordad que las constantes podéis definirlas:
- desde las propiedades del proyecto en el IDE
- en la línea de comandos del compilador (por ejemplo,
/define:LOG
) - variables de entorno del sistema operativo (
set LOG=1
) - en directivas sobre vuestro propio código (directiva
#define LOG
)
Eso sí, tened en cuenta que el método siempre será compilado e introducido en el ensamblado, son las invocaciones a éste las que son omitidas en caso de no existir la constante indicada.
Hay que tener en cuenta las siguientes observaciones para el uso del atributo Conditional
:
- Los métodos a los que se aplica no pueden tener tipo de retorno, es decir, serán
void
. Esto tiene bastante sentido, si pensamos en los efectos laterales que podría causar la desaparición de una invocación tras la cual se haga uso del valor retornado. - Sólo se pueden aplicar a métodos en clases o estructuras. Nada de interfaces (¡tampoco tendría mucho sentido!).
- Si un método virtual está marcado como
Conditional
, las reescrituras de éste realizadas desde clases descendientes también lo estarán. Es bastante lógico, puesto que así se mantienen las dependencias íntegras. - Si el atributo se aplica a un método, éste no puede ser un reemplazo del comportamiento de un antecesor (o sea, que no puede ser un override). También resulta muy lógico.
En resumen, se trata de un buen método para incluir código condicional en nuestros desarrollos, dependiente del contexto de compilación, evitando tener que usar directivas #if… #endif
. Pero no lo olvidéis: cuidado con los efectos laterales citados.
Publicado en: Variable not found.
Publicado por José M. Aguilar a las 11:57 p. m.
Etiquetas: .net, asp.net, c#, depuración, desarrollo, trucos
El control de errores en aplicaciones web es fundamental si queremos ofrecer un interfaz robusto y amigable para los usuarios en cualquier situación. No hay nada más frustrante para un usuario que una pantalla de error con contenidos indescifrables y que no le aportan alternativas de salida.
El framework ASP.NET MVC nos ofrece mecanismos de control de errores muy potentes basada en la utilización del atributo HandleError,
el cual definirá la vista que será mostrada al usuario cuando se produzca alguna excepción no controlada en el código de los controladores, siempre que en el web.config
se haya activado el uso de errores personalizados mediante la propiedad CustomErrors
.
En este post vamos a profundizar en el uso del atributo HandleError,
comentando cómo se implementa en el controlador, su ámbito de actuación, los parámetros que ofrece y la forma de crear las vistas para mostrar los errores de forma amigable.
El controlador
HandleError
puede ser declarado tanto a nivel de clase (controlador) como a nivel de acción. En el primer caso, se establecerá el comportamiento general para todas las acciones del controlador, mientras que en el segundo será aplicable sólo a la acción a la que se asocie el atributo:
// Control de errores a nivel de clase de controlador,
// que será aplicado a todas las acciones del mismo.
[HandleError()]
public class HomeController : Controller
{
...
}
// Control de errores a nivel de acción concreta...
[HandleError(ExceptionType=typeof(ArgumentException), View="ArgumentError")]
public ActionResult Calculate(int a, int b)
{
ViewData["results"] = calculateSomething();
return View();
}
En realidad, el comportamiento definido en el atributo HandleError
a nivel de clase también se aplicará a los errores generados por las vistas u otros resultados (ActionResult
) retornados por los controladores. Es decir, sobre el segundo de los ejemplos anteriores, la vista “ArgumentError” (que existirá en un archivo llamado ArgumentError.aspx) será invocada cuando la excepción ArgumentException
sea lanzada bien por el propio controlador, o bien por la vista “Calculate” que retorna por defecto.
Un último detalle sobre esto: el atributo HandleError
puede ser especificado tantas veces como necesitemos sobre la misma acción o controlador, indicando comportamientos para distintos tipos de excepción. El atributo que se tendrá en cuenta cuando se produzca un error será el primero que se encuentre cuyo tipo de excepción (parámetro ExceptionType) sea compatible con la excepción lanzada.
El manejador de errores que se empleará en una acción será el primero que corresponda al tipo de excepción producida, teniendo en cuenta tanto los atributos que adornan la acción como los que acompañan a su controlador, y siempre según un orden preestablecido.
Veamos con más detenimiento los parámetros que admite la declaración del atributo.
Parámetros de HandleError
[HandleError(
ExceptionType=typeof(DivisionByZero),
View="ErrorPersonalizado",
Master="MaestraErrores",
Order = 0)
]
public ActionResult Index()
...
- ExceptionType permite indicar el tipo de excepción que se pretende controlar. Por defecto, el sistema entenderá que la regla se refiere al tipo base
Exception
, por lo que se aplicará a todos los errores que se generen, pues todas las excepciones heredan de esta clase. - View, el nombre de la vista que será mostrada al usuario. Por defecto se tomará el valor “Error”, por eso en la plantilla de proyectos ASP.NET MVC ya existe una vista con este nombre.
- Master, la página maestra con la que será renderizada la vista, independientemente de lo que tenga declarado ésta.
- Order, un valor numérico (por defecto –1, el más prioritario) que indica la prioridad de aplicación de esta regla cuando el sistema encuentre varios atributos
HandleError
aplicables al mismo elemento y que puedan presentar conflictos. Los valores más pequeños, Por ejemplo, si no se indicara este parámetro en el siguiente caso, el resultado dependería del orden de declaración, lo cual no es demasiado recomendable:
[HandleError(Order=10, View="ErrorGenerico")]
public class HomeController : Controller
{
...
[HandleError (ExceptionType=typeof(DivisionByZero),
View="OperacionIncorrecta", Order=1)]
public ActionResult Calculate()
...
Como puede intuirse, esto hará que en caso de producirse una división por cero, se muestre la vista “OperacionIncorrecta” y no la “ErrorGenerico”.
Cada vez que utilicemos HandleError
es conveniente tener muy en cuenta la prioridad (definida en la propiedad Order
), el alcance (las excepciones a tratar, definidas en la propiedad ExceptionType
), así como los valores por defecto en cada caso. Esto evitará comportamientos misteriosos del sistema una vez se produzcan errores en tiempo de ejecución.
Acceso desde la vista a la información del error
Ya hemos comentado anteriormente que la vista que será mostrada a los usuarios cuando se produzca un error será la indicada en el parámetro View
del atributo HandleError
, o la vista "Error", si este parámetro no es informado. Sea cual sea, el archivo nombredevista.aspx deberá estar localizable por el motor de vistas en el momento de su lanzamiento (por cierto, si no te gustan las ubicaciones por defecto, puedes ver cómo modificar la forma en la que se buscan las vistas en este post).
La vista de un error es una página .aspx normal, como una vista más de la web, pero con la particularidad de que puede recibir información sobre el error que ha provocado su presentación. De hecho, se trata de una vista tipada que hereda de ViewPage<System.Web.Mvc.HandleErrorInfo>
, de forma que la propiedad Model
será del tipo HandleErrorInfo
, clase que nos ofrece completa información sobre el origen del problema:
<%@ Page Language="C#" MasterPageFile="~/Views/Shared/Site.Master"
Inherits="System.Web.Mvc.ViewPage<System.Web.Mvc.HandleErrorInfo>" %>
<asp:Content ID="errorTitle" ContentPlaceHolderID="TitleContent" runat="server">
Error en el sistema
</asp:Content>
<asp:Content ID="errorContent" ContentPlaceHolderID="MainContent" runat="server">
<h2>
Ups, se ha producido un ligero inconveniente...
</h2>
<p>
La acción <%= Model.ActionName %>
del controlador <%= Model.ControllerName %>
ha lanzado la excepción <%= Model.Exception.GetType().Name %> con
el mensaje "<%= Model.Exception.Message %>".
</p>
</asp:Content>
A pesar de no ser un buen ejemplo como pantalla del error amigable para el usuario ;-), el código anterior ilustra cómo es posible acceder desde la vista a los datos del contexto del error proporcionados por el entorno, y cómo la clase HandleErrorInfo
nos ofrece una información sobre la acción en la que se ha lanzado la excepción, el controlador en el que se encuentra la misma, y lo más interesante, nos ofrece en la propiedad Exception
la excepción producida, por lo que tendremos acceso a su tipo, descripción e incluso al estado de la pila de llamadas en el momento de producirse el error.
Publicado en: www.variablenotfound.com
Publicado por José M. Aguilar a las 11:59 p. m.
Etiquetas: asp.net, aspnetmvc, c#, desarrollo, programación, web
<code>
y </code>
el texto que tengamos seleccionado en el momento de su utilización, pero evita tener que entrar al modo de edición HTML para hacer estos ajustes. Basándome en el código fuente de CodeTag (que podéis descargar al final de este post), voy a describir, paso a paso, cómo podemos crear complementos para Writer que nos faciliten un poco las tareas cotidianas de edición.
0. Antes de empezar…
Para poder desarrollar el plugin necesitáis básicamente dos cosas:- en primer lugar, el propio software Live Writer, más que nada porque necesitamos referenciar algunos de sus ensamblados, y por ir probando el complemento durante el desarrollo.
- en segundo lugar, Visual Studio 2005 o 2008, aunque sea una edición express. Este desarrollo vamos a realizarlo en C#, pero la traducción a Visual Basic .NET sería trivial.
1. Preparación del proyecto
Los plugins para Live Writer son archivos .dll, es decir, ensamblados .NET, que se colocan en el directorio "plugins" de la carpeta de instalación del programa. En mi caso, están en la carpetaC:\Archivos de programa\Windows Live\Writer\Plugins,
y si no habéis cambiado las rutas de instalación por defecto, será allí donde los podéis encontrar también. Durante el proceso de arranque, Writer examinará dicha carpeta y cargará los complementos que encuentre en ella. Por tanto, en primer lugar creamos un proyecto de librería de clases como siempre, al que le damos el nombre CodeTag. Obviamente, podéis dar al proyecto el nombre que estiméis conveniente.
Una vez que el IDE crea la estructura, debemos añadir dos referencias al proyecto, que serán necesarias para poder continuar:
La primera referencia es al ensamblado que contiene el API de Live Writer, que podéis encontrar en el directorio de instalación de la herramienta. El archivo a incluir es
WindowsLive.Writer.Api.dll
.- La segunda es a
System.Windows.Forms
. Hay que tener en cuenta que Live Writer es una herramienta de escritorio, y esta referencia es importante para poder interactuar con el mismo.
Otro detalle más que nos va a facilitar las cosas: vamos a indicar a Visual Studio que el resultado de la compilación lo copie en el directorio de plugins de Live Writer. Para ello, lo único que tenemos que hacer es acudir a las propiedades del proyecto, pestaña “eventos de generación” e incluir la siguiente orden en el cuadro “línea de comandos del evento posterior a la generación” (obviamente, modificando la ruta si es necesario):
XCOPY /D /Y /R "$(TargetPath)" "C:\Archivos de Programa\Windows Live\Writer\Plugins\"
Ojo, es importante tener en cuenta algo más cuando vayamos a compilar: si Live Writer está abierto, no podremos sobrescribir nuestro plugin con una nueva versión, pues éste se encontrará en uso. En estos casos debemos cerrar el programa antes de copiar el archivo, o antes de compilar.
Con estas operaciones ya tenemos el proyecto listo para empezar a codificar el plugin.
2. Escribimos la clase principal
Un plugin sencillo como este no requiere demasiada programación, pero sí hay que cumplir una serie de requisitos para que se integre correctamente en Live Writer.En primer lugar, creamos una clase, a la que llamaremos
CodeTag
, e insertamos el siguiente código: public class CodeTag: ContentSource
{
public override DialogResult CreateContent(IWin32Window dialogOwner,
ref string content)
{
if (string.IsNullOrEmpty(content))
content = "YourCodeHere";
content = "<code>" + content + "</code> ";
return DialogResult.OK;
}
}
Como se puede observar, la clase hereda de
ContentSource,
un tipo definido en WindowsLive.Writer.Api
, que sirve como base para la creación de complementos sencillos como el que nos ocupa. Para casos más complejos, que permitieran por ejemplo edición de contenidos bidireccional entre el contenido de la página y un editor personalizado podríamos heredar de SmartContentSource
, pero no profundizaremos en ello ahora. La codificación de la lógica del plugin puede realizarse en varios puntos, dependiendo de la forma en que Writer lo active; en nuestro caso, el complemento se lanzará cuando el usuario presione un botón en la barra lateral o seleccione la opción correspondiente en el menú "insertar", por lo que simplemente deberemos sobrescribir el método
CreateContent
. Dicho método recibe dos parámetros. El primero de ellos hace referencia a la ventana activa, que podemos utilizar como "padre" si quisiéramos crear un cuadro de diálogo desde nuestro código. El segundo parámetro contendrá una referencia al texto seleccionado en el momento de lanzar el plugin, pudiendo darse los dos casos que se contemplan en la codificación:
- Si la variable
content
viene vacía o con valor nulo, es que el complemento ha sido lanzado en modo inserción, es decir, no existía ningún texto seleccionado y por lo tanto lo que se pretende es añadir las etiquetas de código para introducir posteriormente contenido. Como puede observarse en el código, lo que se hace en este caso es insertar las etiquetas<code>
con un texto arbitrario para que el usuario lo modifique a su antojo más adelante. - En caso contrario, si la variable
content
trae algún contenido, lo que se hace es rodear éste por la apertura y cierre de la etiqueta<code>
.
En ambos casos se retorna un valor
DialogResult.OK
, que indica a Live Writer que debe insertar el texto contenido en content en la ubicación donde se encuentre el cursor, o bien sustituir el texto seleccionado por el nuevo valor. 3. Añadimos metadatos
Heredar de la claseContentSource
no es el único requisito para que esta sea considerada como un componente de Writer, es necesario adornarla con un conjunto de atributos como los mostrados en el siguiente código: [WriterPlugin("98497c2b-bbfd-4bd1-b343-226f3c9e766b", "Code Tag",
Description = "Crea etiquetas <code></code> en el contenido",
PublisherUrl = "http://www.variablenotfound.com")]
[InsertableContentSource("Etiqueta <code>")]
public class CodeTag: ContentSource
{
[...]
}
El atributo [
WriterPlugin]
es el que realmente identifica la clase como plugin de Live Writer. Los parámetros que se le están enviando son los siguientes: - El primer parámetro es un GUID, es decir, un identificador único que debemos generar para el plugin, utilizando este servicio on-line u otras herramientas como la incluida en Visual Studio.
- El segundo parámetro es el nombre del plugin. Es la identificación que aparece en el cuadro de diálogo de configuración de complementos de Live Writer.
Description
permite añadir una descripción textual ampliada del plugin.PublisherUrl
es una dirección web de referencia al autor. Pura propaganda ;-)
El atributo
[InsertableContentsource]
indica que el complemento debe aparecer en el menú "insertar" de las barra de tareas de Writer y en el menú principal. El parámetro que le estamos enviando es simplemente el texto que aparecerá en estos menús. 4. Compilamos el proyecto
Con lo hecho hasta el momento ya podemos compilar e intentar probar nuestro complemento. Este procedimiento lo repetiremos varias veces durante el desarrollo, por lo que es posible que nos encontremos a menudo con un error como el siguiente:Esto simplemente quiere decir que Live Writer está usando CodeTag.dll y no puede ser sobrescrito, por lo que ¡cerrad Live Writer antes de compilar!
Pero vaya, es cierto que en el menú aparece, pero destaca sobre el resto de complementos porque es el único que no tiene un icono, así que habrá que mejorarlo un poco…
5. Añadirle un icono al plugin
Incluir un icono a nuestro complemento le dará sin duda un aspecto más profesional, vamos a ello.<code>
que pretendemos crear. El archivo debemos incluirlo, para no tener problemas, en el directorio raíz del proyecto, y a continuación, hay que indicar a Visual Studio que dicha imagen será un recurso incrustado (embedded resource) en el ensamblado. Este paso es importante, pues si no se hace correctamente, la imagen no será incluida en la DLL.
A continuación es necesario indicar a Live Writer la imagen a utilizar como icono, lo que se consigue añadiendo un parámetro más (
ImagePath
) en la definición del atributo [WriterPlugin]
con la ruta hacia el fichero que hemos incrustado. Eso sí, no me refiero a la ruta física del archivo .bmp, sino a la representación como espacio de nombres de la misma (por ejemplo, si la imagen se llama logo.bmp
y está en (raíz)\recursos
, la ruta hacia ella será “recursos.logo.bmp”
). Como en este caso hemos depositado la imagen en el directorio raíz, la declaración de atributos quedará como sigue:
[WriterPlugin("98497c2b-bbfd-4bd1-b343-226f3c9e766b", "Code Tag",
Description = "Crea etiquetas <code></code> en el contenido",
ImagePath = "CodeTag.bmp",
PublisherUrl = "http://www.variablenotfound.com")]
[InsertableContentSource("Etiqueta <code>")]
public class CodeTag : ContentSource
{
[...]
}
Un último apunte relativo a este tema: si al iniciar Live Writer éste es incapaz de localizar el recurso indicado en el parámetro
ImagePath
, el plugin funcionará, pero aparecerá el siguiente mensaje en el arranque de la aplicación: 6. O, por si no quieres teclear…
Requiere, como mínimo, Visual C# 2008 Express, con su correspondiente Service Pack 1.
Publicado en: Variable not found.
Publicado por José M. Aguilar a las 11:52 p. m.
Etiquetas: blogging, c#, desarrollo, software, utilidades

En esta tercera y última entrega de la serie, vamos a centrarnos en otra de las grandes utilidades de las expresiones lambda en .NET framework: la definición de árboles de expresión.
Las lambda como árboles de expresión

Esto es muy diferente al caso anterior, donde hablábamos de las lambda como funciones ejecutables que podían ser referenciadas por delegados e invocadas de forma directa. Entonces las expresiones lambda eran transformadas en tiempo de compilación en código ejecutable (de hecho, en métodos estáticos que pueden ser consultados usando Reflector u otros desensambladores), y por ello podíamos utilizarlas de forma directa.
En el caso de los árboles de expresión, la compilación no genera código ejecutable correspondiente a las instrucciones definidas en la lambda, sino el código para crear y llenar el árbol con dicha expresión. Después, en tiempo de ejecución, será posible recorrer dicho árbol, analizarlo, seriarlo para almacenarlo o moverlo a otras capas, e incluso compilarlo para poder lanzar su ejecución.
Pero paremos aquí un momento... según lo dicho, cuando el compilador se encuentra con una expresión lambda, por ejemplo
x => x * 2
, puede optar por generar una función anónima o por generar el código de llenado del árbol, ¿cómo sabe lo que debe hacer?Pues bien, el compilador elegirá la opción adecuada dependiendo del tipo de la referencia a la expresión lambda. Si se trata de un tipo de delegado como
Func
o Action
, generará código ejecutable y las referencias a la función serán tratados como delegados; si se usa el tipo Expression
, que veremos más adelante, se generará el árbol de expresión y las referencias no se considerarán delegados, sino objetos de este tipo. Por este motivo no podemos utilizar para declarar lambdas variables locales de tipo implícito: el compilador no sabría qué hacer.
var dobla = a => a * 2; // Error: no se puede asignar
// una expresión lambda a una variable
// local de tipo implícito.
Definición de árboles con expresiones lambda
Para definir un árbol de expresión a partir de una lambda debemos utilizar el tipo genéricoExpression<TipoDelegado>
, siendo TipoDelegado
un delegado como Action
o Func
de los descritos en el post anterior. ¿Que esto os parece confuso? Pues esperad a ver el ejemplo... ;-) Expression<Func<int, int, int>> media = (a, b) => (a + b) / 2;
En realidad, aparte de lo difícil de leer que es una declaración de este tipo, la idea es bastante sencilla. Lo que estamos indicando al compilador es que queremos montar un árbol de una expresión de la que conocemos el tipo de los parámetros de entrada y del valor de retorno, datos que indicamos mediante el delegado que utilizamos como argumento genérico de la clase
Expression
, la porción que he marcado de amarillo en el ejemplo anterior. Además, este delegado lo utilizaremos cuando queramos convertir el árbol de expresión en código ejecutable, más adelante veremos cómo.Para que quede claro, ahí van algunos ejemplos más:
// Árbol de expresión de una función que determina
// si el parámetro que le llega es par
Expression<Func<int, bool>> esPar = a => a%2==0;
// Árbol de expresión de una función
// que retorna el máximo de dos números.
Expression<Func<int, int, int>> maximo =
(a, b) => a > b ? a:b;
// Árbol de expresión de una función que
// retorna un string transformado
Expression<Func<string, string>> TrimMays =
a => a.Trim().ToUpper();
// Árbol de expresión de una acción
// sin parámetros que escribe por consola la fecha y hora.
Expression<Action> accion =
() => Console.WriteLine(DateTime.Now);
Fijaos que el tipo de delegado
Action
o Func
, que son los parámetros genéricos de la expresión, son los que definen los tipos de parámetros y retornos de la lambda. En el primer caso, el Func<int, bool>
define que la función recibe un entero y retorna un booleano; en el último caso, ni se envía ni se recibe nada, de ahí que utilicemos un delegado Action
.Un detalle importante, antes de que se me olvide comentarlo. Los árboles de expresión sirven, como su nombre indica, para almacenar expresiones. Esto limita el tipo de construcciones de código que podemos usar en las lambdas que los definen: no está permitido utilizar asignaciones, bloques con llaves { }, ni bucles... debe tratarse de una expresión que pueda representarse en una jerarquía de árbol. Si intentamos saltarnos estas restricciones, el compilador generará un error, aunque el tipo de delegado sea correcto y la función lambda también, como en el siguiente ejemplo:
// Suma los n primeros naturales
Expression<Func<int, int>> expr =
n => {
int t = 0;
for (int i = 1; i <= n; t += i++) ;
return t;
};
// Error CS0834: Una expresión lambda con cuerpo no puede ser
// convertida a árbol de expresión

¿Y cómo hace eso? Muy sencillo. El compilador analiza la expresión, genera la secuencia de instrucciones que compone el árbol, las compila, e introduce el resultado en el ensamblado. El siguiente ejemplo muestra cómo crear un árbol de expresión usando una lambda, y una aproximación al código equivalente que genera el compilador, para que nos podamos hacer una idea del trabajo que nos ahorra esta característica del lenguaje:
// Árbol definido con una lambda:
Expression<Func<int, bool>> esPar = a => a % 2 == 0;
// Y el mismo árbol definido a mano,
// lo que genera el compilador automáticamente:
ParameterExpression param = Expression.Parameter(typeof(int), "a");
ConstantExpression dos = Expression.Constant(2, typeof(int));
ConstantExpression cero = Expression.Constant(0, typeof(int));
BinaryExpression modulo = Expression.Modulo(param, dos);
BinaryExpression comparacion = Expression.Equal(modulo, cero);
Expression<Func<int, bool>> esPar2 =
Expression.Lambda<Func<int, bool>>(comparacion, param);
Uso de los árboles de expresión
Ya sabemos qué son los árboles de expresión y cómo podemos definirlos, pero aún no le hemos visto sentido a su existencia. Pero lo tiene, vaya si lo tiene ;-)En primer lugar, el hecho de poder definir el árbol partiendo de una expresión lambda, además de comodidad a la hora de codificar, nos permite aprovechar el tipado fuerte y la potencia del intellisense para evitar errores. En el ejemplo anterior, las probabilidades de que nos equivoquemos creando el árbol de forma manual son muy superiores a que ocurra si utilizamos la sintaxis lambda.
Segundo, fijaos que en ningún momento se está generando código IL o compilando la expresión representada por la lambda. Estamos creando una estructura en memoria. Esto quiere decir que después podemos procesar esta información como estimemos conveniente; podemos, por ejemplo, analizar su contenido, recorrerlo, seriarlo, o transformarlo, en función de nuestras necesidades. Es ideal, por tanto, cuando tengamos interés en interpretar una expresión para realizar alguna acción con ella.
Vamos a ver ahora varios ejemplos para ilustrar el uso de las lambdas y árboles de expresión en el mundo real.
Linq
En el caso de Linq aplicado a un proveedor externo de datos relacional (por ejemplo, las tecnologías Linq to SQL, o Entity Framework), no tiene interés alguno el código ejecutable asociado a una expresión, sino su estructura, pues al final va a ser traducida al lenguaje o tecnología del almacén de información. Supongamos la siguiente consulta para obtener los productos que comienzan por "S": // Usando una consulta Linq:
var datos = from p in productos
where p.Nombre.StartsWith("S")
select p;
// O su equivalente usando
// operadores de consulta:
var datos = productos.Where(p=>p.Nombre.StartsWith("S"));
La lambda resaltada servirá para crear un árbol de expresión con las condiciones indicadas, pues el método de extensión
Where
aplicado acepta un predicado de tipo Expression
. No se genera ningún método anónimo para la expresión lambda, ni se traduce a IL su contenido: sólo interesa para definir la expresión que será introducida en el árbol. Más adelante, en el momento de extraer realmente la información desde el almacén correspondiente, el componente proveedor de datos recorrerá y analizará la estructura en memoria, generando su equivalente en SQL, que es lo que lanzará al SGBD para obtener los datos.Por cierto, existen en la actualidad una gran cantidad de proveedores de Linq, capaces de transformar los árboles de expresión en consultas a casi cualquier tipo de almacén. Fijaos que la posibilidad de separar la codificación lambda de la interpretación de la expresión hace posible su utilización en una gran variedad de ámbitos.
Cálculo simbólico
Otro ejemplo que ilustra muy bien las posibilidades de los árboles de expresión, de mano del maestro Octavio Hernández. Se trata del artículo Cálculo simbólico en C# 3.0, publicado en la web de El Guille a principios de 2007.A lo largo del artículo se realiza la implementación básica de un sistema de cálculo de derivadas de funciones matemáticas partiendo de un árbol de expresiones. El proceso, que el autor va explicando paso a paso, consiste en analizar el árbol, e ir generando otro árbol con el resultado de la derivación de cada expresión encontrada. A continuación se muestra la porción de código donde se realiza la derivación de una operación de suma, utilizando recursividad para derivar además cada uno de los sumandos:
private static Expression Derive(this Expression e, string paramName)
{
switch (e.NodeType)
{
[...]
// sum rule
case ExpressionType.Add:
{
Expression dleft =
((BinaryExpression) e).Left.Derive(paramName);
Expression dright =
((BinaryExpression) e).Right.Derive(paramName);
return Expression.Add(dleft, dright);
}
[...]
}
}
Al final, fijaos que de nuevo no nos interesa en absoluto la lambda como función anónima ni delegado, sino la estructura de la propia expresión, de forma que podamos recorrerla y transformarla en otra expresión, en este caso la función derivada de la original. El siguiente ejemplo muestra el uso de esta clase:
Expression<Func<double, double>>
funcion = x => x*x; // f(x)=x^2
Expression<Func<double, double>>
derivada = funcion.Derive(); // f'(x)=2*x
Console.WriteLine(derivada); // Muestra la función derivada:
// x => ((x * 1) + (1 * x))
Creedme, vale la pena echarle un vistazo.
ASP.NET MVC
Un último ejemplo, que demuestra la versatilidad del uso de lambdas y árboles de expresión en multitud de escenarios. En el framework ASP.NET MVC, es posible crear enlaces hacia acciones desde dentro de las vistas (páginas .ASPX), que no son sino métodos dentro de unas clases concretas llamadas "controladores". El caso es que para hacer referencia a un método del controlador, pueden utilizarse estas dos vías, de resultado idéntico: // Obtiene un enlace al método "ChangePassword"
// de la clase "AccountController":
Html.ActionLink("Cambiar clave", "ChangePassword", "Account")
// Lo mismo, pero usando un árbol de expresión:
Html.ActionLink<AccountController>
(acc=>acc.ChangePassword(), "Cambiar clave")
Aunque el resultado es el mismo, la segunda usa un árbol de expresión para realizar la referencia al método
ChangePassword
de la clase AccountController
. La implementación del método ActionLink
recorre el árbol generado desde la expresión lambda para obtener el nombre del controlador y del método, por lo que es equivalente a primera fórmula, pero beneficiándose de las ventajas del tipado fuerte y del intellisense en la edición.Los árboles de expresión como código ejecutable
Hasta ahora siempre me he referido a los árboles de expresión como entidades de almacenamiento. De alguna u otra forma, estaba equiparando su utilidad a la cualquier estructura de datos que permitiera guardar y procesar información, lo cual es cierto pero sólo parcialmente.Los árboles de expresión aportan una característica adicional: se pueden convertir en código ejecutable. Es decir, es posible compilar un árbol de expresión en tiempo de ejecución, dando lugar a una función anónima a la que podemos tener acceso, es decir, invocarla, a través de sus delegados.
Veámoslo con un caso concreto. Retomando el ejemplo de obtención de derivadas, sería perfectamente posible obtener el valor de la función derivada en un punto ampliando ligeramente el código visto anteriormente:
Expression<Func<double, double>>
funcion = x => x*x; // f(x)=x^2
Expression<Func<double, double>>
derivada = funcion.Derive(); // f'(x)=2*x
// Compilamos la función derivada
// y obtenemos un delegado a la misma:
Func<double, double> funcDerivada = derivada.Compile();
double result = funcDerivada(6); // Obtenemos el valor de la función
// invocando al delegado con x=6
Console.WriteLine(result); // Muestra "12"
Como se puede observar en el código, la llamada al método
Compile()
devuelve un delegado del tipo Func
indicado en el parámetro genérico de la expresión, que apunta hacia la función anónima creada "al vuelo" a partir de las expresiones contenidas en el árbol. Es decir, desde la definición simbólica contenida en el árbol ¡obtenemos código ejecutable!Aunque espectacular, en realidad no hay nada mágico en esta característica: se trata de ir recorriendo el árbol y emitiendo el código IL correspondiente a cada expresión, que está perfectamente tipificada y definida. En el namespace
System.Linq.Expressions
existe una bonita clase interna llamada ExpressionCompiler
que se dedica exclusivamente a ello, utilizando herramientas suministradas por System.Reflection.Emit
, como los generadores de lenguaje intermedio ILGenerator
.¡Y hasta aquí hemos llegado!
A lo largo de estos tres posts hemos recorrido las principales características y utilidades de las expresiones lambda. Obviamente, han quedado cosas por detrás; no era objetivo de esta serie profundizar demasiado, sino ofrecer una visión suficiente para animar a los desarrolladores a utilizar esta potente característica que nos ofrece C# (y VB.NET).Espero que os haya sido una lectura útil, al menos tanto como me ha resultado a mí su escritura. Ah, y para consultas, sugerencias o puntualizaciones, por aquí me tenéis.
Publicado en: www.variablenotfound.com.
Publicado por José M. Aguilar a las 4:40 p. m.
Etiquetas: árboles de expresión, c#, lambdas, linq, vs2008