Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to use incompleted types in reflection?

I want to dynamically generate the assembly, which can have functions with different structures. To be more accurate, these functions can be recursive, they can call other functions within the same assembly etc. I found the System.Reflection module which theoretically provides tools to do this, but in practice I have encountered many drawbacks for this approach. For example - I cannot generate recursive functions via TypeBuilder and MethodBuilder classes, because exception will be thrown (usage of incomplete types). I learned that I can generate selfrecursive functions via IlGenerator - but it is too cumbersome - I hoped that there is an easier way to do this.

Here is my program which demonstrates the problem (at generation of method Fact the following Exception is thrown:

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

Code:

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

Any help is appreciated.

like image 617
LmTinyToon Avatar asked Mar 12 '18 09:03

LmTinyToon


People also ask

What is the use of reflection in Java?

Thank you. Reflection provides objects (of type Type) that describe assemblies, modules, and types. You can use reflection to dynamically create an instance of a type, bind the type to an existing object, or get the type from an existing object and invoke its methods or access its fields and properties.

What is the use of reflection in C++?

Reflection provides objects (of type Type) that describe assemblies, modules and types. You can use reflection to dynamically create an instance of a type, bind the type to an existing object, or get the type from an existing object and invoke its methods or access its fields and properties.

How to examine and instantiate generic types with reflection?

How to: Examine and Instantiate Generic Types with Reflection. Information about generic types is obtained in the same way as information about other types: by examining a Type object that represents the generic type. The principle difference is that a generic type has a list of Type objects representing its generic type parameters.

What is an incomplete type?

An incomplete type can be: 1 A structure type whose members you have not yet specified. 2 A union type whose members you have not yet specified. 3 An array type whose dimension you have not yet specified. More ...


2 Answers

Expressions and reflection in C# have their limitations (albeit still very powerful for simple use cases). To the best of my knowledge, Emit is the way to go if you need the functionality which you describe (even the basic requirement of emitting assemblies).

Some years ago, I used RunSharp quite effectively in some minimal dynamic code generation use cases. It takes away most of the IL pains. For example, in this code I create a proxy wrapper at runtime.

You could also have a look at what Castle Project uses for their code generation, e.g., their DynamicProxy is quite popular.

like image 85
Steven Jeuris Avatar answered Oct 23 '22 10:10

Steven Jeuris


This is probably not what you are after but have you thought about CodeDom ?

The downside is it wont be truly dynamic, as you have to load the assembly

Dynamic Source Code Generation and Compilation

As pointed out by Steven Jeuris There is a great question on the differences between CodeDom and Emit Reflection.Emit vs CodeDOM

  • CodeDom generates C# source code and is usually used when generating code to be included as part of a solution and compiled in the IDE (for example, LINQ to SQL classes, WSDL, XSD all work this way). In this scenario you can also use partial classes to customize the generated code. It is less efficient, because it generates C# source and then runs the compiler to parse it (again!) and compile it. You can generate code using relatively high-level constructs (similar to C# expressions & statements) such as loops.

  • Reflection.Emit generates an IL so it directly produces an assembly that can be also stored only in memory. As a result is a lot more efficient.You have to generate low-level IL code (values are stored on stack; looping has to be implemented using jumps), so generating any more complicated logic is a bit difficult.

Simple CodeDom Exmaple from MSDN

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

And you could use one of several methods to run your code

 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);
}
like image 30
TheGeneral Avatar answered Oct 23 '22 09:10

TheGeneral