我想知道是否有可能将c#代码片段保存到文本文件(或任何输入流),然后动态执行这些?假设提供给我的内容将在任何Main()块内编译良好,是否有可能编译和/或执行此代码?出于性能考虑,我更愿意编译它。
至少,我可以定义他们需要实现的接口,然后他们将提供实现该接口的代码“部分”。
我想知道是否有可能将c#代码片段保存到文本文件(或任何输入流),然后动态执行这些?假设提供给我的内容将在任何Main()块内编译良好,是否有可能编译和/或执行此代码?出于性能考虑,我更愿意编译它。
至少,我可以定义他们需要实现的接口,然后他们将提供实现该接口的代码“部分”。
当前回答
您可以将一段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)
{
// define source code, then parse it (to the type used for compilation)
SyntaxTree syntaxTree = CSharpSyntaxTree.ParseText(@"
using System;
namespace RoslynCompileSample
{
public class Writer
{
public void Write(string message)
{
Console.WriteLine(message);
}
}
}");
// define other necessary objects for compilation
string assemblyName = Path.GetRandomFileName();
MetadataReference[] references = new MetadataReference[]
{
MetadataReference.CreateFromFile(typeof(object).Assembly.Location),
MetadataReference.CreateFromFile(typeof(Enumerable).Assembly.Location)
};
// analyse and generate IL code from syntax tree
CSharpCompilation compilation = CSharpCompilation.Create(
assemblyName,
syntaxTrees: new[] { syntaxTree },
references: references,
options: new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary));
using (var ms = new MemoryStream())
{
// write IL code into memory
EmitResult result = compilation.Emit(ms);
if (!result.Success)
{
// handle exceptions
IEnumerable<Diagnostic> failures = 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
{
// load this 'virtual' DLL so that we can use
ms.Seek(0, SeekOrigin.Begin);
Assembly assembly = Assembly.Load(ms.ToArray());
// create instance of the desired class and call the desired function
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();
}
}
}
其他回答
关于如何在运行时生成代码,其他人已经给出了很好的答案,所以我想我应该谈谈你的第二段。我在这方面有一些经验,只是想分享我从中学到的一课。
至少,我可以定义an 接口,它们将被要求 为了实现,他们会提供 代码“section”实现了这一点 接口。
如果使用接口作为基类型,可能会遇到问题。如果将来向接口中添加一个新方法,那么实现该接口的所有现有客户端提供的类现在都将变成抽象的,这意味着您将无法在运行时编译或实例化客户端提供的类。
在发布旧接口大约1年之后,在分发了大量需要支持的“遗留”数据之后,需要添加新方法时,我遇到了这个问题。我最终创建了一个继承自旧接口的新接口,但这种方法使加载和实例化客户端提供的类变得更加困难,因为我必须检查哪个接口可用。
我当时想到的一个解决方案是使用一个实际的类作为基类型,如下所示。类本身可以被标记为抽象的,但是所有的方法都应该是空的虚方法(不是抽象方法)。然后,客户端可以重写他们想要的方法,我可以向基类添加新方法,而不会使现有的客户端提供的代码无效。
public abstract class BaseClass
{
public virtual void Foo1() { }
public virtual bool Foo2() { return false; }
...
}
不管这个问题是否适用,您都应该考虑如何对代码库和客户端提供的代码之间的接口进行版本控制。
您可以将一段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)
{
// define source code, then parse it (to the type used for compilation)
SyntaxTree syntaxTree = CSharpSyntaxTree.ParseText(@"
using System;
namespace RoslynCompileSample
{
public class Writer
{
public void Write(string message)
{
Console.WriteLine(message);
}
}
}");
// define other necessary objects for compilation
string assemblyName = Path.GetRandomFileName();
MetadataReference[] references = new MetadataReference[]
{
MetadataReference.CreateFromFile(typeof(object).Assembly.Location),
MetadataReference.CreateFromFile(typeof(Enumerable).Assembly.Location)
};
// analyse and generate IL code from syntax tree
CSharpCompilation compilation = CSharpCompilation.Create(
assemblyName,
syntaxTrees: new[] { syntaxTree },
references: references,
options: new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary));
using (var ms = new MemoryStream())
{
// write IL code into memory
EmitResult result = compilation.Emit(ms);
if (!result.Success)
{
// handle exceptions
IEnumerable<Diagnostic> failures = 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
{
// load this 'virtual' DLL so that we can use
ms.Seek(0, SeekOrigin.Begin);
Assembly assembly = Assembly.Load(ms.ToArray());
// create instance of the desired class and call the desired function
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();
}
}
}
我最近需要生成用于单元测试的进程。这篇文章很有用,因为我创建了一个简单的类来使用字符串代码或来自我的项目的代码来做到这一点。要构建这个类,需要ICSharpCode。反编译器和微软。CodeAnalysis NuGet包。这是这个类:
using ICSharpCode.Decompiler;
using ICSharpCode.Decompiler.CSharp;
using ICSharpCode.Decompiler.TypeSystem;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
public static class CSharpRunner
{
public static object Run(string snippet, IEnumerable<Assembly> references, string typeName, string methodName, params object[] args) =>
Invoke(Compile(Parse(snippet), references), typeName, methodName, args);
public static object Run(MethodInfo methodInfo, params object[] args)
{
var refs = methodInfo.DeclaringType.Assembly.GetReferencedAssemblies().Select(n => Assembly.Load(n));
return Invoke(Compile(Decompile(methodInfo), refs), methodInfo.DeclaringType.FullName, methodInfo.Name, args);
}
private static Assembly Compile(SyntaxTree syntaxTree, IEnumerable<Assembly> references = null)
{
if (references is null) references = new[] { typeof(object).Assembly, typeof(Enumerable).Assembly };
var mrefs = references.Select(a => MetadataReference.CreateFromFile(a.Location));
var compilation = CSharpCompilation.Create(Path.GetRandomFileName(), new[] { syntaxTree }, mrefs, new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary));
using (var ms = new MemoryStream())
{
var result = compilation.Emit(ms);
if (result.Success)
{
ms.Seek(0, SeekOrigin.Begin);
return Assembly.Load(ms.ToArray());
}
else
{
throw new InvalidOperationException(string.Join("\n", result.Diagnostics.Where(diagnostic => diagnostic.IsWarningAsError || diagnostic.Severity == DiagnosticSeverity.Error).Select(d => $"{d.Id}: {d.GetMessage()}")));
}
}
}
private static SyntaxTree Decompile(MethodInfo methodInfo)
{
var decompiler = new CSharpDecompiler(methodInfo.DeclaringType.Assembly.Location, new DecompilerSettings());
var typeInfo = decompiler.TypeSystem.MainModule.Compilation.FindType(methodInfo.DeclaringType).GetDefinition();
return Parse(decompiler.DecompileTypeAsString(typeInfo.FullTypeName));
}
private static object Invoke(Assembly assembly, string typeName, string methodName, object[] args)
{
var type = assembly.GetType(typeName);
var obj = Activator.CreateInstance(type);
return type.InvokeMember(methodName, BindingFlags.Default | BindingFlags.InvokeMethod, null, obj, args);
}
private static SyntaxTree Parse(string snippet) => CSharpSyntaxTree.ParseText(snippet);
}
要使用它,调用Run方法如下所示:
void Demo1()
{
const string code = @"
public class Runner
{
public void Run() { System.IO.File.AppendAllText(@""C:\Temp\NUnitTest.txt"", System.DateTime.Now.ToString(""o"") + ""\n""); }
}";
CSharpRunner.Run(code, null, "Runner", "Run");
}
void Demo2()
{
CSharpRunner.Run(typeof(Runner).GetMethod("Run"));
}
public class Runner
{
public void Run() { System.IO.File.AppendAllText(@"C:\Temp\NUnitTest.txt", System.DateTime.Now.ToString("o") + "\n"); }
}
发现这很有用-确保编译后的程序集引用你当前引用的所有内容,因为你很有可能希望你正在编译的c#在代码中使用一些类等。
(字符串代码是正在编译的动态c#)
var refs = AppDomain.CurrentDomain.GetAssemblies();
var refFiles = refs.Where(a => !a.IsDynamic).Select(a => a.Location).ToArray();
var cSharp = (new Microsoft.CSharp.CSharpCodeProvider()).CreateCompiler();
var compileParams = new System.CodeDom.Compiler.CompilerParameters(refFiles);
compileParams.GenerateInMemory = true;
compileParams.GenerateExecutable = false;
var compilerResult = cSharp.CompileAssemblyFromSource(compileParams, code);
var asm = compilerResult.CompiledAssembly;
在我的例子中,我发出了一个类,它的名称存储在一个字符串className中,它有一个名为Get()的公共静态方法,返回类型为StoryDataIds。下面是调用该方法的样子:
var tempType = asm.GetType(className);
var ids = (StoryDataIds)tempType.GetMethod("Get").Invoke(null, null);
Warning: Compilation can be surprisingly, extremely slow. A small, relatively simple 10-line chunk of code compiles at normal priority in 2-10 seconds on our relatively fast server. You should never tie calls to CompileAssemblyFromSource() to anything with normal performance expectations, like a web request. Instead, proactively compile code you need on a low-priority thread and have a way of dealing with code that requires that code to be ready, until it's had a chance to finish compiling. For example you could use it in a batch job process.
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<string, string>() { { "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<CompilerError>().ToList().ForEach(error => Console.WriteLine(error.ErrorText));
}
}
这里最重要的类是CSharpCodeProvider,它利用编译器动态编译代码。如果希望随后运行代码,只需要使用一些反射来动态加载程序集并执行它。
下面是c#中的另一个示例(尽管稍微不那么简洁),它还精确地向您展示了如何使用System运行运行时编译的代码。反射的名称空间。