Puede que se dé la ocasión que busquemos un comportamiento parecido al de ExpandoObject pero necesitemos cambiar alguna cosa. Por ejemplo, el siguiente código lanza una RuntimeBinderException ya que la propiedad Name no ha sido asignada con anterioridad.
namespace ConsoleApplication2
{
class Program
{
static void Main(string[] args)
{
dynamic expando = new ExpandoObject();
Console.WriteLine(expando.Name);
}
}
}
Pero, ¿y si quisiéramos que cuando no exista un miembro se devuelva null en vez de una excepción? Con ExpandoObject esto es imposible, pero la buena noticia es que podemos hacer nuestro propio ExpandoObject si implementamos la interfaz IDynamicMetaObjectProvider.
Mirando la información de la MSDN y ojeando en internet descubrimos dos cosas:
a) Implementar IDynamicMetaObjectProvider es bastante difícil.
b) Microsoft ha proporcionado una clase que proporciona una implementación por defecto de IDynamicMetaObjectProvider: DynamicObject.
DynamicObject proporciona un montón de métodos de la forma “TrySomething” que son los que se usan en la ligadura dinámica. Por ejemplo, TryGetMember y TrySetMember nos permiten definir qué ocurre cuando se accede a un miembro de la clase de forma dinámica. Con esto ya podemos hacer nuestra propia versión de ExpandoObject:
public class MyExpando : DynamicObject
{
private Dictionary<string, object> members = new Dictionary<string, object>();
public override bool TryGetMember(GetMemberBinder binder, out object result)
{
if (this.members.TryGetValue(binder.Name, out result))
{
return true;
}
else
{
return true;
}
}
public override bool TrySetMember(SetMemberBinder binder, object value)
{
if (members.ContainsKey(binder.Name))
{
members[binder.Name] = value;
}
else
{
members.Add(binder.Name, value);
}
return true;
}
}
Lo que hacemos es muy parecido a lo que intuíamos que hacía internamente ExpandoObject: cuando se asigna un valor (TrySetMember) este se guarda en un diccionario, y luego cuando se recupera (TryGetMember) se mira si existe y en caso de que no exista se devuelve null (en vez de una excepción). Para comprobar que todo funciona bien, un pequeño programa de prueba:
class Program
{
static void Main(string[] args)
{
dynamic expando = new MyExpando();
expando.FirstName = "Hola";
Console.WriteLine(expando.FirstName);
if (expando.LastName == null)
{
Console.WriteLine("null");
}
Console.ReadKey();
}
}
La salida es “Hola” en una línea y “null” en la siguiente. Si en cambio en el else del TryGetMember hubiéramos escrito un “return false;” el comportamiento sería idéntico al de ExpandoObject: RuntimeBinderException.
En el próximo post retomaré el experimento de librería para CRPGs y como DynamicObject simplifica la implementación del patrón prototype.
El uso de dynamic no está solamente limitado a crear variables, métodos, lambdas,… con tipos dinámicos, si no que se ha añadido un nuevo namespace a C# 4.0 llamado System.Dynamic que contiene diferentes clases e interfaces relacionadas con la resolución de operaciones dinámicas (las clases de nombre XYZBinder). A parte de estas clases hay dos especiales que merece la pena comentar con más detalle: ExpandoObject y DynamicObject.
ExpandoObject está definido de la siguiente manera en la MSDN:
Represents an object whose members can be dynamically added and removed at run time.
Básicamente ExpandoObject es un objeto al que se le pueden añadir nuevas propiedades, métodos, eventos,… en tiempo de ejecución. Vamos a ver cómo. Si declaramos una variable de tipo ExpandoObject podemos observar que no tiene casi métodos o propiedades:
La “magia” ocurre cuando declaramos un ExpandoObject como dynamic:
Intellisense nos avisa que como expando es una variable dinámica, todas las operaciones se resolverán en tiempo de ejecución. Lo cual nos va a permitir hacer cosas como esta:
Era de esperar que el código compilara ya que usando una variable del tipo dynamic casi cualquier cosa vale. Pero lo que no está tan claro es porque el código ejecuta, ya que como vimos en la primera captura ExpandoObject no tiene una propiedad llamada Lives ni un método llamado SomeMethod que acepte un int y devuelve un string. Si volvemos a la MSDN nos encontramos que la clase está declarada de la siguiente manera:
public sealed class ExpandoObject : IDynamicMetaObjectProvider, IDictionary<string, Object>, ICollection<KeyValuePair<string, Object>>, IEnumerable<KeyValuePair<string, Object>>, IEnumerable, INotifyPropertyChanged
ExpandoObject implementa muchas interfaces, pero la única “especial” es IDynamicMetaObjectProvider, así que podemos asumir que es la que está relacionada con la “magia” de antes. De todas formas también vemos que ExpandoObject se comporta como un IDictionary<string, object>, así que es más o menos fácil imaginarse que puede estar pasando internamente: cuando se asigna un valor a una propiedad que no existe, de alguna forma que desconocemos se guarda el nombre de la propiedad y el valor en un diccionario interno y cuando se lee la propiedad este valor se recupera del mismo. Nada muy sorprendente, se podría incluso pensar que esto es solo “syntactic sugar” que traduce expando.Lives = 3 a expando.Add(“Lives”, 3) o expando[“Lives”] = 3 (no lo es, pero lo parece).
¿Y cuándo usar ExpandoObject? Pues ExpandoObject debería usarse en los sitios que se tenga un Dictionary<string, object>, por ejemplo cuando se leen pares de clave-valor de ficheros de configuración, o cuando se necesite una clase a la que se le pueda añadir funcionalidad en tiempo de ejecución, lo cual es más raro, pero hay casos de sistemas que requieren esta flexibilidad y las alternativas a usar ExpandoObject suelen tener una sintaxis horrorosa y no ofrecen casi ninguna ventaja.
Y finalmente, ¿y sí queremos modificar el comportamiento de ExpandoObject? Pues desgraciadamente no se puede, pero la buena noticia es que podemos hacer nuestra propia versión de ExpandoObject si implementamos IDynamicMetaObjectProvider. Pero eso lo dejo para el próximo post.
NET está haciendo una apuesta muy fuerte por los lenguajes dinámicos y sus características. Primero con toda la infraestructura del DLR (Dynamic Language Runtime), IronRuby, IronPython,… y ahora añadiendo mejoras en la interacción de estos nuevos lenguajes con C# a través de la palabra reservada dynamic.
dynamic es un nuevo tipo, pero es un tipo especial que se resuelve en tiempo de ejecución y no en tiempo de compilación, es decir, podemos hacer algo como:
dynamic a = GetDynamicObject();
a.SomeMethod();
Y realmente, podríamos ejecutar cualquier cosa sobre a, porque el Visual Studio no tiene ni idea de que es a hasta que se ejecute este código (vamos, que cuando le damos al punto no sale nada en el Intellisense ;) ). Esto seguramente le resultará raro a mucha gente, ya que por lo general los programadores de C# estamos acostumbrados al tipado estático (los tipos se saben en tiempo de compilación) pero si queremos poder interactuar con lenguajes dinámicos necesitamos que C# soporte el tipado dinámico.
Hay que tener en cuenta que dynamic no es lo mismo que object ni que var. Por ejemplo, esto compila con dynamic:
dynamic a = "hola";
a.SomeMethod();
int i = a;
Como a es dinámico, todas las operaciones en las que a esté involucrado se resolverán en tiempo de ejecución (a pesar de que claramente las dos van a petar). Si hacemos esto:
dynamic num = 3;
var result = num + 5;
Y usamos el visual para ver el tipo de result, nos saldrá que es dynamic, ya que como num es dynamic, a pesar de que tiene un entero dentro y le estamos sumando otro entero, toda la operación pasa a ser dinámica. Así mismo dynamic no solo puede usarse para declarar tipos aislados, sino también para parámetros en funciones. Esto es perfectamente legal:
public
void Method(dynamic argument)
{
argument.DoSomething();
}
En tiempo de compilación funcionará, y luego en tiempo de ejecución dependerá de si el objeto argument tiene un método llamado DoSomething sin parámetros. Si no es el caso recibiremos una RuntimeException al llegar a esa línea de código. Esto también es legal en tiempo de compilación:
Func<dynamic, bool> func = x => x.BoolenValue;
Donde habríamos declarado un delegado que recibe un tipo dinámico y devuelve un bool.
La verdad que para C# dynamic es un arma de doble filo: por un lado te permite hacer aberraciones ya que cualquier cosa dinámica compila y todos los errores te los vas a encontrar en tiempo de ejecución. Pero por otro lado te permite hacer cosas que son muy difíciles (o engorrosas) de expresar con C# 3.0 y que en ciertas situaciones son muy útiles. En los próximos posts explicaré más en detalle nuevas características de dynamic y el DLR y como nos pueden ayudar a hacer sistemas muy flexibles en C#.
Para más información: http://msdn.microsoft.com/en-us/library/dd264736%28VS.100%29.aspx
Hace unos días recibí una noticia que la verdad me hacía mucha ilusión recibir: he sido renovado como MVP de DirectX/XNA :) Este es mi segundo año como MVP y estoy encantado de poder seguir participando en esta tecnología que personalmente creo que tiene mucho futuro (aunque los principios siempre sean difíciles).
Además este año (que yo sepa) se nos unen como MVPs de DirectX/XNA dos personas que admiro muchísimo: Iñaki Ayucar (¡otro español!) y Promit Roy. ¡Enhorabuena a ambos!
![mvplogo[1] mvplogo[1]](http://kartones.net/blogs/jadengine/mvplogo1_thumb_0E5E33BD.jpg)
Una de las cosas que creo es importante cuando usas un lenguaje es entender como funciona por dentro. Por ejemplo es cierto que C# se parece mucho a Java pero a bajo nivel presentan muchas diferencias de filosofía y diseño que creo son importantes de entender para hacer un uso eficaz del mismo. Algo similar ocurre cuando alguien que está acostumbrado a C++ se pasa a C#, muchas veces piensa que es un C++ “fácil” (sin punteros, sin gestión de memoria) y luego se queja de que su rendimiento es mucho peor, que va lento,… cuando a veces todo esto es porque está usando C# como si fuera C++ y las cosas no funcionan así.
Un ejemplo de libro de esta situación es la implementación del patrón Singleton en C#. Si buscáis un poco, en muchas páginas webs para implementar un Singleton lazy y thread-safe en Java/C++ se recomienda la siguiente implementación:
public sealed class Singleton
{
static Singleton instance = null;
static readonly object padlock = new object();
Singleton()
{
}
public static Singleton Instance
{
get
{
if (instance == null)
{
lock (padlock)
{
if (instance == null)
{
instance = new Singleton();
}
}
}
return instance;
}
}
}
La sintaxis es de C#, pero lo importante es la idea del if/lock/if. A esto se le llama double-checked-locking y por motivos bastante sutiles esa implementación no es totalmente thread-safe (más detalles en este paper). El problema de thread-safe se puede solucionar haciendo la variable instance volatile (a partir de la JDK 5.0 y Visual C++ 2005, ni idea en otros compiladores de C++).
El problema es que como mucha gente de Java/C++ se ha acostumbrado a implementar el Singleton de esa manera, cuando llegan a C# hacen lo mismo. Pero resulta que en C# la implementación correcta es la siguiente:
public sealed class Singleton
{
static readonly Singleton instance = new Singleton();
static Singleton()
{
}
Singleton()
{
}
public static Singleton Instance
{
get
{
return instance;
}
}
}
Visto así por encima, esa implementación no parece ni lazy, ni thread-safe, ni nada de nada. Pero si nos cogemos el gran CLR via C# (libro totalmente recomendado) podemos entender como es que tan poco código hace tantas cosas. Toda la implementación gira en torno a los constructores estáticos y como funcionan dentro de .NET. Lo primero es entender que esto:
public sealed class Singleton
{
static int a = 5;
}
En IL se traduce a esto:
public sealed class Singleton
{
static int a;
static Singleton()
{
a = 5;
}
}
Y que por ejemplo esto otro:
public sealed class Singleton
{
static int a = 5;
static int b;
static Singleton()
{
b = 10;
}
}
En IL queda así:
public sealed class Singleton
{
static int a;
static int b;
static Singleton()
{
a = 5;
b = 10;
}
}
Vamos, que las inicializaciones de variables estáticas una vez compilada la clase se incluyen dentro de un constructor estático (si el constructor existe se añaden al principio en el orden que son declaradas, si no existe se genera un constructor y se añaden).
En C#, cuando el compilador JIT (el que pasa de IL a código máquina) encuentra un constructor estático, mira si ese constructor ya se ha ejecutado o no:
- Si no se ha ejecutado, emite un lock, el código de ejecución y un unlock.
- Si se ha ejecutado, no emite nada.
Es decir, el JIT se asegura que el constructor estático solo se ejecute una vez aunque haya varios hilos, por eso en C# no hace falta poner el lock, ya que la línea new Singleton() se ejecuta dentro del constructor estático y el propio JIT ya se ha encargado que ese constructor solo sea llamado una vez. Con esto ya tenemos cubierto el tema de thread-safe, pero ¿y lo de que sea lazy?
Primero, tengo que reconocer que os he mentido un poco antes. Esta clase:
public sealed class Singleton
{
static int a = 5;
}
Y esta otra clase:
public sealed class Singleton
{
static int a;
static Singleton()
{
a = 5;
}
}
No generan exactamente el mismo IL: la primera está marcada con un atributo que se llama BeforeFieldInit y la segunda no. Cuando una clase está marcada como BeforeFieldInit el runtime del framework puede decidir ejecutar su inicializador de tipo (constructor estático) cuando se haga la primera referencia a ese tipo o antes, según le parezca. Pero si no está marcada con BeforeFieldInit (porque hemos puesto un constructor estático explícito) entonces el inicializador del tipo solo puede ejecutarse en el momento que se haga uso de ese tipo.
Es decir, como mi implementación del Singleton tiene un constructor estático, esta clase no está marcada como BeforeFieldInit y lo que haya dentro del constructor estático (new Singleton()) se ejecutará justo cuando haga Singleton.Instance. Pero si no tuviera un constructor estático la línea new Singleton() se podría ejecutar una hora antes de llamar a Singleton.Instance (por ejemplo), según decida el runtime. Y esto puede ser un problema porque no tenemos ni idea de cuando se va a ejecutar ese constructor y si tiene código muy pesado puede ralentizar el resto del programa en algún punto que no sea adecuado, o incluso ejecutarse y que luego resulte que nunca se utiliza en el programa.
Podéis encontrar una discusión mucho más en profundidad del tema en este artículo y este otro (cuentan lo mismo pero de forma mucho más técnica).
Actualización: Reed Copsey ha publicado un artículo sobre este mismo tema llamado "Just keep repeating: C# is not Java. C# is not C++" que contiene más ejemplos y resulta bastante interesante.
Hay un tipo de personas a las que envidio admiro de sobremanera: los emprendedores. Yo siempre he tenido ideas que he pensado que podrían ser un buen producto, o al menos suficentemente decente para funcionar. Cuando empecé con esto de los videojuegos tenía la idea del juego definitivo, normalmente un refrito de los tres o cuatro juegos de moda del momento, y junto con algunos amigos y algún colaborador nos lanzábamos a lo loco a intentar realizarlos - seguro que al igual que muchos de los que leéis estas líneas.
Todo empezaba genial, con la gente muy animada dedicando buena parte de su tiempo libre y de repente, un día:
- Pepito ha ligado el finde anterior y ya no tiene tiempo para seguir en el proyecto.
- Juanito lleva 5 días sin dormir enganchado al Call of Duty 6. Pero es para intentar conseguir nuevas ideas, ya sabéis, hay que jugar a todas las obras maestras - aunque tu juego sea un clon del pong.
- Luisito está más pelao que las ratas y se ha buscado un trabajo, así que cuando termina su jornada solo le apetece tumbarse en el sofá y ver la tele.
- Jaimito viendo que los demás no hacen ni el huevo, obviamente se contagia del espíritu reinante y tampoco hace nada.
Total, que al cabo de dos meses el resultado es un proyecto cancelado y lo mismo hasta alguna bronca o discusión entre los integrantes del equipo. Después de un tiempo, y ya siendo más “maduros”, este proceso se vuelve a repetir con idénticos resultados y así N veces hasta que uno tras otro nos vamos dando cuenta que mejor dedicarnos a otra cosa.
Pero hay gente que es capaz de salir de este círculo y llevar sus ideas adelante. Cuando te pones a analizarlo el resultado es sorprendente:
¡han convertido su proyecto en su trabajo!
Traduciendo: de lunes a viernes dedican entre las 9 y las 19 a su idea (cuando las cosas van bien, cuando hay que apretar parecen un 7 Eleven). Y eso que también tienen novias y les encanta viciarse al Call of Duty 6. Además, ¡la de riesgos a los que se exponen frente a un trabajo tradicional! Que si no encuentras clientes, los proveedores te la lían, no consigues financiación,…
Y esa es la forma de pensar que hace que uno no emprenda. O al menos es la que hace que yo no me atreva a emprender y admire a la gente que tiene los cojones de arriesgarse, y más en este país donde el fracaso está tan mal considerado – demasiado en mi opinión.
Así que este post va dedicado a toda esa gente que da ese salto, que pone todo su esfuerzo en perseguir sus ideas, en confiar en ellas, y en no dejarse achantar por todos los posibles problemas que puedan cruzarse en su camino.
Por suerte para mi, porque considero una fortuna poder hablar con este tipo de personas, conozco a bastante gente que ha emprendido y me gustaría dedicarles unas líneas:
- A Iñaki Ayucar que posiblemente sea el tío que más sabe de gráficos y código manejado de España, y que lleva años apostando por su empresa Simax, un simulador de conducción que es sencillamente impresionante.
- A mis compañeros de facultad que fundaron Vaelsys, una empresa especializada en visión artificial. Nunca se me olvidarán momentos como cuando Jorge hizo su parte de una práctica de Criptografía en Latín, o cuando Edu se vino a mi casa con su compañera a hacer prácticas de POO y el cabrón terminó comiendo palomitas mientras yo hacía el trabajo. Y encima al cabo de unas semanas vino quejándose de que solo había sacado un notable. Está claro que se le da bien ser jefe (y que yo soy un pringao).
- A la gente de Signum Software y en particular a Olmo del Corral, que se han lanzado a la aventura de lanzar un ORM open source en .NET con ideas bastante novedosas en algunos aspectos.
- A Cokidoo, empresa fundada por yens y Loover (entre otros), dos conocidos de Stratos-AD. Están poniendo toda la carne en el asador con una red social enfocada a estudiantes de movilidad (erasmus,…) que tiene un acabado impecable. Ahora mismo están participando en el concurso BBVA Open Talent así que pasaros por aquí y votadles.
- A ethernet, otro Stratero al que tuve la oportunidad de conocer en el Congreso de Desrrolladores de Videojuegos 08. Javi ha creado un producto llamado Agroguía que es un sistema de guiado GPS para tractores. Un curioso nicho de mercado, todo sea dicho :p
- A SiPoX, un habitual de las Kdds madrileñas de Stratos-AD que ha fundado Undead Code, otra empresa orientada hacia el tema de aplicaciones web y web 2.0. También participan en el BBVA Open Talent con Somflee, una mezcla entre red social y herramienta de gestión de contenidos digitales. Podéis votarles aquí.
Para todos ellos, lo mejor. A ver si un día de tanto hablar con vosotros se me contagia algo y me lanzo yo también a la aventura :) (¡acordaos de mi cuando seáis ricos!)
Ahora que ya hemos visto que es una expresión lambda podemos ver otra nueva característica mucho más “rara” de C# 3.0: los árboles de expresiones. En algunos lenguajes, como Lisp, se permite manejar el código como si fueran datos y los datos como si fueran código (hace un par de días leyendo el blog del gran Eric Lippert descubrí que a esto se le llama homoiconic). Los árboles de expresión son la forma que tiene C# de implementar esta funcionalidad (de forma algo limitada).
Por ejemplo, en el artículo anterior teníamos la expresión lambda:
Que devolvía si un número era menor que cinco o no. Expresado en forma de un delegado del tipo Func, el código sería:
Func<int, bool> f = num => num < 5;
Y la forma de poner lo mismo usando un árbol de expresiones es:
System.Linq.Expressions.Expression<Func<int, bool>> e = num => num < 5;
La diferencia entre Func y Expression es que f es una función que se puede ejecutar:
Pero para ejecutar e tenemos que hacer lo siguiente:
bool result = e.Compile()(10);
Es decir: para ejecutar un árbol de expresiones primero tenemos que compilarlo (porque son datos que representan código, no código en sí) y una vez compilado ya podemos utilizar el resultado como si fuera un método normal.
De forma gráfica, e internamente está representado por el siguiente árbol:
Pero lo interesante de los árboles de expresiones es que en vez de dejar que el compilador los genere automáticamente, podemos construirlos nosotros a mano. Una vez tenemos una idea de como se representan internamente generarlos por código es bastante fácil (aunque algo tedioso):
ParameterExpression param = Expression.Parameter(typeof(int), "num");
ConstantExpression five = Expression.Constant(5, typeof(int));
BinaryExpression lessThan = Expression.LessThan(param, five);
Expression<Func<int, bool>> e = Expression.Lambda<Func<int, bool>> (lessThan, new ParameterExpression[] { param });
Si hacemos un poco de memoria, en el mini motor de RPGs una parte bastante importante del código se encargaba de trabajar con las expresiones matemáticas que definen los valores de las variables de los objetos (los puntos de vida, el bonificador de ataque,…). Lo que hacíamos era definir el modificador en algún sitio (un fichero, una BD, …) como una expresión infijo, es decir, como esto:
(a) Attack = 3 * Level + 4 / 7 ^ 34 - 345
Se aplicaba el algoritmo de Shunting-Yard para transformarla en una expresión postfijo:
(b) Attack = 3 Level * 4 7 34 ^ / + 345 -
Y una vez teníamos (b) en forma postfijo se utilizaba un algoritmo para evaluar la expresión que lo que hace es construir un árbol con la siguiente pinta:
Y sinceramente, este árbol se parece un montón al árbol del ejemplo anterior :) Así que el cambio realizado en la librería de RPGs va a utilizar árboles de expresiones para permitirnos pasar de esto:
string strExp = "3 * Level + 4 / 7 ^ 34 - 345";
A esto:
Func<Evaluator, double> func = (evaluator) => 3 * evaluator.Chain(Level) + 4 / 7 ^ 34 - 345;
¡Hemos transformado una cadena de texto en una función en C#! Esta función recibe un objeto del tipo Evaluator (que se utiliza para calcular el valor de las variables como Level, Dexterity,… de forma recursiva) y devuelve un double (el valor de la fórmula).
El código que realiza esto es bastante sencillo (es una modificación del algoritmo de evaluación):
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using SLE = System.Linq.Expressions;
namespace GravityAge.Rpg
{
public static class Experiments
{
#region Methods
public static SLE.Expression<Func<Evaluator, double>> ToExpressionTree(this Expression expression)
{
Stack<SLE.Expression> operands;
SLE.Expression op1, op2;
SLE.ParameterExpression parameter = SLE.Expression.Parameter(typeof(Evaluator), "evaluator");
operands = new Stack<SLE.Expression>();
for (int i = 0; i < expression.Terms.Count; i++)
{
switch (expression.Terms[i].TermType)
{
case TermType.Number:
{
operands.Push(SLE.Expression.Constant(expression.Terms[i].Value, typeof(double)));
break;
}
case TermType.Variable:
{
operands.Push(SLE.Expression.Call(parameter, "Chain", new Type[] { }, SLE.Expression.Constant(expression.Terms[i].Text)));
break;
}
case TermType.Operator:
{
op2 = operands.Pop();
op1 = operands.Pop();
switch (expression.Terms[i].TermOperator)
{
case TermOperator.Add:
{
operands.Push(SLE.Expression.Add(op1, op2));
break;
}
case TermOperator.Subtract:
{
operands.Push(SLE.Expression.Subtract(op1, op2));
break;
}
case TermOperator.Multiply:
{
operands.Push(SLE.Expression.Multiply(op1, op2));
break;
}
case TermOperator.Divide:
{
operands.Push(SLE.Expression.Divide(op1, op2));
break;
}
case TermOperator.Power:
{
operands.Push(SLE.Expression.Power(op1, op2));
break;
}
}
break;
}
default:
{
throw new ArgumentException("An undefined token (" + expression.Terms[i].Text + ") appeared while calculating an expression.");
}
}
}
if (operands.Count == 1)
{
return SLE.Expression.Lambda<Func<Evaluator, double>>(operands.Pop(), parameter);
}
else
{
return null;
}
}
#endregion
}
}
Como podréis ver el código se encuentra dentro de una clase llamada Experiments, ya que no tenía muy claro si iba a ser capaz de hacer esto o no cuando empecé :p Así que estos días me dedicaré a refactorizar y pulir algunas cosas y en breve subiré una nueva versión de la librería a Kartones por si a alguien le interesa.
En el artículo anterior comenté el diseño de una pequeña librería para implementar las reglas de un RPG. Desde ese día he realizado pequeños cambios en el código con el objetivo de simplificarlo y aumentar el rendimiento. En este artículo (y el próximo) voy a hablar de algunas características nuevas de C# 3.0 que permiten que nuestra librería sea mucho más flexible.
En C# 3.0 la gente de Microsoft añadió muchas mejoras en el lenguaje, la mayoría inspirada en los lenguajes funcionales como Lisp, OCaml,… En particular hay dos que van de la mano: las expresiones lambda y los árboles de expresiones. Este primer artículo va a tratar de las expresiones lambda, así que recordemos que era un delegado y poco a poco veremos el porqué se añadieron todas estas nuevas características a C#.
Por ejemplo, queremos filtrar una lista de elementos T en base a un criterio pero queremos que el criterio sea modificable. Este “criterio” es una función que recibirá un T y devolverá un booleano indicando si el elemento cumple el criterio o no. En código tradicional de C# esto sería un delegado y sería algo como lo que sigue:
delegate bool Criteria<T>(T element);
class Program
{
static List<T> FilterList<T>(List<T> list, Criteria<T> criteria)
{
List<T> newList = new List<T>();
foreach (T element in list)
if (criteria(element))
newList.Add(element);
return newList;
}
static bool CriteriaLessThan5(int intValue)
{
return intValue < 5;
}
static bool CriteriaBiggerThan5(int intValue)
{
return intValue > 5;
}
static void Main(string[] args)
{
List<int> numbers = new List<int>() { 1, 2, 3, 4, 5, 6, 7, 8, 9 };
List<int> numbers2;
numbers2 = FilterList(numbers, CriteriaLessThan5);
foreach (int number in numbers2)
Console.WriteLine(number);
numbers2 = FilterList(numbers, CriteriaBiggerThan5);
foreach (int number in numbers2)
Console.WriteLine(number);
Console.ReadKey();
}
}
Si os fijáis he declarado un delegado (Criteria) que recibe un elemento T y devuelve bool. Luego he añadido una función FilterList que recibe una lista y un criterio y devuelve la lista filtrada y por último en el main he usado dos funciones diferentes para filtrar una misma lista de números.
A partir de C# 2.0 se añadió al lenguaje la capacidad de declarar delegados anónimos (al vuelo). Usando delegados anónimos el código anterior tendría la siguiente pinta:
delegate bool Criteria<T>(T element);
class Program2
{
static List<T> FilterList<T>(List<T> list, Criteria<T> criteria)
{
List<T> newList = new List<T>();
foreach (T element in list)
if (criteria(element))
newList.Add(element);
return newList;
}
static void Main(string[] args)
{
List<int> numbers = new List<int>() { 1, 2, 3, 4, 5, 6, 7, 8, 9 };
List<int> numbers2;
numbers2 = FilterList(numbers, delegate(int e) { return e < 5; });
foreach (int number in numbers2)
Console.WriteLine(number);
numbers2 = FilterList(numbers, delegate(int e) { return e > 5; });
foreach (int number in numbers2)
Console.WriteLine(number);
Console.ReadKey();
}
}
Ahora los delegados están metidos directamente en la llamada al método FilterList. En C# 3.0 se añaden las expresiones lambda que es una forma más “bonita” de escribir lo mismo. Además se añaden los delegados genéricos Action y Func que nos ahorran tener que declarar delegados propios. Con lo que en C# 3.0 nuestro código quedaría así:
class Program3
{
static List<T> FilterList<T>(List<T> list, Func<T, bool> criteria)
{
List<T> newList = new List<T>();
foreach (T element in list)
if (criteria(element))
newList.Add(element);
return newList;
}
static void Main(string[] args)
{
List<int> numbers = new List<int>() { 1, 2, 3, 4, 5, 6, 7, 8, 9 };
List<int> numbers2;
numbers2 = FilterList(numbers, e => e < 5);
foreach (int number in numbers2)
Console.WriteLine(number);
numbers2 = FilterList(numbers, e => e > 5);
foreach (int number in numbers2)
Console.WriteLine(number);
Console.ReadKey();
}
}
Fijaos cómo ahora ya no se declara ningún delegado “Criteria” porque FilterList recibe un Func<T, bool>, es decir, un delegado genérico que tiene un parámetro T y devuelve un bool. Y además hemos sustituido en la llamada a FilterList lo de “delegate(int e)…” por una expresión lambda (=>) que recibe un “e” y devuelve si ese “e” es menor o mayor que 5 (el compilador automáticamente infiere que “e” tiene que ser de tipo int).
En el próximo artículo veremos como se relacionan las expresiones lambda con los árboles de expresiones, que nos permiten generar y modificar código de forma dinámica dentro del propio programa (código como datos, algo bastante común en los lenguajes funcionales) y la utilidad que tiene esto en una librería para implementar RPGs.
Aviso: este artículo es bastante largo, aunque no demasiado técnico, así que debería ser fácil de seguir.
Desde hace bastantes años siempre he tenido la idea de portar las reglas del Dungeons and Dragons (o algo similar) al ordenador, pero siempre he terminado fracasando por varios motivos:
- Son sistemas muy complicados.
- Están llenos de excepciones.
- Tienen que ser muy extensibles porque seguramente aparecerán nuevas reglas con el paso del tiempo.
Estos días he tenido bastante tiempo libre y he decido intentar por n-esima vez hacer un “mini-framework” que me permita implementar algo parecido a una simplificacón del sistema d20 para mis propios proyectos. El resto de este artículo va a tratar de los problemas que he encontrado para realizar esta implementación básica (aún le faltan muchas cosas). Al final de todo hay un link con el código fuente para él que le quiera echar un vistazo.
El primer problema que me he encontrado siempre al intentar hacer este tipo de sistemas es que los objetos tienen muchísimas propiedades. Por ejemplo un personaje tiene atributos, habilidades, dotes, conjuros, equipo, clases,… A pesar de agrupar en clases y usar todas las herramientas que proporciona la programación orientada a objetos siempre he terminado teniendo en cada clase un ejército de propiedades que se vuelve inmanejable.
Así que decidí atacar el problema de otra forma: cada clase tiene una tabla hash que representa sus propiedades. De esta forma cada clase puede tener “infinitas” propiedades y además las puedo definir cuando quiera. Si quiero una propiedad hago:
objeto.Set("Propiedad", valor);
Y si quiero obtener el valor hago:
object resultado = objeto.Get("Propiedad");
La verdad que casi cualquiera que vea esto pensará que esta aproximación tiene muchos problemas:
- Se usan strings para los nombres de las propiedades y es muy fácil equivocarse al escribirlos (confundir una mayúscula, una letra,…) y que el programa de un error o devuelva un valor erróneo. Depurar esto es un jaleo bastante serio.
- Hay que calcular hashes de strings todo el rato (una operación bastante lenta).
- No es tipada: tenemos objects por todos los lados que tenemos que castear al tipo correcto (lento y propenso a errores).
La verdad que yo mismo no estaba muy convencido de que esto fuera una buena idea hasta que me encontré este artículo de Steve Yegge donde habla del patrón prototype y de la programación basada en prototipos en vez de orientada a objetos.
El artículo, aún siendo largo, es bastante interesante y explica bastante bien que es y como se usan los prototipos. Resumiendo mucho: un prototipo es una tabla hash que contiene todas las propiedades y operaciones de un objeto (en vez de ser entidades independientes como en la programación orientada a objetos).
Dado que yo programo en C# y que este lenguaje no da soporte para prototipos (como por ejemplo IO), implementé las ideas del post de Steve en la clase Prototype. Esta clase contiene:
- Un campo parent que es el prototipo del que se “hereda”.
- Un campo properties que es el diccionario de propiedades.
- Una propiedad IsReadOnly que permite hacer al prototipo de solo lectura.
- Cuatro métodos (Get, Set, Contains y Remove) que permiten modificar la información del prototipo.
- Y un método Clone que permite crear un nuevo prototipo que herede del actual.
Lo único interesante de esta clase es que los métodos Get y Contains permiten buscar valores en el padre del prototipo. Es decir, parent cumple la función de la superclase en la programación orientada a objetos. Como podéis ver, esta implementación no es nada complicada y permite hacer cosas como:
Prototype guardia = new Prototype();
guardia.Set("Ataque", 10);
guardia.Set("Vida", 5);
Prototype alitar = guardia.Clone();
alitar.Set("Nombre", "Alitar");
alitar.Set("TieneMiedoALosDragones", true);
Como se puede ver, he definido un guardia prototipo y luego un guardia especial que he llamado Alitar y que tiene miedo a los dragones. Alitar se comportará como cualquier otro guardia, pero cuando vea un dragón huirá en vez de luchar (¿quizás también tendría que ponerle más inteligencia? :p).
Una vez resuelto el problema fácil, queda el difícil (y bastante más interesante): calcular lo que vale una variable en un RPG. Por ejemplo: ¿cuanto vale el bonificador de ataque cuerpo a cuerpo de un personaje? En el d20 este número depende de muchísimas cosas:
- La fuerza del personaje (o la destreza según que arma esté usando y si tiene las dotes adecuadas).
- El nivel de cada clase del personaje.
- Sus dotes.
- Conjuros.
- Otras habilidades o modificadores circunstanciales.
Básicamente calcular ciertos valores en un RPG es un lío increíble. Hace tiempo descubrí una herramienta llamada PCGen que es un generador de personajes para el sistema d20. Esta herramienta tiene un lenguaje de definición de reglas muy potente, por ejemplo:
BONUS:COMBAT|AC|Level / 2|TYPE=NaturalArmor.REPLACE
Esta línea significa un bonificador de combate a la armadura (AC) igual al nivel del personaje dividido entre dos, del tipo armadura natural que reemplaza a cualquier bonificador existente del mismo tipo. Este ejemplo es bastante básico, pero hay verdaderas virguerías definidas con él.
Así que realmente el segundo problema son dos subproblemas:
El primer subproblema es “sencillo” de resolver si uno no se complica mucho la vida. En mi caso decidí imponer a las expresiones la restricción de que las diferentes partes que la forman deberían estar separadas por un espacio. Esto me permite partir la expresión con una sola llamada a String.Split. Luego un simple parser recorre los pedazos de la expresión y los clasifica en cuatro tipos:
-
Números: 3, 5.7, 23434,…
-
Operadores: +, –, *, /, ^
-
Símbolos: (, )
-
Variables: cualquier otra cosa
Esto debería ser suficiente para definir cualquier tipo de operación matemática que pueda ser de utilidad. En el fichero ExpressionParser.cs se puede ver el código de este pequeño parser. Una cosa “curiosa” del código es que al final del parseo se ejecuta una función llamada ShuntingYard. Esta función implementa el algoritmo de Shunting-Yard inventado por Djikstra que permite pasar expresiones en formato infijo (2 + 3) a formato postfijo o notación polaca inversa (2 3 +).
¿Y por qué hacer semejante cosa? Porque la notación postfijo tiene una gran ventaja: es muy fácil de evaluar. Estoy seguro que hacerse un evaluador de expresiones en formato infijo no es demasiado difícil, pero en formato postfijo es trivial y el coste de transformar la expresión es muy bajo (y solo hay que pagarlo una vez).
Y por último hay que ver como buscar los modificadores que afectan a una variable y como calcular su valor. Imaginemos que tenemos un objeto Espada definido de esta forma:
Prototype arma = new Prototype();
arma.Set("Nombre", "Espada");
arma.Set("Dureza", 5);
Para obtener su dureza basta con escribir:
int dureza = arma.Get<int>("Dureza");
Ahora imaginemos que cojo a la Espada y la modifico añadiéndole otro prototypo que representa que está hecha de Mithril, lo que le da una dureza extra.

Para calcular su dureza ahora hay que buscar en sus modificadores pero también en los del objeto Mithril. En el siguiente diagrama se puede ver un ejemplo un poco más complicado:

Se puede ver que se ha formado una estructura en forma de árbol, así que para buscar los modificadores decidí utilizar una búsqueda en anchura: primero la raiz, luego los hijos, luego los hijos de los hijos,…
Pero ahora imaginemos que esa espada está en manos de un personaje que es de la clase bárbaro, y que los bárbaros hacen que todos los objetos en sus manos sean menos duros (porque tienden a romperlos).

Como se puede ver la dureza del arma ha sido modificada por un valor que no está en sus hijos, si no que está en otra parte del árbol. Así que hay que modificar un poco la búsqueda en anchura:
Pero sigamos imaginando: ahora el personaje también tiene una Armadura y esta a su vez es de Adamantio.
Está claro que si pregunto por la dureza de la Espada no debería influir para nada los modificadores a la dureza de la Armadura o del Adamantio. Para resolver este nuevo problema hay que añadir una nueva propiedad a los modificadores: visibilidad (global o local). Y hay que volver a cambiar la búsqueda en anchura:
-
Se guarda el nodo que inició la búsqueda (la Espada).
-
Se comienza la búsqueda desde la raíz (el Personaje).
-
Si el modificador está en un nodo fuera del subárbol definido por el nodo que inició la búsqueda, solo se aplicarán los modificadores globales. Si el modificador está definido en un nodo perteneciente al subárbol se aplicarán todos los tipos de modificadores.
La búsqueda se inició en el objeto Espada, así que fuera del subárbol que define (marcado en rojo) solo se aplican los modificadores marcados como global. Así conseguimos que se aplique el modificador de Bárbaro pero no el de Armadura o Adamantio.
Esta forma de buscar modificadores cubre todos los casos que se me han ocurrido. Seguro que hay situaciones que no resuelvo, pero no me pienso complicar más la vida (de momento y sin una buena razón).
Ya está la búsqueda de modificadores, ahora queda ver como calcular el valor de una variable. El valor de una variable es modificado por los objetos de tipo Modifier que a su vez contienen expresiones matemáticas en forma de objetos Expression. Estas expresiones pueden ser simples como hemos visto hasta ahora o depender de otras variables, por ejemplo:

Ahora la dureza de la Espada depende de su peso también. Para resolver esto la evaluación de variables se ejecutará de la siguiente manera:
-
Cuando se comienza a evaluar una variable se apunta en una tabla de variables calculadas con el valor de menos infinito.
-
Si esta variable tiene un modificador que contiene una variable, se consulta a la tabla de variables ya calculadas.
-
Si el valor de la variable es menos infinito hay una referencia circular (variable = variable) y el valor no se puede resolver. Excepción.
-
Si es otra cosa se devuelve el valor y se continúa evaluando la variable actual.
-
Si la variable no se encuentra en la tabla de variables ya calculadas se inicia una nueva evaluación para esa variable (vuelta al principio de este proceso).
-
Cuando se termina de calcular el valor de una variable se actualiza en la tabla.
La clase Evaluator es la que se encarga de la búsqueda de modificadores y evaluación de variables.
El código fuente de todo este jaleo se puede descargar desde este link (está bajo licencia MIT así que se puede hacer cualquier cosa con él). El zip contiene una solución de Visual Studio 2008 (C#) que está compuesta de dos proyectos:
-
GravityAge.Statecraft.Core: el código de los prototipos y la evaluación de variables. Está bastante comentado así que debería ser fácil de comprender si se ha entendido este artículo.
-
GravityAge.Statecraft.Tests: unas cuantas pruebas unitarias para comprobar que todo está más o menos bien (debería hacer más, pero de momento con estas me basta). Hay una prueba que no se pasa (comprobar que una expresión infijo está bien formada) pero no sé como hacer que el parser se de cuenta de eso sin complicarme la vida demasiado, así que dejo el test roto para que no se me olvide que algún día debería solucionarlo.
Este finde antes de irme de viaje he podido participar en otro “12 Meses 12 Proyectos”. 12m12p es una idea que se nos ocurrió a un grupo de amigos de Madrid de juntarnos de vez en cuando e intentar hacer un juego completo en un fin de semana. Normalmente no terminamos nada, pero nos lo pasamos muy bien :)
Esta vez se decidió hacer un juego de Air Hockey para Surface, ya que teníamos acceso a los SDKs de la mesita (aunque no teníamos una para probar, una lástima). Y sorprendentemente, conseguimos terminarlo y dejarlo bastante pulido :)
En este 12m12p participamos Luis, Riqui, Antón, Pedro, Olmo y un servidor y en el blog de Luis podéis encontrar varias imágenes y un vídeo de ejemplo. El juego está desarrollado en C#+WPF y nos agenciamos un HP TouchSmart para hacer las pruebas, pero a pesar de que soporta multitouch no reconoce cada toque como un ratón diferente así que acabamos poniendo varios ratones a un portatil normal para jugar.
La verdad que voy a echar bastante de menos 12m12p estos meses que voy a estar fuera, ya que son una oportunidad muy buena para vivir un proyecto (aunque sea pequeño) de principio a fin, donde lo que importa es terminar como sea la aplicación (si la gente viera algunas partes del código iban a llorar :p).
More Posts
Next page »