Export (0) Print
Expand All

Dynamic Method Bags

By Bill Wagner

The new dynamic features in C# 4.0 are usually discussed as an interop feature. You can write code using dynamic objects to call libraries defined in IronPython, or IronRuby, or COM objects. Just because a feature was defined for one use doesn’t mean that’s all it’s good for. I work with dynamic typing to explore new ways to solve problems without ever leaving C#. In this article, I’ll show you a bit of C# trickery that combines generics, expression trees, and dynamic to create a method bag. A method bag is a type that lets developers add new methods at runtime, and then call those methods through dynamic dispatch in C#.

The end goal of this exercise is to write code like this:

// Create an object that contains some new methods:
var newType = new MethodBag();
newType.SetMethod("Write", () => Console.WriteLine("Hello World"));
newType.SetMethod("Display", (string parm) => Console.WriteLine(parm));
newType.SetMethod("IsValid", () => true);
newType.SetMethod("Square", (int num) => num * num);
newType.SetMethod("Sequence", () => from n in Enumerable.Range(1, 100)
                               where n % 5 == 2
                               select n * n);

Once you’ve created an object and created methods on that object, you can call those just as though they’d been defined at compile time:

// start acting dynamic:
dynamic dispatcher = newType;
dispatcher.Write();
var result = dispatcher.IsValid();
Console.WriteLine(result);
dispatcher.Display("This is a message");
var result2 = dispatcher.Square(5);
Console.WriteLine(result2);
var sequence = dispatcher.Sequence();
foreach (var num in sequence)
    Console.WriteLine(num);

There are several pieces to this puzzle. Let’s go into the pieces one at a time. Each of the techniques is something you’ve seen before: dynamic dispatch, generics, and expression trees. Putting them together makes it possible to add named methods to a type and runtime and then use those methods whenever a dynamic type is expected.

The first task is to understand how dynamic dispatch occurs in C#. I defined this functionality in a class called MethodBag. The MethodBag class is derived from System.Dynamic.DynamicObject, in order to simplify the implementation of the dynamic behavior. You need to implement the interface System.Dynamic.IDynamicMetaObjectProvider in order to create a C# type that contains dynamic behavior. That’s a simple interface definition; you need to create one method that returns a DynamicMetaObject. Implementing a DynamicMetaObject, however, is quite a bit of work. Whenever you can, you should leverage the work done in the System.Dynamic.DynamicObject class by deriving your class from it. DynamicObject implements IDynamicMetaObjectProvider for you. The DynamicMetaObject created by the DynamicObject uses a number of virtual methods declared in DynamicObject to provide the dynamic behavior. Overriding any or all of those methods creates the dynamic behavior your type needs. You can see one example of this in Ander’s talk from the 2008 PDC (http://channel9.msdn.com/pdc2008/TL16/) and on Phil Haack’s blog (http://haacked.com/archive/2009/08/26/method-missing-csharp-4.aspx). In both cases, they have overridden TrySetMember and TryGetMember to create property bag in C#. I needed to override TryInvokeMember in order to implement the MethodBag and provide dynamic dispatch to methods at runtime. Here’s part of the code. I’ll explain the MethodDescription type later in this article. For now, it’s enough to understand that MethodDescription contains information about the code that creates a given method.

using global::System;
using global::System.Collections.Generic;
using global::System.Dynamic;
using global::System.Linq.Expressions;
public class MethodBag : DynamicObject
{
    public override bool TryInvokeMember(InvokeMemberBinder binder,
        object[] args, out object result)
    {
        result = null;
        if (!methods.ContainsKey(binder.Name))
            return false;
        // Described later
        MethodDescription method = methods[binder.Name];
        if (method.NumberOfParameters != args.Length)
            return false;
        result = method.Invoke(args);
        return true;
    }
}

TryInvokeMember returns a Boolean flag that indicates whether or not this method did actually perform the dynamic method invocation. If it can’t perform whatever work is needed, it should return false. In this class, that’s what happens if the caller doesn’t define a method, or if the method has the wrong number of parameters.  Once those checks pass, I’ll use a helper class (the MethodDescription class) to invoke that code a caller defined for the named method.

You are handling dynamic invocation, so you need to validate everything yourself. TryInvokeMember passes the parameters for the method in the args array (of type System.Object). If the method has a return type other than void, that returned object gets stored in the result object.  

Validating parameters is much easier if you create a strongly typed API for client code to define member methods. That way, inside the MethodBag, you can determine the number and types of parameters. The number of combinations for parameter types is incredibly large: as you add methods you add more method parameters. Creating generic methods means that you can avoid making specific versions for each or all of those combinations.

You’ll create overloads of SetMethod() for each number of parameters you want to support, including variations for Action and Func with each number of parameters. Inside each of those methods, you’ll store some of the information about the about the expression. Later, you’ll use that information to more easily invoke that expression.

The implementation is in the MethodDescription class and a few derived classes. MethodDescription defines the functionality you’ll need for any method added to the method bag. You’ll need to access the number of parameters. You’ll need to get and set the embedded Expression that defines the behavior for the dynamic method. Finally, you’ll need a way to execute the method at runtime. Here’s the definition of the MethodDescription class:

private abstract class MethodDescription
{
    internal abstract int NumberOfParameters
    {
        get;
    }
    internal Expression target
    {
        get;
        set;
    }
    internal abstract object Invoke(object[] parms);
}

You create derived classes for methods with different numbers of parameters, and for methods that do or do not return a value. Inside each of those derived classes, you’ll create a specific version of Invoke() that passes the correct number of parameters to a delegate created from the original expression. Inside each override of Invoke(), you’ll make use of C#’s dynamic support to do all the type coercing. After all, you’re implementing a dynamic object. Whether you check the types of all the parameters, or you let the C# runtime dynamic binder do it, you’ll get the same result.

Let’s walk through the methods that implement dynamic dispatch for a lambda that represents a System.Action method. You’ll set a method like this:

newType.SetMethod("Write", () => Console.WriteLine("Hello World"));

Anytime someone calls ‘dynamicThing.Write()’, you want to print “Hello World” to the console. The lambda expression is passed to SetMethod() as an expression. Inside SetMethod, it creates a new ActionDescription object which stores the expression:

public void SetMethod(string name, Expression<Action> lambda)
{
    var desc = new ActionDescription { target = lambda };
    methods.Add(name, desc);
}

Finally, Invoke() must compile and invoke that target.

internal override object Invoke(object[] parms)
{
    var target2 = target as Expression<Action>;
    target2.Compile().Invoke();
    return null;
}

Invoke() casts the target expression as an Expression<Action>. Then, it compiles and executes the resulting action. Because a System.Action has a void return type, this version of Invoke() sets the return value to null.

I said earlier that Invoke() will leverage the dynamic subsystem to manage some of the complexity involved in invoking these expressions. That starts to happen as soon as you support any expressions that take parameters or return values. The version of Invoke() that corresponds to an Action<T> looks like this:

internal override object Invoke(object[] parms)
{
    dynamic target2 = target;
    target2.Compile().Invoke(parms[0]);
    return null;
}

Invoke() now makes use of the dynamic subsystem. First, it creates a local variable that is statically typed to be dynamic. That enables me to compile and execute the resulting delegate without needing to create a new overload for every possible type that could be used for T in Action<T>. You can’t create a generic version of Invoke(). You’d need to create overloads for every number of parameters you’d want to support. Furthermore, you’d need determine which of the generic methods to call from TryInvokeMember() based on the number of parameters in the method call. It would end up being much more code in order to get just single parameter methods. Leveraging dynamic support easily extends to any number of parameters, and processing the return values.

Download the source code from the LINQ farm.

Download the source code directly.

The MethodBag does not support many of the features of a hand written class. I only coded the versions that take one and two parameters to save space. That should be enough to understand the technique and extend it to higher numbers of parameters.

There are other limitations that are too much work to overcome. If you run into these limitations, consider it a strong indication that you should create a hand coded type to implement the behavior you need.  Most obviously, I have not implemented state in the method bag. It’s actually harder than you might think to implement state by combining a MethodBag with the PropertyBag implemented in the references I cited earlier. That’s because expression trees cannot contain objects that are statically typed as dynamic. There simply isn’t a good syntax to create methods that rely on internal state on the same dynamic object.

I did not implement overloads, methods with the same name that contain different parameter types. It would not be that hard to extend the MethodBag I’ve shown with internal storage that could store method names with different numbers of parameters in different internal collections. That would enable some overloading features. Creating overloads that contain the same number of parameters (but different types) would be quite a bit more difficult. At that point, you would need much more internal storage mechanisms. If you really need multiple overloads, you must view that as a strong indication that you should create your own class.

You can only add public methods. You can’t define methods with any other access modifiers. Nor can you define methods that are virtual or abstract. That’s really not that big of a limitation, because you can’t derive from a specific shape of MethodBag. Also, you can’t specify that a MethodBag implements an interface.

You’ll also notice that I didn’t implement any type validation on the parameters to any of the methods. That’s because it really won’t make any difference to users. Either way, calling a dynamic method with the wrong parameter types will throw an exception. Whether I add code to check each parameter type, or if I simply let the C# runtime binder do the same checks.

Even with these limitations, using dynamic to create methods at runtime does have its place. You can create an object, add methods, and use it anywhere you can use any dynamic object. You could create MethodBags that will run queries for dynamic websites. A MethodBag could create the methods needed based on a users’ preference and dynamically generate the data. You could create a MethodBag to pass to libraries created in dynamic languages. You could create a MethodBag that implements the APIs needed by that dynamic library. It’s not nearly as full featured as a class, but it is a way to pass several method definitions to other APIs.

Show:
© 2014 Microsoft