Dynamic (II): ExpandoObject
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.