我想知道是否可以将C#代码片段保存到文本文件(或任何输入流),然后动态执行它们?假设提供给我的内容可以在任何Main()块中编译好,是否可以编译和/或执行此代码?出于性能原因,我更愿意编译它.
至少,我可以定义一个他们需要实现的接口,然后他们将提供一个实现该接口的代码"section".
C#/所有静态.NET语言的最佳解决方案是使用CodeDOM进行此类操作.(作为一个注释,它的另一个主要目的是动态构造代码,甚至整个类.)
这是LukeH博客的一个很好的简短例子,它使用一些LINQ也只是为了好玩.
using System; using System.Collections.Generic; using System.Linq; using Microsoft.CSharp; using System.CodeDom.Compiler; class Program { static void Main(string[] args) { var csc = new CSharpCodeProvider(new Dictionary() { { "CompilerVersion", "v3.5" } }); var parameters = new CompilerParameters(new[] { "mscorlib.dll", "System.Core.dll" }, "foo.exe", true); parameters.GenerateExecutable = true; CompilerResults results = csc.CompileAssemblyFromSource(parameters, @"using System.Linq; class Program { public static void Main(string[] args) { var q = from i in Enumerable.Range(1,100) where i % 2 == 0 select i; } }"); results.Errors.Cast ().ToList().ForEach(error => Console.WriteLine(error.ErrorText)); } }
这里最重要的类是CSharpCodeProvider
利用编译器动态编译代码.如果您想再运行代码,您只需要使用一些反射来动态加载程序集并执行它.
下面是C#中的另一个示例(虽然稍微简洁一点)另外向您展示了如何使用System.Reflection
命名空间运行运行时编译的代码.
您可以将一段C#代码编译到内存中,并使用Roslyn 生成汇编字节.它已经提到但是值得为此添加一些Roslyn示例.以下是完整的示例:
using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Reflection; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.Emit; namespace RoslynCompileSample { class Program { static void Main(string[] args) { SyntaxTree syntaxTree = CSharpSyntaxTree.ParseText(@" using System; namespace RoslynCompileSample { public class Writer { public void Write(string message) { Console.WriteLine(message); } } }"); string assemblyName = Path.GetRandomFileName(); MetadataReference[] references = new MetadataReference[] { MetadataReference.CreateFromFile(typeof(object).Assembly.Location), MetadataReference.CreateFromFile(typeof(Enumerable).Assembly.Location) }; CSharpCompilation compilation = CSharpCompilation.Create( assemblyName, syntaxTrees: new[] { syntaxTree }, references: references, options: new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary)); using (var ms = new MemoryStream()) { EmitResult result = compilation.Emit(ms); if (!result.Success) { IEnumerablefailures = result.Diagnostics.Where(diagnostic => diagnostic.IsWarningAsError || diagnostic.Severity == DiagnosticSeverity.Error); foreach (Diagnostic diagnostic in failures) { Console.Error.WriteLine("{0}: {1}", diagnostic.Id, diagnostic.GetMessage()); } } else { ms.Seek(0, SeekOrigin.Begin); Assembly assembly = Assembly.Load(ms.ToArray()); Type type = assembly.GetType("RoslynCompileSample.Writer"); object obj = Activator.CreateInstance(type); type.InvokeMember("Write", BindingFlags.Default | BindingFlags.InvokeMethod, null, obj, new object[] { "Hello World" }); } } Console.ReadLine(); } } }
其他人已经就如何在运行时生成代码给出了很好的答案,所以我想我会解决你的第二段.我对此有一些经验,只想分享我从这次经历中吸取的教训.
至少,我可以定义一个他们需要实现的接口,然后他们将提供一个实现该接口的代码"section".
如果您使用interface
基本类型,则可能会出现问题.如果您interface
将来添加一个新方法,那么实现interface
现在的所有现有客户端提供的类都将变为抽象,这意味着您将无法在运行时编译或实例化客户端提供的类.
在发布旧接口大约1年后以及在分发需要支持的大量"遗留"数据之后添加新方法时,我遇到了这个问题.我最终创建了一个继承旧接口的新接口,但这种方法使得加载和实例化客户端提供的类变得更加困难,因为我必须检查哪个接口可用.
我当时想到的一个解决方案是使用实际的类作为基类型,如下所示.类本身可以标记为抽象,但所有方法都应该是空虚拟方法(而不是抽象方法).然后,客户端可以覆盖他们想要的方法,并且可以向基类添加新方法,而不会使现有客户端提供的代码无效.
public abstract class BaseClass { public virtual void Foo1() { } public virtual bool Foo2() { return false; } ... }
无论这个问题是否适用,您都应该考虑如何对代码库和客户端提供的代码之间的接口进行版本控制.