Available as Nuget package
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.
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"); }
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
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
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
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
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 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")) ) )
Condition are particular operation with return type of bool.
The single compare conditionst has the following operators
ReferenceEqual
Comparaison operators
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...
A small utility class with utility methods through reflection
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);
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 }
More to come shortly! As soon as i have the time to write how everything works here!
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.
See Nuget.org for the latest changes. Or the repository on Github.