C# Eval Expression How to use the Compile Method?

The Compile method allows you to compile any C# code or expression at runtime and return a delegate.

The Compile method offers the best performance when you need to execute the same expression multiple times but with different parameters:

var fizzBuzzCompiled = Eval.Compile<Func<int, string>>(@"
if (i % 15 == 0)
	return $'{i} => FizzBuzz';
else if (i % 3 == 0)
	return $'{i} => Fizz';
else if (i % 5 == 0)
	return $'{i} => Buzz';
else
	return $'{i} => {i}';
", "i");
		
var answers = new List<string>();

for(int i = 0; i <= 100; i++)
{
	answers.Add(fizzBuzzCompiled(i));
}

Console.WriteLine(string.Join(Environment.NewLine, answers));

Try it online

The Compile method is slightly harder at first to understand than the Execute method. However, the Compile method offers the best performance as you directly use the delegate (compiled method).

C# Compile Method

You can call the Compile method in 24 different ways:

  • Instance Methods:
    • context.Compile(string code)
    • context.Compile(string code, IEnumerable<Type> parameterTypes)
    • context.Compile(string code, params Type[] parameterTypes)
    • context.Compile(string code, Type type1)
    • context.Compile(string code, Type type1, ..., Type type9)
    • context.Compile<TDelegate>(string code)
    • context.Compile<TDelegate>(string code, IEnumerable<string> parameterNames)
    • context.Compile<TDelegate>(string code, params string[] parameterNames)
  • Static Methods:
    • Eval.Compile(string code)
    • Eval.Compile(string code, IEnumerable<Type> parameterTypes)
    • Eval.Compile(string code, params Type[] parameterTypes)
    • Eval.Compile(string code, Type type1)
    • Eval.Compile(string code, Type type1, ..., Type type9)
    • Eval.Compile<TDelegate>(string code)
    • Eval.Compile<TDelegate>(string code, IEnumerable<string> parameterNames)
    • Eval.Compile<TDelegate>(string code, params string[] parameterNames)
  • Extension Methods:
    • "code".Compile()
    • "code".Compile(IEnumerable<Type> parameterTypes)
    • "code".Compile(params Type[] parameterTypes)
    • "code".Compile(Type type1)
    • "code".Compile(Type type1, ..., Type type9)
    • "code".Compile<TDelegate>()
    • "code".Compile<TDelegate>(IEnumerable<string> parameterNames)
    • "code".Compile<TDelegate>(params string[] parameterNames)

We can easily break our 24 methods into 3 topics to quickly understand them:

The code

This part is exactly like the Execute Method.

You can provide the code to compile in 3 different ways:

  1. Instance method: context.Compile(code)
  2. Static method: Eval.Compile(code)
  3. Extension method: "code".Compile

1 - In the case of the instance method (context.Compile(code)). First, you create a new instance of the EvalContext, and then you can use the Compile method from the context created:

// CREATE a new instance of EvalContext
var context = new EvalContext();

// USE the `Compile` method from the context created
var compiledList1 = context.Compile<Func<List<int>>>("new List<int>() { 1, 2, 3 };");

var list1 = compiledList1();

Try it online

2 - In the case of the static method (Eval.Compile(code)), you can directly call the static Eval.Compile method, which uses a global context under the hood:

// CALL the static `Eval.Compile` method
var compiledList2 = Eval.Compile<Func<List<int>>>("new List<int>() { 1, 2, 3 };");

var list2 = compiledList2();

Try it online

3 - In the case of the extension method ("code".Compile), you can directly call the Compile method that extends the string type, which uses a global context under the hood:

// CALL the `Compile` method that extends the string type
var compiledList3 = "new List<int>() { 1, 2, 3 };".Compile<Func<List<int>>>();

var list3 = compiledList3();

Try it online

One of the major differences is whether you want to create a new instance of the EvalContext or use the global context (an instance stored in a static variable).

We explain the answer more detail in the EvalContext documentation, but in short:

  • If you need to customize the context for this specific compilation, you should use an instance context
  • If you don't need to customize the context for this specific compilation, you can use the global context

The delegate / parameter type

The delegate is the method signature representing what parameter is allowed and the returned type. A delegate is either a Func when returning a value or an Action when there is no return.

For example, Func<string, int, double> is similar to public double MyMethodName(int, double). If you don't wish to return a type, you must use an Action such as Action<string, int> is similar to public void MyMethodName(int, double).

Most of the time, you will want to specify the delegate yourselves, but this is optional as we can create one for you depending on the list of types you specified or how you pass the parameter names.

For example, if you don't specify a delegate but specify 2 types, such as Compile(code, int, int), we will create a Func<int, int, object> delegate. Also, if you don't specify a delegate, you will need to use position in the expression, such as {0} corresponds to the first type, as there is currently no way to pass parameter names.

In the following example, you will find multiple uses of the delegate:

// 1. Eval.Compile<Func<int, int, int, List<int>>>(code)
{
	var compiledList1 = Eval.Compile<Func<int, int, int, List<int>>>("var list = new List<int>(); list.Add({0}); list.Add({1}); list.Add({2}); return list;");
	
	var list1 = compiledList1(2, 4, 6);
}


// 2. Eval.Compile(code, typeof(int), typeof(int), typeof(int));
{
	var compiledList2 = Eval.Compile("var list = new List<int>(); list.Add({0}); list.Add({1}); list.Add({2}); return list;", typeof(int), typeof(int), typeof(int));
	
	var list2 = (List<int>)compiledList2(3, 5, 7);
}

// 3. Eval.Compile<Action<List<int>>>(code);
{
	var list3 = new List<int>();
	var compiledAction = Eval.Compile<Action<List<int>>>("{0}.Add(4);{0}.Add(6);{0}.Add(8);");
	compiledAction(list3);
}

Try it online

The parameter names

Using parameter names allows you to directly use those names in the expression instead of using their position.

If you don't specify a delegate, there is currently no way to specify parameter names, so you will need to keep using their parameter position.

If you specify a delegate, you can specify parameter names allowed in the expression by passing them via an IEnumerable or params.

In the following example, you will find multiple uses of how using parameter names in your dynamic C# expression:

var compiledList1 = Eval.Compile<Func<int, int, int, List<int>>>("new List<int>() { A, B, C };", "A", "B", "C");

var list1 = compiledList1(2, 4, 6);

Try it online

Compile Async

The C# Eval library doesn't have CompileAsync methods. Under the hood, we compile the expression which the Expression.Compile method, which doesn't have a CompileAsync method either.

However, the Compile method support Async expression, but the task needs to be handled on the Execute part and not the compile part:

  1. Return a task from the delegate: Eval.Compile<Func<Task>>(code)
  2. Handle the task using await in the code

1 - If you return a task from the delegate (Eval.Compile<Func<Task>>(code)), you need to handle the task with await outside the code you want to compile and execute dynamically:

var compiled1 = Eval.Compile<Func<string, Task<string>>>("File.ReadAllTextAsync(fileName)", "fileName");		
var text1 = await compiled1(fileName);

Try it online

2 - If you handle the task, you need to use await inside the code you want to compile and execute dynamically:

var compiled2 = Eval.Compile<Func<string, string>>("await File.ReadAllTextAsync(fileName)", "fileName");
var text2 = compiled2(fileName);

Try it online

Conclusion

In conclusion, you can call the Compile method in 24 different ways, but if you understand the concept, they all look very similar, and we can resume them to this: Do you want to use the Compile method:

  • With an instance or a global context
  • With or without a delegate/parameter type
  • With or without parameter names

For people beginning with our library, we recommend using the Execute until they get comfortable with the method before moving to the Compile method, which is slightly more complex.

If you want to learn more about the Compile method, we recommend reading the My First Compilation article.


Last updated: 2024-04-09
Author:


Contents