简体   繁体   English

如何使用反射以编程方式创建 class 库 DLL?

[英]How to programmatically create a class library DLL using reflection?

Suppose my code possesses the knowledge about the metadata of a nonexistent class library "mytest.dll", such as the types in this library, the functions of the types, the parameters and return types of the functions, etc.假设我的代码拥有一个不存在的 class 库“mytest.dll”的元数据知识,例如该库中的类型、类型的函数、函数的参数和返回类型等。

How does my code manufacture this DLL using techniques such as reflection?我的代码如何使用反射等技术制造此 DLL?

I know my code can generate the "mytest.cs" text file, then execute the compiler to produce the DLL, then delete the "mytest.cs" file.我知道我的代码可以生成“mytest.cs”文本文件,然后执行编译器生成DLL,然后删除“mytest.cs”文件。 Just want to know if there are "more advanced" or "cooler" ways to do it.只是想知道是否有“更高级”或“更酷”的方法来做到这一点。

Thanks.谢谢。

There are 4 main steps in the process to compile and execute dynamic .net scripts from your application, even really complex scenarios can be simplified in this way:从您的应用程序编译和执行动态 .net 脚本的过程有 4 个主要步骤,即使是非常复杂的场景也可以通过这种方式简化:

  1. Generate the code生成代码
  2. Compile the script编译脚本
  3. Load the assembly加载程序集
  4. Execute the code执行代码

Lets generate a simple Hello Generated C# World App right now::现在让我们生成一个简单的Hello Generated C# World App::

Create a method that will generate an assembly that has 1 class called HelloWorldApp , this class has 1 method called GenerateMessage it will have X input parameters that will be integers, it will return a CSV string of the arguments that were passed in to it. Create a method that will generate an assembly that has 1 class called HelloWorldApp , this class has 1 method called GenerateMessage it will have X input parameters that will be integers, it will return a CSV string of the arguments that were passed in to it.

This solution requires the following package to be installed:此解决方案需要安装以下 package:

PM> Install-Package 'Microsoft.CodeAnalysis.CSharp.Scripting'

And will require the following using statements:并且需要以下 using 语句:

using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.Emit;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Text;

Orchestration编排

The following method encapsulates the above steps:下面的方法封装了上述步骤:

private static void GenerateAndExecuteApp(int numberOfParameters)
{
    string nameSpace = "Dynamic.Example";
    string className = "HelloWorldApp";
    string methodName = "GenerateMessage";

    // 1. Generate the code
    string script = BuildScript(nameSpace, className, methodName, numberOfParameters);
    // 2. Compile the script
    // 3. Load the Assembly
    Assembly dynamicAssembly = CompileScript(script);
    // 4. Execute the code
    int[] arguments = Enumerable.Range(1, numberOfParameters).ToArray();
    string message = ExecuteScript(dynamicAssembly, nameSpace, className, methodName, arguments);

    Console.Out.WriteLine(message);
}

Generate the code生成代码

You say you already have item 1 sorted out, you can use StringBuilder , T4 templates or other mechanisms to generate the code files.你说你已经把第 1 项整理好了,你可以使用StringBuilder 、T4 模板或者其他机制来生成代码文件。

generating the code itself is its own question if you need help with that.如果您需要帮助,生成代码本身就是它自己的问题。

However, for our demo app, the following would work:但是,对于我们的演示应用程序,以下内容将起作用:

private static string BuildScript(string nameSpace, string className, string methodName, int numberOfParameters)
{
    StringBuilder code = new StringBuilder();
    code.AppendLine("using System;");
    code.AppendLine("using System.Linq;");
    code.AppendLine();
    code.AppendLine($"namespace {nameSpace}");
    code.AppendLine("{");
    code.AppendLine($"    public class {className}");
    code.AppendLine("    {");
    var parameterNames = Enumerable.Range(0, numberOfParameters).Select(x => $"p{x}").ToList();
    code.Append($"        public string {methodName}(");
    code.Append(String.Join(",", parameterNames.Select(x => $"int {x}")));
    code.AppendLine(")");
    code.AppendLine("        {");
    code.Append("        return $\"");
    code.Append(String.Join(",", parameterNames.Select(x => $"{x}={{{x}}}")));
    code.AppendLine("\";");
    code.AppendLine("        }");
    code.AppendLine("    }");
    code.AppendLine("}");
    return code.ToString();
}

For an input value of 3, the following code is generated:对于输入值 3,将生成以下代码:

using System;
using System.Linq;

namespace Dynamic.Example
{
    public class HelloWorldApp
    {
        public string GenerateMessage(int p0,int p1,int p2)
        {
        return $"p0={p0},p1={p1},p2={p2}";
        }
    }
}

Compile the script (and Load it)编译脚本(并加载它)

These are two discrete steps, however it is easiest to code them together in the same method, for this example we will ignore the generated dll and load the assembly directly into memory, that is generally the more likely use case for this type of scripting scenario anyway.这是两个独立的步骤,但是最简单的方法是用相同的方法将它们一起编码,对于本例,我们将忽略生成的 dll 并将程序集直接加载到 memory 中,这通常是此类脚本场景的更可能用例反正。

The hardest element of this is usually the referencing of the relevant dlls.最难的元素通常是相关 dll 的引用。 There are a number of ways to achieve this, including loading all the dlls that are in the current executing context, I find a simple way to do this is to access the Assembly reference from the Type reference for the types we want to use inside the dynamic script:有很多方法可以实现这一点,包括加载当前执行上下文中的所有 dll,我发现一个简单Type Assembly中访问我们想要在动态脚本:

List<string> dlls = new List<string> {
    typeof(object).Assembly.Location,
    typeof(Enumerable).Assembly.Location
};

Cut a long story short, this method compiles and loads the assembly into memory.长话短说,此方法将程序集编译并加载到 memory 中。 It includes some crude compilation error handling, just to demonstrate how to do it:它包括一些粗略的编译错误处理,只是为了演示如何做到这一点:

private static Assembly CompileScript(string script)
{
    SyntaxTree syntaxTree = CSharpSyntaxTree.ParseText(script);

    // use "mytest.dll" if you want, random works well enough
    string assemblyName = System.IO.Path.GetRandomFileName();
    List<string> dlls = new List<string> {
        typeof(object).Assembly.Location,
        typeof(Enumerable).Assembly.Location
    };
    MetadataReference[] references = dlls.Distinct().Select(x => MetadataReference.CreateFromFile(x)).ToArray();

    CSharpCompilation compilation = CSharpCompilation.Create(
        assemblyName,
        syntaxTrees: new[] { syntaxTree },
        references: references,
        options: new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary));

    // Now we actually compile the script, this includes some very crude error handling, just to show you can
    using (var ms = new MemoryStream())
    {
        EmitResult result = compilation.Emit(ms);

        if (!result.Success)
        {
            IEnumerable<Diagnostic> failures = result.Diagnostics.Where(diagnostic =>
                diagnostic.IsWarningAsError ||
                diagnostic.Severity == DiagnosticSeverity.Error);

            List<string> errors = new List<string>();
            foreach (Diagnostic diagnostic in failures)
            {
                //errors.AddDistinct(String.Format("{0} : {1}", diagnostic.Id, diagnostic.Location, diagnostic.GetMessage()));
                errors.Add(diagnostic.ToString());
            }

            throw new ApplicationException("Compilation Errors: " + String.Join(Environment.NewLine, errors));
        }
        else
        {
            ms.Seek(0, SeekOrigin.Begin);
            return Assembly.Load(ms.ToArray());
        }
    }
}

Execute the code执行代码

Finally, we can use reflection to instantiate an instance of the new app and then we can obtain a reference to the method and it.最后,我们可以使用反射来实例化新应用程序的实例,然后我们可以获得对该方法及其方法的引用。 The name of the parameters is irrelevant, as long we pass them through in the correct order:参数的名称无关紧要,只要我们以正确的顺序传递它们:

for this demo the order is sort of irrelevant to, given they are all the same type;)对于这个演示,顺序有点无关紧要,因为它们都是相同的类型;)

private static string ExecuteScript(Assembly assembly, string nameSpace, string className, string methodName, int[] arguments)
{
    var appType = assembly.GetType($"{nameSpace}.{className}");
    object app = Activator.CreateInstance(appType);
    MethodInfo method = appType.GetMethod(methodName);

    object result = method.Invoke(app, arguments.Cast<object>().ToArray());
    return result as string;
}

Output Output

The final output from all this for our method with 3 passed into it is:对于我们的方法,最终的 output 传入3是:

p0=1,p1=2,p2=3

So that was super crude, you can bypass most of the indirect reflection aspects through the use of Interfaces .所以这是超级粗略的,你可以通过使用接口绕过大部分间接反射方面。 If your generated script inherits from types or interfaces that the calling code also has a strong reference to, then ExecuteScript in the above example might look like this:如果您生成的脚本继承自调用代码也具有强引用的类型或接口,则上述示例中的ExecuteScript可能如下所示:

private static string ExecuteScript(Assembly assembly, string nameSpace, string className)
{
    var appType = assembly.GetType($"{nameSpace}.{className}");
    object app = Activator.CreateInstance(appType);

    if (app is KnownInterface known)
    {
        return known.GenerateMessage(1,2,3);
    }
    throw new NotSupportedException("Couldn't resolve known type");
}

The major benefit to using an interface or base class reference is that you can natively set properties or call other methods without having to reflect references to them all or to resort to using dynamic which would work, but becomes a bit harder to debug.使用接口或基本 class 引用的主要好处是,您可以本机设置属性或调用其他方法,而不必全部反映对它们的引用或诉诸使用dynamic的方法,这会起作用,但调试起来有点困难。

Of course the interface solution is hard to implement when we had a variable number of parameters, so that's not the best example, usually with dynamic scripts you would construct a known environment, say a known class and methods, but you might want to inject custom code into the body of the method.当然,当我们有可变数量的参数时,接口解决方案很难实现,所以这不是最好的例子,通常使用动态脚本你会构建一个已知的环境,比如已知的 class 和方法,但你可能想要注入自定义代码到方法的主体。

It's a bit of fun in the end, but this simple example shows that C# can be used as a runtime scripting engine without too much trouble.最后还是有点好玩,不过这个简单的例子说明C#可以作为运行时脚本引擎使用,没有太多麻烦。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM