I'm trying to use Roslyn as a way to parse lambda expressions provided at runtime by users using the general format:
// The first line is normally static and re-used across callers to save perf
Script baseScript = CSharpScript.Create(string.Empty, scriptOptions);
ScriptState<T> scriptState = await baseScript.ContinueWith<Expression<Func<T, bool>>>(code, scriptOptions).RunAsync()
var parsedExpression = scriptState.ReturnValue;
Then the caller provides code
as something like P => P.Property > 5
. This all works great when I use a well-known type for T, but I'd like to allow users to use more dynamic types, where each user may define their own set of properties (with types). Sync Expression trees don't support dynamic types (and thus Roslyn can't compile as such), I was hoping to allow the user to define their properties and I'd dynamically generate a runtime type.
The problem I'm bumping into is, after creating the runtime type, I don't have a concrete type to use for T
in .ContinueWith<Expression<Func<T,bool>>>
.
I've using full on reflection doing something like:
var funcType = typeof(Func<,>).MakeGenericType(runtimeType, typeof(bool));
var expressionType = typeof(Expression<>).MakeGenericType(funcType);
var continueWith = script.GetType()
.GetMethods()
.Single(m => m.Name == "ContinueWith" && m.IsGenericMethod && m.GetParameters().Any(p => p.ParameterType == typeof(string)))
.MakeGenericMethod(expressionType);
var lambdaScript = (Script)continueWith.Invoke(script, new object[] { "P => P.String == \"Hi\"", null });
var lambdaScriptState = await lambdaScript.RunAsync();
But this throws an exception:
Microsoft.CodeAnalysis.Scripting.CompilationErrorException: error CS0400: The type or namespace name 'System.Linq.Expressions.Expression
1[[System.Func
2[[Submission#1+Person, ℛ*907cf320-d303-4781-926e-cee8bf1d3aaf#2-1, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null],[System.Boolean, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089]], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089]], System.Core, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089' could not be found in the global namespace (are you missing an assembly reference?)
Where in this case, Person
is the name of the runtime type (and I think what it's complaining about).
I've tried explicitly adding the assembly the type is in to the ScriptOptions
using:
var lambdaScript = (Script)continueWith.Invoke(script, new object[] { "P => P.String == \"Hi\"", scriptOptions.AddReferences(runtimeType.Assembly) });
But this fails with:
System.NotSupportedException: Can't create a metadata reference to an assembly without location.
Is what I'm trying to do possible?