如何在反射中使用未完成的类型?

时间:2018-03-12 09:10:09

标签: c# reflection system.reflection

我想动态生成程序集,它可以具有不同结构的函数。为了更准确,这些函数可以是递归的,它们可以在同一个程序集中调用其他函数等。我找到了System.Reflection模块,它在理论上提供了执行此操作的工具,但实际上我遇到了这种方法的许多缺点。例如 - 我无法通过TypeBuilderMethodBuilder类生成递归函数,因为将抛出异常(使用不完整类型)。我了解到我可以通过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();
        }
    }
}

感谢任何帮助。

2 个答案:

答案 0 :(得分:4)

Expressions C#中的反映有其局限性(虽然对于简单的用例仍然非常强大)。据我所知,如果您需要所描述的功能(甚至是发射组件的基本要求),Emit是可行的方法。

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

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

答案 1 :(得分:3)

这可能不是你想要的,但你有没有想过 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 Exmaple

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);
}