需求是,逻辑处理里有一坨结构化的数据,结构可能比较复杂,有各种属性,每种属性类型有简单类型(int, long, decimal 之类),也有自定义的类类型,也有List、Dictionary。并且这坨数据的结构是稳定的,在不同客户的应用和场景中,只会存在值不同,不会存在属性名字、类型的变化。
然后界面上提供一个输入框,在不同客户的应用和场景中,通过输入不同的表达式,得到客户所要求的数据或者数据集合。以免要给每个客户编译一个版本。
我使用 CSharpCompilation 进行动态编译,但是编译其它程序都没毛病,而在编译带 Linq 解析的字符串时,总是出错:
(12,17): error CS0012: 类型“Func<,>”在未引用的程序集中定义。必须添加对程序集“System.Runtime, Version=5.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a”的引用。
(12,17): error CS0012: 类型“Func<,,>”在未引用的程序集中定义。必须添加对程序集“System.Runtime, Version=5.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a”的引用。
以下是测试程序
1 using Microsoft.CodeAnalysis; 2 using Microsoft.CodeAnalysis.CSharp; 3 using Microsoft.CodeAnalysis.Emit; 4 using System; 5 using System.Collections.Generic; 6 using System.IO; 7 using System.Linq; 8 using System.Reflection; 9 using System.Text; 10 using System.Threading.Tasks; 11 using System.Xml.Linq; 12 13 namespace CSLanguageTest.Compile { 14 public class Test1 { 15 16 private string GetCSLinqCode() { 17 StringBuilder sb = new StringBuilder(); 18 sb 19 .AppendLine("using System;") 20 .AppendLine("using System.Collections.Generic;") 21 .AppendLine("using CSLanguageTest.Compile;") 22 .AppendLine("using System.Linq;") 23 .AppendLine("using System.Runtime;") 24 .AppendLine("using System.Xml.Linq;") 25 .AppendLine("namespace Test {") 26 .AppendLine(" public class Util {") 27 .AppendLine(" public object GetQuery(List<Student> ds) {") 28 .AppendLine(" var result = ") 29 .AppendLine(" from stu in ds") 30 .AppendLine(" where stu.Age > 28") 31 .AppendLine(" select stu;") 32 //.AppendLine(" return result;") 33 .AppendLine(" return null;") 34 .AppendLine(" }") 35 .AppendLine(" }") 36 .AppendLine("}") 37 .AppendLine(); 38 return sb.ToString(); 39 } 40 41 42 public void F3() { 43 string csCode = this.GetCSLinqCode(); 44 45 SyntaxTree tree = CSharpSyntaxTree.ParseText(csCode); 46 var compilation = 47 CSharpCompilation.Create("Test") 48 .AddReferences( 49 MetadataReference.CreateFromFile(typeof(object).Assembly.Location), 50 MetadataReference.CreateFromFile(typeof(List<Student>).Assembly.Location), 51 MetadataReference.CreateFromFile(typeof(Student).Assembly.Location), 52 MetadataReference.CreateFromFile(typeof(System.Runtime.GCSettings).Assembly.Location), 53 MetadataReference.CreateFromFile(typeof(System.Runtime.CompilerServices.DynamicAttribute).Assembly.Location), 54 MetadataReference.CreateFromFile(typeof(System.Linq.EnumerableQuery).Assembly.Location), 55 MetadataReference.CreateFromFile(typeof(System.Xml.Linq.XCData).Assembly.Location) 56 ) 57 .AddSyntaxTrees(tree) 58 .WithOptions(new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary)); 59 60 61 EmitResult emitResult; 62 byte[] dllBytes; 63 using (var stream = new MemoryStream()) { 64 emitResult = compilation.Emit(stream); 65 dllBytes = stream.ToArray(); 66 } 67 68 if (emitResult.Success) { 69 Assembly assembly = Assembly.Load(dllBytes); 70 var obj = assembly.CreateInstance("Test.Util"); 71 var method = obj.GetType().GetMethod("GetQuery", new Type[] { typeof(List<Student>) }); 72 Console.WriteLine(method); 73 } else { 74 foreach(var error in emitResult.Diagnostics) { 75 Console.WriteLine(error.ToString()); 76 } 77 } 78 } 79 } 80 }
using System; using System.Collections.Generic; using System.Linq; namespace CSLanguageTest.Compile { public class Student { public string Name { get; set; } public int Age { get; set; } } }
建议试试 Microsoft.CodeAnalysis.CSharp.Scripting
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);
//hooray now we have discountedAlbums!
详见 Easy way to create a C# lambda expression from a string (with Roslyn)