C# Dynamic Features Helped Me Learn Ruby

Tweet

Up until version 3.0, C# had been a static language. Dynamic features were introduced to the language in version 4.0, as an attempt to improve COM support and interoperability between C# and dynamic languages, such as JavaScript. I ended up using those dynamic features for different reasons, though.

Adding Dynamic Data to C# Objects

Applications often need dynamic data. A common case is the need for to track different data bits about a user. For instance, the application may ship with the ability to track both first and last names for customers, but the users may want to also track “comments” and “number of dependents”. With C# being an Object-Oriented language, developers would like to see those things as properties on the object. Creating the core Customer class is easy:

public class Customer
{
    public string FirstName { get; set; }
    public string LastName { get; set; }
}

Using that class would also be easy:

var customer = new Customer();
var fullName = string.Format("{0} {1}", customer.FirstName, customer.LastName);

But what to do with the “properties” that the user needs to add dynamically? The developers can’t possibly know about those properties or all the other ones the user may create in the future.

We’ll see one possible way to implement such requirement using the dynamic features of C#. We first start with a specification that tests out the behavior we’re looking for:

[Specification]
public void dynamic_properties()
{
    "Given additional dynamic data for a customer"
            .Context(() => make_data_for_a_customer());
    "When constructing a customer object"
            .Do(() => { _customer = GetCustomer(); });
    "Then the object should expose the additional data as properties"
            .Assert(properties_are_exposed);
    " And getters are available"
            .Assert(getters_are_exposed);
    " And setters are available"
            .Assert(setters_are_exposed);
}

If you’re not used to writing that style of tests, check out SubSpec.

Next, the methods that prepare the context, actions, and assertions of our specification:

void make_data_for_a_customer()
{
    _additionalData.Clear();
    _additionalData.Add("Comments", "Some comments..."); 
    _additionalData.Add("NumberOfDependents", 3);
}

Customer GetCustomer()
{
    return new Customer(_additionalData);
}

void properties_are_exposed()
{
    (_customer as DynamicEntity).RespondTo("Comments").ShouldBeTrue();
    (_customer as DynamicEntity).RespondTo("NumberOfDependents").ShouldBeTrue();
}

void getters_are_exposed()
{
    ((string) _customer.Comments).ShouldEqual("Some comments...");
    ((int) _customer.NumberOfDependents).ShouldEqual(3);
}

void setters_are_exposed()
{
    _customer.Comments = "New comments";
    _customer.NumberOfDependents = 9;

    ((string)_customer.Comments).ShouldEqual("New comments");
    ((int)_customer.NumberOfDependents).ShouldEqual(9);
}

But how can the code above (_customer.Comments) compile successfully if the Customer class doesn’t have an explicit Comments property at compile time? The code compiles because the _customer field is declared in the class as dynamic:

dynamic _customer;

In case you wonder about the _additionalData field, it’s nothing more than a dictionary:

Dictionary<string, object> _additionalData = new Dictionary<string, object>();

Having used and abused these features in order to support the applications that grow dynamically, I was quite comfortable looking at how model classes in Rails (more specifically ActiveRecord) didn’t have any properties declared, but still, they were fully populated based on the database schema when the application runs. Take this Customer class in Ruby for example:

class Customer < ActiveRecord::Base
end

Such class could be instantiated and used like so:

customer = Customer.new
customer.comments = "Some comments..."
customer.number_of_dependents = 3

Again, the class gets its properties dynamically based on the Customers table columns.

Hashes, Hashes, Everywhere…

Even though it is possible to instantiate a class and set properties on it in Ruby, much like we do in C#, most Rubyists prefer using hashes. This is what that looks like:

customer = Customer.new(:comments => "Some comments...", 
                              :number_of_dependents => 3)

Even though that might look like named parameters to a C# developer, that’s not the case. The initialize method (which gets called when new is invoked on a class) on an ActiveRecord model takes in a hash with the values we want to set on properties of that object. If we revisit our C# example, we could also have instantiated the Customer class in the following manner:

customer = new Customer(new Dictionary<string, object>
                      {
                          { "Comments", "Some comments..." }, 
                          { "NumberOfDependents", 3 }
                      });

Because I was using lots of dictionaries like that in C#, those things in Ruby didn’t feel totally weird.

What About Adding Dynamic Behavior?

So far I’ve talked about dynamic data being added to objects; but what about adding dynamic behavior? That’s implemented in C# in a similar way. Let’s take the following example: pretend that our Customer object needs to be used in such a context where it must provide a FullName property. Keep in mind that the getter of a property is nothing more than a method that returns a value, therefore, it may have specific behavior. Now let’s assume that how the FullName gets formatted is something that’ll be provided by the user through some sort of configuration, and we never know how exactly each user wants to do it.

We could test such behavior this way:

[Specification]
public void dynamic_behavior()
{
    "Given additional dynamic behavior for a customer"
            .Context(() => make_behavior_for_a_customer());
    "When constructing a customer object"
            .Do(() => _customer = GetCustomer());
    "Then the object should expose the additional behavior"
            .Assert(behaviors_are_exposed);
    "And the behavior should be accessible"
            .Assert(behaviors_are_accessible);
        }

Next, I show how the dynamic behavior is specified in this example:

void make_behavior_for_a_customer()
{
    _additionalBehavior.Clear();
    _additionalBehavior.Add("FullName", c => string.Format("{0} {1}", c.FirstName, c.LastName));
}

Our _additionalBehavior dictionary is slightly more complex than the _additionalData one; instead of taking in a string as the key, and an object as the value, we now take a string as the key, and a lambda as the value (and since a lambda is a way to implement deferred execution, it really doesn’t provide a “value”, but the “means to retrieve a value when needed” instead).

The declaration of that field is a little more complex as well:

Dictionary<string, Func<dynamic, dynamic>> _additionalBehavior =
            new Dictionary<string, Func<dynamic, dynamic>>();

Our lambda is represented by a Func delegate. In other words, a delegate that takes in one parameter of dynamic type, and it returns a value of dynamic type.

Right now you may point out the fact that the lambda I’m providing as additional behavior is still hard-coded into the application, and you’d be right; I’m doing it like this just to keep things simple. In real applications, I had those lambdas created during compile time, either creating Expression Trees during runtime, or doing code generation during runtime (but that’s outside of the scope of this post).

Here’s the last part of our tests:

void behaviors_are_exposed()
{
    (_customer as DynamicEntity).RespondTo("FullName").ShouldBeTrue();
}

void behaviors_are_accessible()
{
    ((string)_customer.FullName).ShouldEqual("Jon Jones");
}

We simply check whether our object responds to the FullName message sent to it, and whether it returns what we expect from it.

Another quick note: you may be asking yourself why I’m casting _customer.FullName to a string in order to access its value. The reason for that is because the ShouldEqual method is an extension method, and this type of method doesn’t currently work on dynamic types.

So how can we make this specification pass? Simply by going back to the DynamicEntity class, adding a field to store the dictionary with the behaviors, and then invoking it from the TryGetMember method:

public override bool TryGetMember(GetMemberBinder binder, out object result)
{
    if (_additionalData.ContainsKey(binder.Name))
    {
        result = _additionalData[binder.Name];
        return true;
    }
    if (_additionalBehavior.ContainsKey(binder.Name))
    {
        result = _additionalBehavior[binder.Name].Invoke(this);
        return true;
    }
    return base.TryGetMember(binder, out result);
}

Since the value stored in the dictionary is a delegate, we call the Invoke method on it, passing in our dynamic object itself (this) so it’s available to the lambda, which does some work with it.

What Would That Look Like in Ruby?

There are several ways to implement dynamic behavior in Ruby, and I’ll only go over one of them, in a style that will probably seem more friendly to C# developers. I’m sure every seasoned Rubyist can come up with cleaner and better ways, but those ways would probably look strange to a C# developer.

Here’s our tests in Ruby (using the RSpec test framework):

require './customer'

describe Customer do

  describe "Dynamic Behaviors" do

    context "Given additional dynamic beheavior for a customer" do

      before(:each) do
        @additional_behavior = { :full_name => 
                 lambda { |c| "#{c.first_name} #{c.last_name}" } }
      end

      describe "When object is constructed" do

        let(:customer) { Customer.new(@additional_behavior) } 

        it "should expose behaviors" do
          customer.should respond_to(:full_name)
        end

        it "should make behaviors accessible" do
          customer.first_name = "Joe"
          customer.last_name = "Jones"
          customer.full_name.should == "Joe Jones"
        end
      end
    end

  end
end

Even if you’re not familiar with Ruby yet, you should be able to follow the code in the test below, first because I wrote it in a way to resemble the C# tests I showed before, and second because RSpec provides a very expressive way to write tests.

This is the Customer class:

require 'dynamic_entity'

class Customer < DynamicEntity
    attr_accessor :first_name, :last_name
end

In this sample I’m declaring the first_name and last_name properties so that we don’t have any dependencies on a database, ActiveRecord, etc. And last but not least, the DynamicEntity class:

class DynamicEntity

  attr_reader :additional_behavior

  def initialize(additional_behavior)
    @additional_behavior = additional_behavior
  end

  def respond_to?(message)
    self.additional_behavior.include?(message)
  end

  def method_missing(message, *args, &block)
    if self.additional_behavior.include?(message)
      self.additional_behavior[message].call(self)
    else
      super
    end
  end
end

I’ve pretty much ported the same type of implementation over from C#, by using method_missing (which is similar to C#’s TryGetMember), storing lambdas in a hash, and invoking them when appropriate. I’ll say it again: there are several other ways, each with its own advantages and disadvantages, to implement something like this in Ruby. I’m only showing you the one that should look the most familiar to you. Once you’ve played around with Ruby on Rails for some time, I totally recommend you read Metaprogramming Ruby, as it explains a lot of the “magic” you’ll find in Rails, ActiveRecord, and other gems out there.

What’s With All Those Tests?

If you’re a .NET developer who’s not used to writing tests for your code, you’re probably wondering what’s up with all those tests in this post. I made sure to include them because they also played a huge role in my Ruby learning. When I started to write C# code that made use of the dynamic features, I immediately realized I just had to write tests for all of that code, since the compiler wouldn’t be there helping me out along the way. It wasn’t a big deal since I was already writing tests for the statically-typed code anyway.

Going into Ruby, it only made sense to stick with writing tests, since everything is dynamic there. Also, being a Ruby noob, I know I’m not always writing the best code, so I write whatever I can just to make my tests pass. Most of the time, my Ruby code looks like C# code (“I’m talking Ruby with a C# accent”, or so I’d say). That’s fine, because once a month or two go by and I look back at that code, chances are I will know a better way, so I can refactor my code and have the tests to back me up in case I mess something up.

Summary

Dynamic programming is one of the things that can help a .NET developer learn “the Ruby way”. Once you see the amazing things people have created with it, go back to C# and see how some of that knowledge can be transferred. A good example of that can be seen in Rob Connery’s Massive project, where he used the C# dynamic feature to create a data access tool that borrows ideas from what he has learned in Ruby and ActiveRecord.

The full code to this post is available here.

Free book: Jump Start HTML5 Basics

Grab a free copy of one our latest ebooks! Packed with hints and tips on HTML5's most powerful new features.

  • http://bibwild.wordpress.com Jonathan Rochkind

    To add behaviors in ruby, arguably better/cleaner to actually define a Module (you can do so dynamically if you want with `Module.new do`), and add it on to an individual instance with ‘extend’ or a class with ‘include’.

    • http://www.lassala.net Claudio Lassala

      Thanks for the input, Jonathan. Like I mentioned in the post, I wanted to do it as familiar to a .NET developer as possible at this moment, without throwing in all the possible ways that Ruby allows for dynamic behavior.

  • http://bibwild.wordpress.com Jonathan Rochkind

    I hear you, but if ruby provides much NICER ways of doing it, then you’re really missing out if you just simulate .NET in ruby. So just for anyone else reading this, please don’t re-implement the ruby Module system just to make it seem like .NET, please just use the ruby language feature of Modules! Or at least consider it.