This project has moved. For the latest updates, please go here.

ScopeCompile

The TypeRegistry allows you to access types and instances in your expression without having to worry about passing them each call as a parameter. However, the TypeRegistry only allows access to the instance at the time the instance is added to the registry.

Often you need to execute your code againt live, changing data that is stored on a variable. Instead of registering the variable in the TypeRegistry, you can pass it as a context object to your delegate

"context.method(context.propertyA, context.propertyB) + context.propertyC"

ScopeCompile is a feature that allows you to set the context for symbols and identifiers in your code, and pass a single object into the compiled function that sets the context for the expression. This allows you to keep your expression short and tidy.

"method(propertyA, propertyB) + propertyC"

When you use ScopeCompile, ExpressionEvaluator assumes that any identifier that has not been registered in the TypeRegistry is a member of the context object being passed into the generated function.

A Data Binding Example

Consider a templating engine which allows a List<> to be bound to a template, enabling data to be repeated as rows in a table. Your data is stored in a List<T> data.Items , The data can span several "pages" so you have a currentPage variable, and you have a helper.GetPagedItems to do the actual paging of data.

The template should be flexible and allow you to bind any expression that returns a list as the engine can be reused in other contexts, so your binding expression in the template looks like this:

"helper.getPagedItems(data.Items, currentPage)" 

The First Approach

The binding engine has a Bind() function that accepts your expression and compiles it. Using the TypeRegistry you have to register each symbol, but that means that you have to know beforehand what symbols there are in the expression:

public void Bind(string expression)
var reg = new TypeRegistry();
var exp = new CompiledExpression(expression) { TypeRegistry = reg };
reg.RegisterSymbol("helper", helper);
reg.RegisterSymbol("data", data);
reg.RegisterSymbol("currentPage", currentPage);
// evaluate the expression and apply to the template
...

This is clearly not reusable. Let's try again. We move the registry out of the binder, into a class specific to the data that will be bound.

// data-specific class
interface IBindable 
{
   TypeRegistry Registry {get; set; }
}

class DataToBeBound : IBindable
{
   public TypeRegistry Registry {get; set; }

  public DataToBeBound()
  {
    /// instantiate helper, data, currentpage...
     Registry = new TypeRegistry();
     Registry.RegisterSymbol("helper", helper);
     Registry.RegisterSymbol("data", data);
     Registry.RegisterSymbol("currentPage", currentPage);
   }

}

// templating engine, in the binding loop...
IBindable data = getBoundData();
binder.Bind(expression, data.Registry)

...

public void Bind(string expression, TypeRegistry reg)
var exp = new CompiledExpression(expression) { TypeRegistry = reg };
...

A bit more reusable, but we still have to register all the symbols, which means a compile-time dependency.

A Common Context

Alternatively, create an object that will hold all of the instances we need to pass in the expression. Using the ExpandoObject class, it becomes a dynamic that we can attach anything to at runtime, which is perfect for our data binding needs. Let's call the instance "scope". Our expression becomes:

"scope.helper.getPagedItems(scope.data.Items, scope.currentPage)"

// data-specific class
interface IBindable
{
   TypeRegistry Registry {get; set; }
}

class DataToBeBound : IBindable
{
   public dynamic Scope { get; set; }
   public TypeRegistry Registry {get; set; }

  public DataToBeBound()
  {
      /// instantiate helper, data, currentpage and dynamically assign to scope
     Registry = new TypeRegistry();
     Registry.RegisterSymbol("scope", Scope);
   }

}

// templating engine, in the binding loop...
IBindable data = getBoundData();
binder.Bind(expression, data)

public void Bind(string expression, IBindable data)
var exp = new CompiledExpression(expression) { TypeRegistry = data.Registry };
...

Binding the data at compile time means that the references to the data to which the function is linked to is also bound at compile time. Therefore if you changed the value of scope and executed func() afterward, it would still be bound to the old Scope.

func();  // bind the data
...
scope = new Scope(); // set the new scope
func();  // old scope is still in effect?

You could recompile the expression, but the compilation time, which - if called several times - would introduce unnecessary overhead. What we need is a way to reuse the function without needing to recompile.

AngularJS to the Rescue! ... ?

ScopeCompile is an AngularJS-inspired approach to data binding, where the current context is set to a scope object, similar to the TypeRegistry approach. The key difference here is that the scope is not set at compile time, but is instead passed as a parameter to the compiled function. You don't have to register or use the scope symbol in the expression: any identifiers that are not registered types or symbols are directly bound to the current scope. This is just syntactic sugar to make the expression easier to read and more AngularJS-like.

var scope = new Scope() { ... };
var expression = "helper.getPagedItems(data.Items, currentPage)";
var exp = new CompiledExpression(expression);
var func = exp.ScopeCompile();
var iterator = func(scope);  // execute the compiled function against the scope

Since func is a delegate, we are now free to get rid of exp instance. We will need to execute this expression again for the next page (a different context), but we shouldn't need to compile it again as:
  • compiling an expression once is negligible, but compiling the same expression multiple times causes noticeable delay
  • the expression itself has not changed, only the context.

Generic Versions

CompiledExpression and ScopeCompile comes in generic and non-generic flavors.

CompiledExpression.ScopeCompile() - returns a Func<object, object>()
CompiledExpression.ScopeCompile<TParam>() - returns a Func<TParam, object>()

CompiledExpression<TResult>.ScopeCompile() - returns a Func<object, TResult>()
CompiledExpression<TResult>.ScopeCompile<TParam>() - returns a Func<TParam, TResult>()

Use CompiledExpression when the result of your expression may be of varying types. Internally the result is boxed into an object.

Use CompiledExpression<TResult> when you know the exact type of your result, and wish to avoid boxing to type object.

Use ScopeCompile() when you don't know the type of your scope parameter. ExpressionEval will use run-time callsite binding, basically generating the same code as if you were using the dynamic keyword (late-bound). Dynamics is fairly undersupported at this time (but this is improving). Any errors with property and method binding might be thrown at "runtime" i.e. when the delegate is executed.

Use ScopeCompile<TParam>() if you of course know the type of your scope parameter. It will throw an error at "compile" time i.e. when Parse() is called, if properties or methods are not found on the object.

Last edited Feb 3, 2015 at 1:29 PM by RupertAvery, version 9

Comments

mennodeij Oct 20, 2015 at 4:41 PM 
I've tried to combine the TypeRegistry.RegisterType("Math", typeof(Math)) with ScopeCompile(), but that does not work. The error is "cannot find Sin on RuntimeObject" or similar, where I wanted to use Math.Sin(...) in the expression.
It does work when using Call() but it would be nice to re-use the expression as I will be using it many times with varying values.