Using custom dynamic object with C# 4.0 for even more flexible parameter passing

A few days back, Rob Conery wrote a post comparing C# and Ruby, and how using the dynamic keyword in C# lets you pass in arguments into a C# method in a way similar that it is done in Ruby land.  I’m not a Rubyist, but the same ideas are in JavaScript as well.  I have worked on .Net applications that take in a dictionary parameter of named value pairs, and sometimes it has been a bit of a bear to maintain, but I agree with Rob that it can be very flexible, especially if you don’t have your API nailed down quite yet and you are constantly changing your method signature.

Rob gives an example of using the dynamic keyword in C# to pass in arguments like so:

  1: DoStuff(new { Message = "Hello Monkey" });
  2: 
  3: static void DoStuff(dynamic args) {
  4:     Console.WriteLine(args.Message);
  5: }

However, I quickly saw a problem with this method, and that is that all the arguments used inside of the DoStuff method are now required to be defined in the dynamic object being passed in.  So if I had a DoStuff method like so:

  1: static void DoStuff(dynamic args) { 
  2:     Console.WriteLine(args.Message); 
  3:     Console.WriteLine(args.Name);
  4: }

This code would crash at runtime unless you passed in the Name as well.  In JavaScript, we can check to see if a value has been defined on an object like so:

  1: if(obj.Name){
  2:     //do something with obj.Name
  3: }
  4: else {
  5:     //Ignore because obj.Name is not defined.
  6: }

It would be really helpful if we could do the same thing in C#, so I set out on a mission to try to see if this was possible.  After much digging on the web (and many thanks to this <a href="http://www.codeproject.com/KB/cs/dynamicincsharp.aspx" target="_blank">excellent article</a>from Anoop Madhusudanan) I found a method that gets close, but not quite there.  Instead of passing in the argument to the if statement, we pass in a Has(Member Name) property.  For instance, if we wanted to check to see if a Name property was on our dynamic object, we call if(obj.HasName), and if we were looking for age if(obj.HasAge), etc..

We accomplish this by creating a new class called DynamicParam that derives from DynamicObject, and overload TryGetMember, TrySetMember, and TryInvokeMember.  A dictionary member variable keeps track of all the dynamic members we have defined, as well as stores the values for these members.  Elsewhere, I have read that internally, this is how the ExpandoObject works.

I’m not saying this is necessarily a good practice, but it is interesting use of using the dynamic features of C#, and you might find it useful in your application.  The full source for the sample console app is below, and you can find it on gist here http://gist.github.com/517890.

  1: using System;
  2: using System.Collections.Generic;
  3: using System.Linq;
  4: using System.Text;
  5: using System.Dynamic;
  6: 
  7: namespace ConsoleApplication2
  8: {
  9:     class Program
 10:     {
 11:         static void Main(string[] args)
 12:         {
 13:             dynamic param = new DynamicParam();
 14:             param.Message = "butt";
 15:             param.SayHi = new Action<string> (s => Console.WriteLine(s));
 16:             
 17:             DoStuff(param);
 18:             Console.ReadLine();
 19:         }
 20: 
 21:         static void DoStuff(dynamic args)
 22:         {
 23:             Console.WriteLine(args.Message);  //This param is required since we don't check if exists or not
 24:             if(args.HasTitle)  //Check to see if args Has a Title param, since we don't this statement doesn't execute
 25:                 Console.WriteLine(args.Title);
 26:             if(args.HasSayHi) //Check to see if args Has a SayHi method
 27:                 args.SayHi("h22i");
 28:         }
 29:     }
 30: 
 31:     class DynamicParam : DynamicObject
 32:     {
 33:         private Dictionary<string, object> _members = new Dictionary<string, object>();
 34: 
 35:         public override bool TryGetMember(GetMemberBinder binder, out object result)
 36:         {
 37:             if (binder.Name.StartsWith("Has"))
 38:             {
 39:                 var name = binder.Name.Substring(3);
 40:                 if (_members.ContainsKey(name))
 41:                     result = true;
 42:                 else
 43:                     result = false;
 44:                 return true;
 45:             }
 46:             else
 47:             {
 48:                 if (_members.ContainsKey(binder.Name))
 49:                 {
 50:                     result = _members[binder.Name];
 51:                     return true;
 52:                 }
 53:                 else
 54:                 {
 55:                     result = false;
 56:                     return true;
 57:                 }
 58:             }
 59:         }
 60: 
 61:         public override bool TrySetMember(SetMemberBinder binder, object value)
 62:         {
 63:             if (!_members.ContainsKey(binder.Name))
 64:                 _members.Add(binder.Name, value);
 65:             else
 66:                 _members[binder.Name] = value;
 67:             return true;  
 68:         }
 69:     }
 70: }
 71: