24

Is there a way to convert string representation of lambda to a lambda Func?

Func<Product, bool> func = Parse<Product, bool>("product => product.Name.Length > 0");

I tried Dynamic LINQ but it doesn't work as expected - for example it doesn't expect lambda syntax =>.

Summary of answers:

  • writing my own C# compiler - very funny
  • firing up external compiler (like csc.exe) - very slow
  • using DLINQ - as I said I don't see how it can parse lambda expressions

Why do I need this: because there's no way to pass lambdas to custom attributes like

[Secure(role => role.CanDoThis && role.AllowedCount > 5)]

So as a workaround I'd like to pass lambda as string: "role => role.CanDoThis && role.AllowedCount > 5". But seems like I'll have to use DLINQ like this: "CanDoThis && AllowedCount > 5" - since that's the syntax it understands. But my question was about true lambdas, I've already used DLINQ at the time of asking.

queen3
  • 15,333
  • 8
  • 64
  • 119
  • Why do you worry about that firing up the compiler would be slow? You can cache the resulting expression. – erikkallen Nov 10 '09 at 14:17
  • It seems that C# 5 will come with something to do exactly what you want. Take a look at a video from PDC 2008 where Anders Hejlsberg speaks about the future of C#. – Bruno Reis Nov 10 '09 at 14:18
  • I'm waiting for C# 4.0 to be released... C# 5 is way too far away ;-) I actually need this feature for lambdas in attributes. Hope 4.0 will have it (as well as generic attributes). – queen3 Nov 10 '09 at 14:32
  • There won't be generic attributes in c# 4.0. http://stackoverflow.com/questions/1211170/will-there-be-generic-attributes-in-c-4 – Arnis Lapsa Nov 10 '09 at 14:52
  • While a pity, for this case I need ability to pass lambdas/delegates to attributes, not generic attributes. The 4.0 spec in the referenced question scares me ;-) and it doesn't even contain the word "lambda"! – queen3 Nov 10 '09 at 15:23
  • see http://stackoverflow.com/questions/11004909/attribute-constructor-with-lambda/11005636#11005636 for an answer for a similar use case (though not a duplicate question) – Yaur May 30 '13 at 22:40

5 Answers5

10

They are many lambda expression parsers available. Some of them are Lambda-Parser, Sprache

Sample Code:

Example1 : string concat and number calculate:

string code = "2.ToString()+(4*2)"; // C# code Func<string> 
func = ExpressionParser.Compile<Func<string>>(code); // compile code 
string result = func(); // result = "28"
Romano Zumbé
  • 7,893
  • 4
  • 33
  • 55
Prasad Kanaparthi
  • 6,423
  • 4
  • 35
  • 62
7

You could parse the string and build up a lambda expression using the Expression class, essentially duplicating the function of the compiler.

tvanfosson
  • 524,688
  • 99
  • 697
  • 795
6

I guess you have to resort to the CSharpCodeProvider. However, dealing with all possible local variable references might not be trivial. And how would you tell the CSharpCodeProvider about the type of the lambda parameter? I would probably create a template class looking like this:

class ExpressionContainer {
    public Expression<Func<Product, bool>> TheExpression;
    public string Length;

    public ExpressionContainer() {
        TheExpression = <user expression text>;
    }
}

Then do something like this:

string source = <Code from above>;
Assembly a;
using (CSharpCodeProvider provider = new CSharpCodeProvider(...) {
    List<string> assemblies = new List<string>();
    foreach (Assembly x in AppDomain.CurrentDomain.GetAssemblies()) {
        try {
            assemblies.Add(x.Location);
        }
        catch (NotSupportedException) {
            // Dynamic assemblies will throw, and in .net 3.5 there seems to be no way of finding out whether the assembly is dynamic before trying.
        }
    }

    CompilerResults r = provider.CompileAssemblyFromSource(new CompilerParameters(assemblies.ToArray()) { GenerateExecutable = false, GenerateInMemory = true }, source);
    if (r.Errors.HasErrors)
        throw new Exception("Errors compiling expression: " + string.Join(Environment.NewLine, r.Errors.OfType<CompilerError>().Select(e => e.ErrorText).ToArray()));
    a = r.CompiledAssembly;
}
object o = a.CreateInstance("ExpressionContainer");
var result = ( Expression<Func<Product, bool>>)o.GetType().GetProperty("TheExpression").GetValue(o);

Note, however, that for long-running applications, you should create all these in-memory assemblies in a separate appdomain since they can't be freed until the appdomain they reside in is unloaded.

erikkallen
  • 33,800
  • 13
  • 85
  • 120
  • 1
    Yes and then I'll have csc.exe running and AppDomains only for couple of lambdas... This reminds me of my Turbo Pascal program that allowed user to enter expressions... and had to be deployed along with Turbo Pascal compiler ;-) – queen3 Nov 10 '09 at 14:36
  • On the other hand - there was something called 'dynamic methods'. A more lightweight way to handle situations like these. Unluckily - i've never used them. :/ – Arnis Lapsa Nov 10 '09 at 14:45
  • @queen: Does it matter? Your customers will already have csc deployed, as it is part of the framework. Also, the whole purpose of running in separate appdomains is that you can retrieve the value and then tear down the appdomain. Your customer can buy quite a few new servers to handle the computation for the money you save by not implementing a compiler yourself. – erikkallen Nov 10 '09 at 15:26
1

Responding to your more specific problem, (and you may already know this but I'll try mentioning it anyway), you could create a dictionary that maps values that can be a constant (integers or enums) to lambdas.

sealed class Product {
   public bool CanDoThis { get; set; }
   public int AllowedCount { get; set; }
}

public enum SecureFuncType {
   Type1,
   Type2,
   Type3
}

sealed class SecureAttribute : Attribute {
   [NotNull] readonly Func<Product, bool> mFunc;

   public SecureAttribute(SecureFuncType pType) {
      var secureFuncs = new Dictionary<SecureFuncType, Func<Product, bool>> {
         { SecureFuncType.Type1, role => role.CanDoThis && role.AllowedCount > 1 },
         { SecureFuncType.Type2, role => role.CanDoThis && role.AllowedCount > 2 },
         { SecureFuncType.Type3, role => role.CanDoThis && role.AllowedCount > 3 }
      };

      mFunc = secureFuncs[pType];
   }
}

[Secure(SecureFuncType.Type1)]
sealed class TestClass {
}

// etc...
Dave Cousineau
  • 12,154
  • 8
  • 64
  • 80
1

You might be able to do something with CSharpCodeProvider (wrap the expression with some more code to create a valid class and compile it into an assembly, then load the assembly).

I believe that is how LINQPad does it.

Rasmus Faber
  • 48,631
  • 24
  • 141
  • 189
  • I know how to invoke csc.exe, and have experience with doing on-the-fly compilation using CodeDom, that's too much overhead - in my experience it actually runs csc.exe (when I used it on .NET 1.1). – queen3 Nov 10 '09 at 13:56