Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Method to create and store method chain at runtime

The problem I have is that I need to do about 40+ conversions to convert loosely typed info into strongly typed info stored in db, xml file, etc.

I'm plan to tag each type with a tuple i.e. a transformational form like this:

host.name.string:host.dotquad.string

which will offer a conversion from the input to an output form. For example, the name stored in the host field of type string, the input is converted into a dotquad notation of type string and stored back into host field. More complex conversions may need several steps, with each step being accomplished by a method call, hence method chaining.

Examining further the example above, the tuple 'host.name.string' with the field host of name www.domain.com. A DNS lookup is done to covert domain name to IP address. Another method is applied to change the type returned by the DNS lookup into the internal type of dotquad of type string. For this transformation, there is 4 seperate methods called to convert from one tuple into another. Some other conversions may require more steps.

Ideally I would like an small example of how method chains are constructed at runtime. Development time method chaining is relatively trivial, but would require pages and pages of code to cover all possibilites, with 40+ conversions.

One way I thought of doing is, is parsing the tuples at startup, and writing the chains out to an assembly, compiling it, then using reflection to load/access. Its would be really ugly and negate the performance increases i'm hoping to gain.

I'm using Mono, so no C# 4.0

Any help would be appreciated. Bob.

like image 1000
scope_creep Avatar asked Jul 20 '10 00:07

scope_creep


1 Answers

Here is a quick and dirty solution using LINQ Expressions. You have indicated that you want C# 2.0, this is 3.5, but it does run on Mono 2.6. The method chaining is a bit hacky as i didn't exactly know how your version works, so you might need to tweak the expression code to suit.

The real magic really happens in the Chainer class, which takes a collection of strings, which represent the MethodChain subclass. Take a collection like this:

{
"string",
"string",
"int"
}

This will generate a chain like this:

new StringChain(new StringChain(new IntChain()));

Chainer.CreateChain will return a lambda that calls MethodChain.Execute(). Because Chainer.CreateChain uses a bit of reflection, it's slow, but it only needs to run once for each expression chain. The execution of the lambda is nearly as fast as calling actual code.

Hope you can fit this into your architecture.

public abstract class MethodChain {
private MethodChain[] m_methods;
    private object m_Result;


    public MethodChain(params MethodChain[] methods) {
        m_methods = methods;
    }

    public MethodChain Execute(object expression) {

        if(m_methods != null) {
            foreach(var method in m_methods) {
                expression = method.Execute(expression).GetResult<object>();
            }
        }

        m_Result = ExecuteInternal(expression);
        return this;
    }

    protected abstract object ExecuteInternal(object expression);

    public T GetResult<T>() {
        return (T)m_Result;
    }
}

public class IntChain : MethodChain {

    public IntChain(params MethodChain[] methods)
        : base(methods) {

    }

    protected override object ExecuteInternal(object expression) {
        return int.Parse(expression as string);
    }
}

public class StringChain : MethodChain {

    public StringChain(params MethodChain[] methods):base(methods) {

    }

    protected override object ExecuteInternal(object expression) {
        return (expression as string).Trim();
    }
}


public class Chainer {

    /// <summary>
    /// methods are executed from back to front, so methods[1] will call method[0].Execute before executing itself
    /// </summary>
    /// <param name="methods"></param>
    /// <returns></returns>
    public Func<object, MethodChain> CreateChain(IEnumerable<string> methods) {

        Expression expr = null;
        foreach(var methodName in methods.Reverse()) {

            ConstructorInfo cInfo= null;
            switch(methodName.ToLower()) {
                case "string":
                    cInfo = typeof(StringChain).GetConstructor(new []{typeof(MethodChain[])});
                    break;
                case "int":
                    cInfo = typeof(IntChain).GetConstructor(new[] { typeof(MethodChain[]) });
                    break;
            }
            if(cInfo == null)
                continue;

            if(expr != null)
                expr = Expression.New(cInfo, Expression.NewArrayInit( typeof(MethodChain), Expression.Convert(expr, typeof(MethodChain))));
            else
                expr = Expression.New(cInfo, Expression.Constant(null, typeof(MethodChain[])));
        }

        var objParam = Expression.Parameter(typeof(object));
        var methodExpr = Expression.Call(expr, typeof(MethodChain).GetMethod("Execute"), objParam);
        Func<object, MethodChain> lambda = Expression.Lambda<Func<object, MethodChain>>(methodExpr, objParam).Compile();

        return lambda;
    }
    [TestMethod]
    public void ExprTest() {
        Chainer chainer = new Chainer();
        var lambda = chainer.CreateChain(new[] { "int", "string" });
        var result = lambda(" 34 ").GetResult<int>();
        Assert.AreEqual(34, result);
    }
}
like image 135
Igor Zevaka Avatar answered Sep 19 '22 17:09

Igor Zevaka