0

I use the CSharpCodeProvider to execute C# code from a string. But of course it creates a class for the code to run. Is there a way to call a method from the current app it is called from? For eg to call CallTest() from a parsed script in this (pseudo) example code

public class Test {

// lot of code

private void CallTest() {
    Console.WriteLine("CallTest()");
}

public object OnMethodInvoke(string functionName, List<object> parameters) {
    // lot of if statements
    if(functionName == "exec") {
        CSharpCodeProvider c = new CSharpCodeProvider();
        ICodeCompiler icc = c.CreateCompiler();
        CompilerParameters cp = new CompilerParameters();
        cp.ReferencedAssemblies.Add("System.dll");
        cp.CompilerOptions = "/t:library";
        cp.GenerateInMemory = true;
        System.Text.StringBuilder sb = new System.Text.StringBuilder("");
        sb.Append("using System;\n");
        sb.Append("namespace ScriptStack.Dynamic {\n");
        sb.Append("public class Code {\n");
        sb.Append("public object EvalCode(){\n");
        sb.Append((string)parameters[0]);
        sb.Append("}\n");
        sb.Append("}\n");
        sb.Append("}\n");
        CompilerResults cr = icc.CompileAssemblyFromSource(cp, sb.ToString());
        if (cr.Errors.Count > 0) return null;
        System.Reflection.Assembly a = cr.CompiledAssembly;
        object o = a.CreateInstance("ScriptStack.Dynamic.Code");
        MethodInfo mi = o.GetType().GetMethod("EvalCode");
        return mi.Invoke(o, null);
    }
}

}
banner
  • 69
  • 1
  • 8
  • Based on your problem statement, it seems what you are trying to achieve is easier to do with Reflection. Is there a reason you prefer CodeDom? – timur Feb 08 '20 at 12:05

1 Answers1

0

If I got your problem statement correctly, you want to be able to compile a statement like

t.CallTest(); //where t is an instance of your class

and run it.

As I hinted in the comments, Reflection might be way easier to do. But for a second, let's imagine you need to keep pressing on and generate some fancy code that you will then need to run (which is where LINQ Expression trees might be a better answer, but never mind - suppose we're still sticking to CodeDom).

There are a couple of issues I see to overcome here:

  1. The unit of compilation in CodeDom is assembly, I don't believe you can have assembly nested in a class. Therefore you need to define at least a namespace and a class (which you seem to have done already)
  2. I also don't believe CodeDom allows you to compile partial classes, therefore your generated code with direct references to Test will not compile even if you manage to overcome point #1

With the above in mind, I believe one option will be to basically have your generated code invoke reflection to obtain a reference to your desired method and invoke it dynamically (which kinda gets reduced to my suggestion #1):

using System;
using System.CodeDom.Compiler;
using System.Collections.Generic;
using System.Globalization;
using System.Reflection;
using System.Text;
using Microsoft.CSharp;

namespace ConsoleApp9
{
    public class MainTestClass
    {
        private void CallTest(string value) => Console.WriteLine($"CallTest({value})");

        public object OnMethodInvoke(string functionName, List<object> parameters)
        {
            if (functionName == "exec")
            {

                CSharpCodeProvider c = new CSharpCodeProvider();
                CompilerParameters cp = new CompilerParameters();
                cp.ReferencedAssemblies.Add("System.dll");
                cp.ReferencedAssemblies.Add("ConsoleApp9.exe"); // note a reference to your main assembly here
                cp.CompilerOptions = "/t:library";
                cp.GenerateInMemory = true;
                StringBuilder sb = new StringBuilder("");
                sb.Append("using System;\n");
                sb.Append("using System.Reflection;\n");
                sb.Append("using ConsoleApp9;\n"); // reference your main assembly ConsoleApp9. this is my test console app 
                sb.Append("namespace ScriptStack.Dynamic\n");
                sb.Append("{\n");
                sb.Append(" public class Code\n");
                sb.Append(" {\n");
                sb.Append("     private MainTestClass t;\n"); // reference your 
                sb.Append("     public Code(MainTestClass t) { this.t = t; }\n"); // we're going to capture reference to calling MainTestClass instance and use it for reflection
                sb.Append("     public object EvalCode()\n");
                sb.Append("     {\n");
                sb.Append("         // this is a very standard method of invoking private methods. see this SO answer: https://stackoverflow.com/a/135482/12339804 \n");
                sb.Append("         var mi = typeof(MainTestClass).GetMethod(\"CallTest\", BindingFlags.NonPublic | BindingFlags.Instance);\n");
                sb.Append("         return mi.Invoke(t, new object[]{ \"" + (string)parameters[0] + "\" });\n"); //suppose you want to pass a string to your generated C# source.
                sb.Append("     }\n");
                sb.Append(" }\n");
                sb.Append("}\n");
                var cr = c.CompileAssemblyFromSource(cp, sb.ToString());
                if (cr.Errors.Count > 0) return null;
                var a = cr.CompiledAssembly;
                object o = a.CreateInstance("ScriptStack.Dynamic.Code", false, BindingFlags.Default, null, new object[] { this }, CultureInfo.InvariantCulture, null); //we have to opt for a way more overloaded CreateInstance method due to us requiring to pass a this into the Code class we're instantiating
                var mi = o.GetType().GetMethod("EvalCode");
                return mi.Invoke(o, null);
            }
            throw new NotImplementedException();
        }
    }

    class Program
    {
        static void Main(string[] args)
        {
            var t = new MainTestClass();
            t.OnMethodInvoke("exec", new List<object> { "parameter 1" });
            Console.ReadKey();
        }
    }
}
timur
  • 14,239
  • 2
  • 11
  • 32