Addendum to Dynamic Method Bags

Bill Wagner has an interesting article on MSDN about implementing Dynamic Objects in C# 4: http://msdn.microsoft.com/en-us/library/ee658247.aspx.  He leaves it up to the reader to figure out the right way to implement the class he describes and provides suggestions.  This is a great way to structure a propaedeutic of this sort, as it affords the clever reader an opportunity to dig into the material on her own.

At the same time, it is often helpful to have a key against which to compare one’s own work.  To that end, the full code required to run the sample application described in the article follows.

Here is the code that serves as our test.  Bill Wagner describes an object that inherits from DynamicObject that will run the following code:

    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);
    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);

    Console.ReadLine();

 

Here is an implementation of MethodBag that will fulfill the expectations established above:

internal class MethodBag: DynamicObject
{
    private Dictionary<string, MethodDescription> methods = 
        new Dictionary<string, MethodDescription>();

    #region Method Descriptions

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

    private class ActionDescription: MethodDescription
    {
        internal override int NumberOfParameters
        {
            get { return 0; }
        }
        internal override object Invoke(object[] parms)
        {
            var target2 = target as Expression<Action>;
            target2.Compile().Invoke();
            return null;
        }
    }

    private class ActionDescription<T> : MethodDescription
    {
        internal override int NumberOfParameters
        {
            get { return 1; }
        }
        internal override object Invoke(object[] parms)
        {
            dynamic target2 = target;
            target2.Compile().Invoke(parms[0]);
            return null;
        }
    }

    private class FuncDescription<T> : MethodDescription
    {
        internal override int NumberOfParameters
        {
            get { return 0; }
        }
        internal override object Invoke(object[] parms)
        {
            dynamic target2 = target;
            return target2.Compile().Invoke();
        }
    }

    private class FuncDescription<T,S> : MethodDescription
    {
        internal override int NumberOfParameters
        {
            get { return 1; }
        }
        internal override object Invoke(object[] parms)
        {
            dynamic target2 = target;
            return target2.Compile().Invoke(parms[0]);

        }
    }

    #endregion

    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;

    }

    #region Set Methods

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

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

    public void SetMethod<T>(string name, Expression<Func<T>> lambda)
    {
        var desc = new FuncDescription<T> { target = lambda };
        methods.Add(name, desc);
    }
    
    public void SetMethod<T,S>(string name, Expression<Func<T,S>> lambda)
    {
        var desc = new FuncDescription<T,S> { target = lambda };
        methods.Add(name, desc);
    }

    #endregion
}

 

Your output should look like this:

Hello World

True

This is a message

25

4

49

144

289

484

729

1024

1369

1764

2209

2704

3249

3844

4489

5184

5929

6724

7569

8464

9409