Please read this post for my reasons behind this article series.
Some people have asked me, why I try to do things in C# that are clearly Ruby-ideoms. The short answer is: Because I can. The longer answer involves thoughts about that you should never stop learning. For a programmer, this involves pushing your tools to their boundaries and beyond. Seeking new insights other places and apply them to your environment. Stuff like that.
Catch-all property (like Ruby’s method_missing)
One of the powerful features of Ruby is method_missing. With this seemingly innocent construct you are able to make a class respond to things that are not statically defined beforehand.
Typical uses are for ORMs like ActiveRecord, that enables you to map class properties to database table-fields without explicitly defining the fieldnames in your class.
The way ActiveRecord does this is, that the model base-class contains a Hash (Dictionary) called attr. When a table-record is loaded, all fields are loaded into this hash. And method_missing are then used to map property-names directly to keys in the hash.
user = User.find(13) puts user.email
This can also be done in C# using DynamicObject. http://msdn.microsoft.com/en-us/library/system.dynamic.dynamicobject.aspx
DynamicObject is a rather bold introduction into a strong-typed language like C#, since it expose members such as properties and methods at run time, instead of at compile time.
using System;
using System.Collections.Generic;
using System.Dynamic;
public class MyFakeORM : DynamicObject {
// For a clearer example, I don't go into the stuff about loading data from the database into this model
// Dictionary to hold all fields from the loaded record
Dictionary _attr = new Dictionary();
// Catch-all methods for getting and setting a "missing" property
public override bool TrySetMember(SetMemberBinder binder, object value) {
_attr[binder.Name] = value;
return true;
}
public override bool TryGetMember(GetMemberBinder binder, out object result) {
return _attr.TryGetValue(binder.Name, out result);
}
}
Note: For this to work, you also need to define your instance-variable as a dynamic instead of MyFakeORM:
dynamic user = new MyFakeORM(); user.email = "carsten@sarum.dk"; Console.WriteLine(user.email);
If you want to enable access with both properties as shown above AND as a common Dictionary, you need to add setters and getters for the class:
using System;
using System.Collections.Generic;
using System.Dynamic;
public class MyFakeORM : DynamicObject {
...
public object this[string key] {
get {
return _attr[key];
}
set {
_attr[key] = value;
}
}
...
// The rest is as before
}
Now all of the following is valid:
dynamic user = new MyFakeORM(); user.email = "carsten@sarum.dk"; user['email'] = "carsten@sarum.dk"; Console.WriteLine(user.email); Console.WriteLine(user['email']);