dynamic (III): DynamicObject
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.