【问题标题】:How to use incompleted types in reflection?如何在反射中使用不完整的类型?
【发布时间】:2018-08-20 06:05:24
【问题描述】:

我想动态生成程序集,它可以具有不同结构的功能。更准确地说,这些函数可以是递归的,它们可以在同一个程序集中调用其他函数等。我发现System.Reflection 模块理论上提供了执行此操作的工具,但实际上我遇到了这种方法的许多缺点。例如 - 我无法通过 TypeBuilder 和 MethodBuilder 类生成递归函数,因为会抛出异常(使用不完整类型)。我了解到我可以通过IlGenerator 生成自递归函数 - 但它太麻烦了 - 我希望有一种更简单的方法来做到这一点。

这是我演示问题的程序(在生成方法Fact 时抛出以下异常:

Exception thrown: 'System.NotSupportedException' in mscorlib.dll 
Additional information: Specified method is not supported..

代码:

using System;
using System.Reflection;
using System.Reflection.Emit;
using System.Linq.Expressions;

namespace GenericFuncs
{
    public class ProgramBuilder
    {
        public Type createMyProgram()
        {
            var assmName = new AssemblyName("DynamicAssemblyExample");
            var assmBuilder = AppDomain.CurrentDomain.DefineDynamicAssembly(assmName, AssemblyBuilderAccess.RunAndSave);
            var moduleBuilder = assmBuilder.DefineDynamicModule(assmName.Name, assmName.Name + ".dll");
            var myProgramType = buildMyProgram(moduleBuilder, moduleBuilder.DefineType("MyProgram", TypeAttributes.Public));
            assmBuilder.Save(assmName.Name + ".dll");
            return myProgramType;
        }        

        private Type buildMyProgram(ModuleBuilder mb, TypeBuilder programBuilder)
        {
            buildFactFunction2(mb, mb.GetType("MyProgram"), programBuilder.DefineMethod("InfLoop", MethodAttributes.Public | MethodAttributes.Static));
            buildFactFunction(mb, mb.GetType("MyProgram"), programBuilder.DefineMethod("Fact", MethodAttributes.Public | MethodAttributes.Static));
            return programBuilder.CreateType();
        }

        private void buildFactFunction(ModuleBuilder mb, Type program_type, MethodBuilder methodBuilder)
        {
            var param = Expression.Parameter(typeof(int), "n");
            var lambda = Expression.Lambda(Expression.Call(methodBuilder, param), param);
            lambda.CompileToMethod(methodBuilder);
        }

        static public void my_print(string s)
        {
            Console.WriteLine(s);
        }

        private void buildFactFunction2(ModuleBuilder mb, Type program_type, MethodBuilder methodBuilder)
        {
            var il = methodBuilder.GetILGenerator();
            il.Emit(OpCodes.Ldstr, "a");
            il.Emit(OpCodes.Call, typeof(ProgramBuilder).GetMethod("my_print"));
            il.Emit(OpCodes.Call, methodBuilder);
            il.Emit(OpCodes.Ret);
        }
    }

    class Program
    {
        static void Main(string[] args)
        {
            var pbuilder = new ProgramBuilder();
            var ptype = pbuilder.createMyProgram();
            Console.ReadLine();
        }
    }
}

感谢任何帮助。

【问题讨论】:

    标签: c# reflection system.reflection


    【解决方案1】:

    Expressions 和 C# 中的反射有其局限性(尽管对于简单的用例仍然非常强大)。据我所知,Emit 是您需要描述的功能(甚至是发射程序集的基本要求)的最佳选择。

    几年前,我在一些最小的动态代码生成用例中非常有效地使用了RunSharp。它消除了大部分 IL 的痛苦。例如,在this code 中,我在运行时创建了一个代理包装器。

    您还可以看看Castle Project 用于他们的代码生成,例如,他们的DynamicProxy 非常受欢迎。

    【讨论】:

    • 感谢您的回复。我会检查一下,但似乎 RunSharp 项目已经死了(而且我不确定它是不是相当稳定的库)。 (也许城堡项目会有所帮助)
    • @LmTinyToon 它给了你一个起点,让你知道我希望谷歌做什么。 :) 例如,我之前没有听说过Happil,现在在寻找类似的库时会出现。我想关键的信息是你需要使用 Emit(最好你可以找到最新的稳定包装库来帮助你)。
    【解决方案2】:

    这可能不是您所追求的,但您是否考虑过 CodeDom ?

    缺点是它不会是真正动态的,因为您必须加载程序集

    Dynamic Source Code Generation and Compilation

    正如 Steven Jeuris 所指出的,关于 CodeDom 和 Emit 之间的区别有一个很好的问题Reflection.Emit vs CodeDOM

    • CodeDom 生成 C# 源代码,通常在生成要作为解决方案的一部分包含并在 IDE 中编译的代码时使用(例如 例如,LINQ to SQL 类、WSDL、XSD 都以这种方式工作)。在这个 场景你也可以使用部分类来自定义生成的 代码。它的效率较低,因为它会生成 C# 源代码,然后 运行编译器来解析它(再次!)并编译它。你可以 使用相对高级的构造生成代码(类似于 C# 表达式和语句),例如循环。

    • Reflection.Emit 生成一个 IL,因此它直接生成一个程序集,该程序集也只能存储在内存中。结果是更多 高效。您必须生成低级 IL 代码(值存储在 堆;循环必须使用跳转来实现),因此生成任何 再复杂的逻辑就有点难了。

    来自 MSDN 的简单 CodeDom 示例

    public Sample()
    {
        targetUnit = new CodeCompileUnit();
        CodeNamespace samples = new CodeNamespace("CodeDOMSample");
        samples.Imports.Add(new CodeNamespaceImport("System"));
        targetClass = new CodeTypeDeclaration("CodeDOMCreatedClass");
        targetClass.IsClass = true;
        targetClass.TypeAttributes =
            TypeAttributes.Public | TypeAttributes.Sealed;
        samples.Types.Add(targetClass);
        targetUnit.Namespaces.Add(samples);
    }
    
    public void AddFields()
    {
        // Declare the widthValue field.
        CodeMemberField widthValueField = new CodeMemberField();
        widthValueField.Attributes = MemberAttributes.Private;
        widthValueField.Name = "widthValue";
        widthValueField.Type = new CodeTypeReference(typeof(System.Double));
        widthValueField.Comments.Add(new CodeCommentStatement(
            "The width of the object."));
        targetClass.Members.Add(widthValueField);
    
        // Declare the heightValue field
        CodeMemberField heightValueField = new CodeMemberField();
        heightValueField.Attributes = MemberAttributes.Private;
        heightValueField.Name = "heightValue";
        heightValueField.Type =
            new CodeTypeReference(typeof(System.Double));
        heightValueField.Comments.Add(new CodeCommentStatement(
            "The height of the object."));
        targetClass.Members.Add(heightValueField);
    }
    
    public void GenerateCSharpCode(string fileName)
    {
        CodeDomProvider provider = CodeDomProvider.CreateProvider("CSharp");
        CodeGeneratorOptions options = new CodeGeneratorOptions();
        options.BracingStyle = "C";
        using (StreamWriter sourceWriter = new StreamWriter(fileName))
        {
            provider.GenerateCodeFromCompileUnit(
                targetUnit, sourceWriter, options);
        }
    }
    
    static void Main()
    {
        Sample sample = new Sample();
        sample.AddFields();
        //sample.AddProperties();
        //sample.AddMethod();
        //sample.AddConstructor();
        //sample.AddEntryPoint();
        sample.GenerateCSharpCode(outputFileName);
    }
    

    您可以使用几种方法之一来运行您的代码

     private static Assembly CompileSourceCodeDom(string sourceCode)
     {
         CodeDomProvider cpd = new CSharpCodeProvider();
         var cp = new CompilerParameters();
         cp.ReferencedAssemblies.Add("System.dll");
         cp.GenerateExecutable = false;
         CompilerResults cr = cpd.CompileAssemblyFromSource(cp, sourceCode);
    
         return cr.CompiledAssembly;
    }
    
    private static void ExecuteFromAssembly(Assembly assembly)
    {
        Type fooType = assembly.GetType("Foo");
        MethodInfo printMethod = fooType.GetMethod("Print");
        object foo = assembly.CreateInstance("Foo");
        printMethod.Invoke(foo, BindingFlags.InvokeMethod, null, null, CultureInfo.CurrentCulture);
    }
    

    【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2012-08-30
    • 2014-01-13
    • 2021-12-11
    • 1970-01-01
    • 2012-06-08
    • 2018-01-30
    • 2023-04-09
    相关资源
    最近更新 更多