I would like to create a function that converts a "C# function call" to a string.
For example:
In my C# Project there is a public function, that can be called like this:
myTestClass.myTestFunction();
It's a function without arguments and returnvalue.
Now I would like to implement another function that accepts a C#
"Expression" as argument and converts the "Expression" to a string:
Expression<Func<???>> expr = () => myTestClass.myTestFunction();
string myString="";
myString=convertExpressionToString(expr);
myString should contain now "myTestClass.myTestFunction();"
It is important that the complete function call including the class
name is inside the string.
Any ideas how to solve this?
For this case you can simply write
private static string ConvertExpressionToString(LambdaExpression expr)
{
var methodCallExpr = expr.Body as MethodCallExpression;
if (methodCallExpr != null ) {
return methodCallExpr.Method.DeclaringType.Name + "." +
methodCallExpr.Method.Name + "();";
}
}
The general case is more complicated; however, this gives you an idea of where to start.
A more elaborate version prints parameters passed as constant expressions:
private static string ConvertExpressionToString(LambdaExpression expr)
{
var sb = new StringBuilder();
var methodCallExpr = expr.Body as MethodCallExpression;
sb.Append(methodCallExpr.Method.DeclaringType.Name)
.Append(".")
.Append(methodCallExpr.Method.Name)
.Append("(");
var arguments = methodCallExpr.Arguments;
for (int i = 0; i < arguments.Count; i++) {
if (i > 0) {
sb.Append(", ");
}
var constExpr = arguments[i] as ConstantExpression;
if (constExpr == null) {
sb.Append("<expr>");
} else {
sb.Append(constExpr.ToString());
}
}
sb.Append(");");
return sb.ToString();
}
It can cope with this expression:
Expression<Action> expr = () => myTestClass.myTestFunction(5, "hello");
However, since the parameters can be any valid expressions, it quickly becomes complicated to include other cases. And then there are out and ref parameters and optional parameters.
Just call ToString on the method's body.
string myString = expr.Body.ToString();
what you're trying to get is an action
Expression<Action> expr = () => myTestClass.myTestFunction();
MethodCallExpression mbr = (MethodCallExpression)expr.Body;
String methodName = mbr.Method.Name;
Assert.AreEqual(methodName, "myTestFunction");
MemberExpression me = (MemberExpression) mbr.Object;
String memberName = me.Member.Name;
Assert.AreEqual(methodName, "myTestClass");
String finalName = string.Format("{0}.{1}()", memberName, methodName);
Assert.AreEqual("myTestClass.myTestFunction()", finalName);
because you're accessing myTestClass via a closure you need to realize that you're accessing it via a Member Expression
so the body is a Method call. we get the method name from that, then we get the expression representing the object that we're calling the method on, we cast that to a member expression because that's what it is in this case and we get the name from the member on that member expression.
Related
I have simple lambda as a string:
var str = "f => f.Substring(0, f.IndexOf(' '))";
which eventualy takes first word from the passed string.
What is the easiest way to compile/convert this peace of string into Func and make the following code to work.
Func<string, string> func = ...
var firstWord = func("Hello World");
Will Expression help me? Will appreciate working sample,
Thanks
Problem is that there is no strong typing support here, so you can't even utilize expression trees. However, DynamicLinq already does this, so you can easily get this working with a few modifications. Just create a few static methods of your own in the DynamicExpression class to add this functionality:
public static Expression<Func<T, S>> ParseLambda<T, S>(string expression)
{
string paramString = expression.Substring(0, expression.IndexOf("=>")).Trim();
string lambdaString = expression.Substring(expression.IndexOf("=>") + 2).Trim();
ParameterExpression param = Expression.Parameter(typeof(T), paramString);
return (Expression<Func<T,S>>)ParseLambda(new[] { param }, typeof(S), lambdaString, null);
}
public static LambdaExpression ParseLambda(string expression, Type returnType, params Type[] paramTypes)
{
string paramString = expression.Substring(0, expression.IndexOf("=>")).Trim("() ".ToCharArray());
string lambdaString = expression.Substring(expression.IndexOf("=>") + 2).Trim();
var paramList = paramString.Split(',');
if (paramList.Length != paramTypes.Length)
throw new ArgumentException("Specified number of lambda parameters do not match the number of parameter types!", "expression");
List<ParameterExpression> parameters = new List<ParameterExpression>();
for (int i = 0; i < paramList.Length; i++)
parameters.Add(Expression.Parameter(paramTypes[i], paramList[i].Trim()));
return ParseLambda(parameters.ToArray(), returnType, lambdaString, null);
}
Usage for both:
Func<string, string> func = DynamicExpression.ParseLambda<string, string>("f => f.Substring(0, f.IndexOf(\" \"))").Compile();
Func<string, int, string> otherFunc = ((Expression<Func<string, int, string>>)DynamicExpression.ParseLambda("(str, ind) => (ind * 100).ToString() + str")).Compile();
Edit: This has not been thoroughly tested, so you might want to make sure with some unit tests that these produce the correct results. I'm not doing anything overly complicated, just explicitly declaring the parameters to use in the public static LambdaExpression ParseLambda(ParameterExpression[] parameters, Type resultType, string expression, params object[] values) method.
Also fixed bug pointed out by #Mauro Destro by trimming the parameter name from paramList when creating the ParameterExpression.
You can use Roslyn scripting to do this. For example:
var str = "f => f.Substring(0, f.IndexOf(' '))";
var func = await CSharpScript.EvaluateAsync<Func<string, string>>(str);
var firstWord = func("Hello World");
This requires the Microsoft.CodeAnalysis.CSharp.Scripting package.
You will need to examine the string and construct an expression from the provided string. There is no easy or built in method to obtain a method from a string like this.
Good luck.
You might be interested in CodeDom technology. link
There is no ready parser there, though.
how can I get a body from function
Func<bool> methodCall = () => output.SendToFile();
if (methodCall())
Console.WriteLine("Success!");
I need to get this output.SendToFile() as a string
Another example:
string log = "";
public void Foo<T>(Func<T> func)
{
try
{
var t = func();
}
catch (Exception)
{
//here I need to add the body of the lambda
// log += func.body;
}
}
public void Test()
{
var a = 5;
var b = 6;
Foo(() => a > b);
}
Edit:
For more information on this topic see: Expression Trees
You can't. A Func<T> is nothing you can easily analyze. If you want to analyze a lambda, you need to create a Expression<Func<bool>> and analyze it.
Getting the body of an expression is simple:
Expression<Func<bool>> methodCall = () => output.SendToFile();
var body = methodCall.Body;
body would be a MethodCallExpression you could further analyze or just output via ToString. Using ToString won't result exactly in what you would like to have, but it contains that information, too.
For example, executing ToString() on body in LINQPad results in something like this:
value(UserQuery+<>c__DisplayClass0).output.SendToFile()
As you can see, "output.SendToFile()" is there.
To actually execute the code defined by an expression, you first need to compile it:
var func = methodCall.Compile();
func();
Which can be shortened to this:
methodCall.Compile()(); // looks strange but is valid.
I have built my own SQL Query builder that breaks apart an Expression, however, I'm having an issue trying to get the value of string defined in the same function as the lambda expression.
Here is what I am trying to do in console app:
private static void MyBuilderTest()
{
var sqlBuilder = new SqlBuilder();
// Doesn't work -- NEED GUIDANCE HERE
var testValue = "Test"; // Defined in the same function as the lambda below
sqlBuilder.Select<FooObject>(o => o.FooValue == testValue);
// Works
var someObject = new SomeObject { SomeValue = "classTest };
sqlBuilder.Select<FooObject>(o => o.FooValue == someObject.SomeValue);
}
In my builder it subclasses from ExpressionVisitor, and I override the VisitMember. I found that a string defined in at the base Console level will come back as:
Node.Expression.NodeType == ExpressionType.Constant
The Node.Expression passes back properties of:
CanReduce = false
DebugView = ".Constant<ConsoleApplication1.Program+<>c__DisplayClass1>(ConsoleApplication1.Program+<>c__DisplayClass1)"
NodeType = Constant
Type = System.Type {System.RunetimeType}
Value = {ConsoleApplication1.Program}
The Node.Expression.Value contains:
testValue = "Test" (Type: string)
How do I get this value? I've tried several things, like:
var memberType = node.Expression.Type.DeclaringType;
This passes back a ConsoleApplication1.Program type.
However, when I do:
memberType.GetProperty("testValue"); // Declaring Type from Expression
It passes back null.
The above methods work fine if I place the lambda "strings" in a class, but doesn't work if they string is defined in the console function.
Can anyone tell me how to get the string value if it's defined at the function level of the lambda?
EDITED: Added VisitMember
protected override Expression VisitMember(MemberExpression node)
{
if (node.NodeType == ExpressionType.Constant)
{
// Node.Expression is a ConstantExpression type.
// node.Expression contains properties above
// And Has Value of: {ConsoleApplication1.Program}
// Expanding Value in Watch window shows: testValue = "Test"
// How do I get this value, if the ConsoleApplication1.Program type doesn't
// even know about it? Looks like maybe a dynamic property?
}
}
EDITED
Added code to the console app example to show what works and what doesn't.
The lambda in your example has "closed over" the testValue variable, meaning the compiler has captured it as a field of the same name in an automatically generated class called ConsoleApplication1.Program+<>c__DisplayClass1>. You can use normal reflection to get the current value of that field by casting the right hand-side of the binary expression into a MemberExpression.
var testValue = "hello";
var expr = (Expression<Func<string, bool>>) (x => x == testValue);
var rhs = (MemberExpression) ((BinaryExpression) expr.Body).Right;
var obj = ((ConstantExpression) rhs.Expression).Value;
var field = (FieldInfo) rhs.Member;
var value = field.GetValue(obj);
Debug.Assert(Equals(value, "hello"));
testValue = "changed";
value = field.GetValue(obj);
Debug.Assert(Equals(value, "changed"));
Alternatively you can change your variable into a constant.
const string testValue = "hello";
var expr = (Expression<Func<string, bool>>) (x => x == testValue);
var value = ((ConstantExpression) ((BinaryExpression) expr.Body).Right).Value;
Debug.Assert(Equals(value, "hello"));
Instead of doing this by yourself, have a look at PartialEvaluator from Matt Warren. It replaces all references to constants with the constants themselves.
I'm using a combination of reflection and expression trees, and want to pass back certain property accessors from a class to a calling method. My current code has a method traversing the class and returning a list of MemberExpressions. The caller then iterates over the member expressions and creates lambdas, which should then be called with an instance of the inspected class to return the value of the property.
Here is a sample of what it would look like without the method calls (Runnable in LINQPad):
void Main()
{
var t = new Test { Prop = "Test" };
var property = t.GetType().GetProperty("Prop");
var baseType = Expression.Parameter(typeof(Test), "baseType");
var memberAccess = Expression.MakeMemberAccess(baseType, property);
var lambda = Expression.Lambda<Func<Test, string>>(memberAccess, Expression.Parameter(typeof(Test), "baseType"));
var func = lambda.Compile();
var result = func(t);
result.Dump();
}
class Test {
public string Prop { get; set; }
}
This does not work, throwing this exception:
InvalidOperationException: variable 'baseType' of type 'UserQuery+Test' referenced from scope '', but it is not defined
However, if I change the creation of the lambda to this:
var lambda = Expression.Lambda<Func<Test, string>>(memberAccess, baseType);
That is, replace the Expression.Parameter with the variable used earlier, then it works. This is not (easily) possible in the scenario where I want to use it, since I would have to return the original parameter along with the list (I could return a tuple, of course, but I would prefer not to, if it is not necessary).
Why does it work like this? Inspecting the DebugView of the lambda, they are exactly the same no matter what approach is used:
.Lambda #Lambda1<System.Func`2[UserQuery+Test,System.String]>(UserQuery+Test $baseType)
{
$baseType.S
}
Yes, you need to refer ParameterExpression, used earlier. This won't compile too:
private String Foo(Test myParam)
{
return myAnotherParam.MyProperty;
}
With instatiating new ParameterExpression in lambda, you're doing the same thing (but note, when making lambda, you're doing it in reversed order - first, you're constructing a method body, then - a method declaration):
// return myAnotherParam.MyProperty;
var baseType = Expression.Parameter(typeof(Test), "baseType");
var memberAccess = Expression.MakeMemberAccess(baseType, property);
// private String Foo(MyClass myParam)
var lambda = Expression.Lambda<Func<Test, string>>(memberAccess, Expression.Parameter(typeof(Test), "baseType"));
I have simple lambda as a string:
var str = "f => f.Substring(0, f.IndexOf(' '))";
which eventualy takes first word from the passed string.
What is the easiest way to compile/convert this peace of string into Func and make the following code to work.
Func<string, string> func = ...
var firstWord = func("Hello World");
Will Expression help me? Will appreciate working sample,
Thanks
Problem is that there is no strong typing support here, so you can't even utilize expression trees. However, DynamicLinq already does this, so you can easily get this working with a few modifications. Just create a few static methods of your own in the DynamicExpression class to add this functionality:
public static Expression<Func<T, S>> ParseLambda<T, S>(string expression)
{
string paramString = expression.Substring(0, expression.IndexOf("=>")).Trim();
string lambdaString = expression.Substring(expression.IndexOf("=>") + 2).Trim();
ParameterExpression param = Expression.Parameter(typeof(T), paramString);
return (Expression<Func<T,S>>)ParseLambda(new[] { param }, typeof(S), lambdaString, null);
}
public static LambdaExpression ParseLambda(string expression, Type returnType, params Type[] paramTypes)
{
string paramString = expression.Substring(0, expression.IndexOf("=>")).Trim("() ".ToCharArray());
string lambdaString = expression.Substring(expression.IndexOf("=>") + 2).Trim();
var paramList = paramString.Split(',');
if (paramList.Length != paramTypes.Length)
throw new ArgumentException("Specified number of lambda parameters do not match the number of parameter types!", "expression");
List<ParameterExpression> parameters = new List<ParameterExpression>();
for (int i = 0; i < paramList.Length; i++)
parameters.Add(Expression.Parameter(paramTypes[i], paramList[i].Trim()));
return ParseLambda(parameters.ToArray(), returnType, lambdaString, null);
}
Usage for both:
Func<string, string> func = DynamicExpression.ParseLambda<string, string>("f => f.Substring(0, f.IndexOf(\" \"))").Compile();
Func<string, int, string> otherFunc = ((Expression<Func<string, int, string>>)DynamicExpression.ParseLambda("(str, ind) => (ind * 100).ToString() + str")).Compile();
Edit: This has not been thoroughly tested, so you might want to make sure with some unit tests that these produce the correct results. I'm not doing anything overly complicated, just explicitly declaring the parameters to use in the public static LambdaExpression ParseLambda(ParameterExpression[] parameters, Type resultType, string expression, params object[] values) method.
Also fixed bug pointed out by #Mauro Destro by trimming the parameter name from paramList when creating the ParameterExpression.
You can use Roslyn scripting to do this. For example:
var str = "f => f.Substring(0, f.IndexOf(' '))";
var func = await CSharpScript.EvaluateAsync<Func<string, string>>(str);
var firstWord = func("Hello World");
This requires the Microsoft.CodeAnalysis.CSharp.Scripting package.
You will need to examine the string and construct an expression from the provided string. There is no easy or built in method to obtain a method from a string like this.
Good luck.
You might be interested in CodeDom technology. link
There is no ready parser there, though.