Expression builder

Available as Nuget package

Introduction

Recently I had to create lambda functions on the fly. I could'nt use the various code providers like the CSharpCodeProvider, since there would have been too much code to write. So i started to explore the wonders of Expressions. But it's a steep hill to climb.

At the same time i were exploring fluent interfaces, then i managed to build this library with a somehow fluent interface.

Using the code

Debugging

For debugging purposes can be shown the source file that will be created by the function! Let the unit test explain this

public void CanBeCreatedAFunctionReturningVoid()
{
    const string expected =
@"public void Call(System.String first, System.String second)
{
  //No Operation;
}";
    var newExpression = Function.Create()
            .WithParameter<string>("first")
            .WithParameter<string>("second")
            .WithBody(CodeLine.Nop);

    AssertString.AreEqual(expected, newExpression.ToString());

    var lambda = newExpression.ToLambda<Action<string, string>>();
    Assert.IsNotNull(lambda);

    lambda("test", "another");
}

Creating a function

To define a new function these are the steps. We are creating a simple function, with a parameter and a return variable like

int MyFunction(int param)
{
    return param;
}
var newFunction = Function.Create()
        .WithParameter<int>("param")
        .Returns("param")

Than we can convert it to an expression and call it. The compile step will produce a method.

var newExpression = newFunction.ToExpression();
var method = newExpression.Compile();

var result = (int)method.invoke(2);
Assert.AreTrue(2,result);

Or it's possible to build a lambda expression. Than it would be possible to call it with compilation support

var lambda = newFunction.ToLambda<Func<int,int>>();

var result = lambda(2);
Assert.AreTrue(2,result);

Of course it's possible to omit parameters and return variable declarations.

In short, in order of possible usage

Function body

A body can be declared for the function. It will contain a series of CodeLine objects. CodeLine objects into the library implement the ICodeLine interface

var newFunction = Function.Create()
        .WithParameter<int>("param")
        .WithBody(
            //A list of CodeLines
        )
        .Returns("param")

In short, in order of possible usage

CodeLines

Variables

It is possible to declare new simple variables. They will be assigned automatically with a default value. Here we create a variable of type int named intVariable, with the default value of 0.

var newFunction = Function.Create()
    ...
    .WithBody(
        ...
        CodeLine.CreateVariable<int>("intVariable"),
        ...
    )
    ...

Two methods can be used

Assignment and constants

Values can be assigned to variables.

Let's start remembering that with assignments three roles are present

Inside the library the LValue and RValue are associated with two interfaces, and are produced by the "Operation" classes

For example let's assign to the variable intVariable a constant value with the AssignConst method. Then we will assign the value of intVariable to anotherVariable. Code line are not ILeftable or IRightable

var newFunction = Function.Create()
    ...
    .WithBody(
        ...
        CodeLine.CreateVariable<int>("intVariable"),
        CodeLine.CreateVariable<int>("anotherVariable"),
        CodeLine.AssignConstant("intVariable",2),
        CodeLine.Assign("anotherVariable","intVariable")
        ...
    )
    ...

The assignement can be made with several operations. The default is AssignementOperator.Assign in total they are

While the complete CodeLines are

Operation

A particular concept is the "Operation". We will add the L or R values into the description to specify where an operation can be placed. The type IOperation means that the value can be both an ILeftable or an IRightable

Just to give an example

var newExpression = Function.Create()
        .WithParameter<int>( "first")
        .WithParameter<string>( "second")
        .WithBody(
            CodeLine.Assign("second", Operation.InvokeReturn("first", "ToString")),
            CodeLine.AssignConstant("second", "another", AssignementOperator.SumAssign)
        )
        .Returns("second")
        .ToExpression();
Assert.IsNotNull(newExpression);


var extMethodExpr = newExpression.Compile();
var result = extMethodExpr.DynamicInvoke(22, "b");
Assert.AreEqual("22another",result);
Lambda operations

Lambda function can be invoked too

Just a sample:

            
var newExpression = Function.Create()
    .WithParameter<string>("first")
    .WithParameter<string>("second")
    .WithBody(
            CodeLine.CreateVariable<string>( "result"),
            CodeLine.Assign("result",
                Operation.Func<object, string, string>(
                    //The lambda expression
                    (a, b) =>
                    {
                        return string.Format("{0}-{1}",a,b);
                    },
                Operation.Variable("first"),
                Operation.Variable("second"))
            )
    )

Conditions

Condition are particular operation with return type of bool.

The single compare conditionst has the following operators

If and While

If operator includes various kind of items.

var newExpression = Function.Create()
        .WithParameter<int>("first")
        .WithBody(
                CodeLine.CreateIf(Condition.CompareConst("first", 3))
                .Then(CodeLine.AssignConstant("first", 2))
                .Else(CodeLine.AssignConstant("first", 1))
        )

The while will be simpler, and kinda obvious...

Utility classes

ReflectionUtil

A small utility class with utility methods through reflection

ExpressionUtil

A small utility class with utility methods through expressions

Some example will clarify :)

    public class FirstLevel
    {
        public SecondLevel First { get; set; }
        public int Other { get; set; }
    }

    public class SecondLevel
    {
        public string Second { get; set; }
    }
    
    var type = ExpressionUtil.GetPropertyInfos<FirstLevel>((a) => a.First.Second).ToArray();
    Assert.AreEqual(2, type.Length);
    Assert.AreEqual(typeof(string), type[1].DataType);
    Assert.AreEqual(typeof(SecondLevel), type[0].DataType);
    Assert.AreEqual("Second", type[1].Name);
    Assert.AreEqual("First", type[0].Name);

Class Wrapper

It is possible to create a wrapper for a class. This can be useful. Instead of getting mad (and slow) with reflection, a cache can be built on startup containing all the ClassWrapperDescriptors Then to apply the various method it would be as easy as creating an instance of a new ClassWrapper passing the instance of the object to wrap to the ClassWrapperDescriptor.

Note that to invoke the methods the parameters must respect the order of the called method. Note even that if the lasts parameters are not passed, they would be considered optional and if the method allow this behaviour, it will assign to them a default value.


public class SampleClass
{
    public string StringProperty { get; set; }

    public void VoidMethod()
    {
        
    }
}

public void Main()
{
    //Create an instance
    var newObject = new SampleClass();
    TestMethod(newObject)
}

public void TestMethod(object instance)
{
    //Create the wrapper descriptor for the given type
    var classWrapperDescriptor = new ClassWrapperDescriptor(instance.GetType());
    //Load the data into the descriptor
    classWrapperDescriptor.Load();
    
    //Create a wrapper
    var classWrapper = classWrapperDescriptor.CreateWrapper(instance);
    
    //Set the propery StringProperty
    classWrapper.Set("StringProperty","test");
    
    //Retrieve the value of StringPropery
    var result = classWrapper.Get<string>("StringProperty");
    
    //And so on with the methods
}

Inisders view

More to come shortly! As soon as i have the time to write how everything works here!

Licensing

Copyright (C) 2013-2014 Kendar.org

Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

Download

See Nuget.org for the latest changes. Or the repository on Github.


Last modified on: May 02, 2015