Welcome to Kartones.Net Sign in

January 2010 - Posts

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.

Posted by Vicente | 2 comment(s)
Filed under: ,

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:

expando

La “magia” ocurre cuando declaramos un ExpandoObject como dynamic:

expando2

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:

expando3

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.

Posted by Vicente | 3 comment(s)
Filed under: ,