5

我正在尝试使用 Roslyn 作为解析用户在运行时使用通用格式提供的 lambda 表达式的一种方式:

// 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;

然后调用者提供code类似P => P.Property > 5. 当我为 T 使用众所周知的类型时,这一切都很好,但我希望允许用户使用更多动态类型,每个用户都可以定义自己的一组属性(带有类型)。同步表达式树不支持动态类型(因此 Roslyn 不能这样编译),我希望允许用户定义他们的属性并且我会动态生成运行时类型。

我遇到的问题是,在创建运行时类型之后,我没有用于Tin的具体类型.ContinueWith<Expression<Func<T,bool>>>

我已经使用完整的反射做类似的事情:

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();

但这会引发异常:

Microsoft.CodeAnalysis.Scripting.CompilationErrorException:错误 CS0400:类型或命名空间名称 'System.Linq.Expressions.Expression 1[[System.Func2[[Submission#1+Person,ℛ*907cf320-d303-4781-926e-cee8bf1d3aaf#2-1,版本=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' 在全局命名空间中找不到(您是否缺少程序集引用?)

在这种情况下,Person运行时类型的名称在哪里(我认为它在抱怨什么)。

我已经尝试将类型所在的程序集显式添加到ScriptOptions使用中:

var lambdaScript = (Script)continueWith.Invoke(script, new object[] { "P => P.String == \"Hi\"", scriptOptions.AddReferences(runtimeType.Assembly) });

但这失败了:

System.NotSupportedException:无法创建对没有位置的程序集的元数据引用。

我正在尝试做的事情可能吗?

4

1 回答 1

1

我设法想出了一个适合我需要的解决方案。

对于某些背景,我之前创建的方式runtimeType也是使用Roslyn 通过执行以下操作:

Dictionary<string, Type> properties = new Dictionary<string, Type>();
var classDefinition = $"public class Person {{ {string.Join("\n", properties.Select(p => $"public {p.Value.Name} {p.Key};"))} }} return typeof(Person);";

var script = baseScript.ContinueWith<Type>(classDefinition);
var scriptState = await script.RunAsync();
var runtimeType = scriptState.ReturnValue;

使用该示例(我从其他一些 SO 帖子中获得),我决定尝试将所有内容一起声明。我更新的代码现在看起来像:

private static readonly string classDefinition = "public class Person { ... }";
Script baseScript = CSharpScript.Create(string.Empty, scriptOptions).ContinueWith(classDefinition);
var scriptState = await baseScript.ContinueWith<Expression>($"Expression<Func<Person,bool>> expression = {code}; return expression;").RunAsync()
var parsedExpression = scriptState.ReturnValue;

主要区别在于,与原来的不同,我不能仅仅.ContinueWith<Expression>因为它是一个 lambda 表达式需要一个委托类型,而在更新的版本中,我们在解析的代码中声明了实际的委托类型,然后只是做一个隐式转换回表达。

我实际上并不需要Expression<Func<T,bool>>在我的代码中,因为我们只是希望将 Expression 传递给 anExpressionVisitor以将所有属性访问器重新写入数组访问器(类似于什么dynamic)。

于 2018-03-27T18:22:00.310 回答