如何在.NET中实现由用户输入动态创建函数体并调用?
Great question! For your internal tool where security isn't a concern, .NET has a couple of built-in, straightforward ways to let users input function implementations and compile them for later use. Let's dive into the most practical approaches:
CodeDOM is .NET's classic dynamic compilation API—it's included out of the box in both .NET Framework and .NET Core/.NET 5+, making it perfect for your "no external dependencies" requirement. Here's how to implement it:
Step-by-Step Breakdown
- Wrap user code in a valid class: You'll need to take the function header you provide, plus the user's implementation, and package it into a complete C# code snippet (including necessary
usingstatements). - Compile in-memory: Use the
CSharpCodeProviderto compile the code directly into memory (no disk writes needed). - Store a delegate: Once compiled, use reflection to grab the method and convert it to a delegate for easy, fast later calls.
Full Code Example
using System; using System.CodeDom.Compiler; using System.Reflection; using System.Linq; using Microsoft.CSharp; public class DynamicFunctionManager { // Store the compiled function as a delegate for quick invocation public Delegate CompiledFunction { get; private set; } public bool CompileFunction(string functionHeader, string userImplementation, Type[] parameterTypes, Type returnType) { // Build the full code template—add user-specified using statements here if needed string fullCode = $@" using System; // Let users add extra using lines here (e.g., using System.Linq;) public class DynamicFunctionContainer {{ {functionHeader} {{ {userImplementation} }} }}"; // Configure compilation settings var compilerParams = new CompilerParameters { GenerateInMemory = true, // Keep the assembly in memory—no files created TreatWarningsAsErrors = false, ReferencedAssemblies = { "System.dll", "System.Core.dll" } // Add other framework assemblies if required }; // Compile the code using var codeProvider = new CSharpCodeProvider(); CompilerResults compileResults = codeProvider.CompileAssemblyFromSource(compilerParams, fullCode); // Handle compilation errors (useful for giving feedback to the user) if (compileResults.Errors.HasErrors) { foreach (CompilerError error in compileResults.Errors) { Console.WriteLine($"Compilation Error: {error.ErrorText} (Line {error.Line})"); } return false; } // Load the compiled assembly and grab the method Assembly compiledAssembly = compileResults.CompiledAssembly; Type functionClass = compiledAssembly.GetType("DynamicFunctionContainer"); MethodInfo targetMethod = functionClass.GetMethod(GetMethodNameFromHeader(functionHeader)); // Create a delegate for fast future calls Type delegateType = typeof(Func<,>).MakeGenericType(parameterTypes.Append(returnType).ToArray()); CompiledFunction = Delegate.CreateDelegate(delegateType, targetMethod); return true; } // Helper to extract the method name from the function header (e.g., "Add" from "public static int Add(int a, int b)") private string GetMethodNameFromHeader(string header) { return header.Split(new[] {' ', '('}, StringSplitOptions.RemoveEmptyEntries)[2]; } // Invoke the compiled function with parameters public object InvokeFunction(params object[] parameters) { return CompiledFunction.DynamicInvoke(parameters); } }
How to Use It
// Example: Let's compile a simple addition function var functionManager = new DynamicFunctionManager(); bool compileSuccess = functionManager.CompileFunction( functionHeader: "public static int Add(int firstNum, int secondNum)", userImplementation: "return firstNum + secondNum;", parameterTypes: new Type[] { typeof(int), typeof(int) }, returnType: typeof(int)); if (compileSuccess) { int result = (int)functionManager.InvokeFunction(2, 5); Console.WriteLine($"Result: {result}"); // Outputs 7 }
If you're open to adding a NuGet package (Microsoft.CodeAnalysis.CSharp), Roslyn is a more powerful, flexible option. It's .NET's official compiler platform and offers better error diagnostics and syntax tree manipulation. However, since you specified no external dependencies, this is just a useful alternative to keep in mind if your requirements change later.
- Let users add
usingstatements: Add a text box where users can input additionalusinglines, then insert them into the code template—this lets them use any .NET framework types they need. - Store multiple functions: Use a
Dictionary<string, Delegate>to store compiled functions by name or ID, so you can call different ones later. - Error feedback: Show compilation errors to the user so they can fix their implementation code.
内容的提问来源于stack exchange,提问作者Andrew

