使用Roslyn解析运行时生成类型

时间:2018-03-26 23:29:15

标签: c# roslyn runtime-compilation

我尝试使用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不能这样编译),我希望允许用户定义它们的属性,并且我动态生成运行时类型。

我遇到的问题是,在创建运行时类型后,我没有一个具体类型可用于T中的.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:类型或命名空间名称&#39; System.Linq.Expressions.Expression 1[[System.Func 2 [[提交#1 +人员,ℛ* 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&#39;无法在全局命名空间中找到(您是否缺少程序集引用?)

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

我尝试使用以下方法明确将类型所在的程序集添加到ScriptOptions中:

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

但这失败了:

  

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

我正在尝试做什么?

1 个答案:

答案 0 :(得分:0)

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

对于某些背景,我之前创建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。

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