How do I evaluate paths like Property1.Property2 into compiled Expression without Roslyn

Viewed 42

In my blazor webassembly app I would like to evaluate string paths like:

  • "Property1.Property2"
  • optionally Property1[index] and Property1["key"]
  • optionally Method1().Property2

The requirements are:

  1. I'm able to compile/cache the expression somehow and evaluate it multiple times with different parameter (parameter = source object). Speed is important
  2. I need to evaluate the path in order to get value
  3. I need to be able to set value if the path points to a property.
  4. I need to be able to get the MemberInfo, although this could be done externally using different evaluator.

I do not need to evaluate mathematical expression like Property1 * Property2

What do I have

I have used RoslynAPI, simmilar to this:

var discountFilter = "album => album.Quantity > 0";
var options = ScriptOptions.Default.AddReferences(typeof(Album).Assembly);
 
Func<Album, bool> discountFilterExpression = await CSharpScript.EvaluateAsync<Func<Album, bool>>(discountFilter, options);
 
var discountedAlbums = albums.Where(discountFilterExpression)

but I cannot use Roslyn in my Blazor WebAssembly apps, because it it too heavy.

Is there a more lightweight way of evaluating paths, that preferably builds expressions?

1 Answers

I haven't tested it for WASM specifically, but expression trees may be your friend here - however, there is no magic "parse this string" button for this; but assuming you have your own parser for the string, you would be looking to end up inspecting the AST and constructing an Expression similar to:

// constructs: (Album album) => album.Quantity > 0
var p = Expression.Parameter(typeof(Album), "album");
var body = Expression.GreaterThan(
    Expression.PropertyOrField(p, "Quantity"),
    Expression.Constant(0, typeof(int)));
var lambda = Expression.Lambda<Func<Album, bool>>(body, p);

Then you can get your reusable delegate via:

Func<Album, bool> func = lambda.Compile();

In terms of setting a value: that's fine too: Expression.Assign inside an Expression.Block, which can again be compiled.

However! On WASM, I suspect that Compile() is going to use interpretation rather than ref-emit, which means it might just be using reflection (PropertyInfo.SetValue etc) under the hood, rather than anything more native.

Related