lundi 24 décembre 2018

Creating dynamically executing code with custom classes and methods using reflection and run-time compilation in C#

A little background:

My goal is to create an API in C# that takes two inputs:

  1. Two custom classes that are located in a separate .cs file and are being compiled within a certain project. That project creates, modifies and generally performs any kind of manipulation with the objects of those classes. There are only two classes because one is responsible for the custom method (see below) input, and the other one - for it's output
  2. Custom methods that operate with objects of the classes mentioned above. These methods produce a certain result - an object of the class responsible for the output.

And produces one output:

  1. An object of the class responsible for the output.

Here's an example of my IO classes:

namespace DummyIOClasses
{
    #region IO_CLASSES
    public class InputClass
    {
        public InputClass(List<double> vals)
        {
            tubeValues = new List<double>();

            foreach (double val in vals)
            {
                tubeValues.Add(val);
            }
        }

        public List<double> tubeValues;
    }

    public class OutputClass
    {
        public OutputClass()
        {
            warningsCards = new List<string>();
            warningsTubes = new List<string>();
            result = "N/A";
        }

        public List<string> warningsCards;
        public List<string> warningsTubes;

        public string result;
    }
    #endregion
}

And here's an example of a custom method:

public OutputClass GetOutput(InputClass input)
{
    OutputClass output = new OutputClass();

    double sum = 0;

    foreach (double val in input.tubeValues)
    {
        if (val % 2 == 0)
        {
            output.warningsCards.Add("Divisible by 2!");
        }

        if (val % 3 == 0)
        {
            output.warningsTubes.Add("Divisible by 3!");
        }

        sum += val;
    }

    output.result = sum.ToString();

    return output;
}

My API will need to be able to load different methods that operate with the same set of IO classes. But, whenever I want to switch to another set of IO classes and use different custom methods that were made for that new set, I should be able to do that through code at run-time.

This seems like an advanced this to implement, and I am still pretty new to C#... Unfortunately, I don't even know where to start or what to do in order to achieve my goal.

So my question is: How would one implement an API like that? I am not asking for a solution, I am asking the more experienced people to guide me in a right way. What tools of C# should I use for this task and how?

Progress so far

What I've done far is implemented a class that takes a dummy .cs file with a bunch of placeholders for code that will be pasted in the future. It looks like this:

ASSEMBLY_PASTE

namespace ScriptInterpreter
{
    CLASS_PASTE

    public class Interpreter
    {
        METHOD_PASTE
    }
}

There are 3 placeholders, as you can see. One for the `using xxxxxxx;' statements, one for the custom IO classes, and one for the custom method that works with objects of those classes.

The first placeholder is simple, I populate a List<string> different using statements at run-time and then call a method that replaces the placeholder with those statements, pretty straight forward.

As for the seconds placeholder, I copy the code for the IO classes from a separate projects .cs file (the code is wrapped in certain a #region to make it easier) and paste them in place of that placeholder.

The third placeholder is even simpler, I have a .cs file that is never compiled and is simply copied in the build folder, it has the custom method that works with the classes from my project. So i just copy-paste the whole file in place of a placeholder.

After all these manipulations the result file looks like this:

using System;
using System.Collections.Generic;

namespace ScriptInterpreter
{
    public class InputClass
    {
        public InputClass(List<double> vals)
        {
            tubeValues = new List<double>();

            foreach (double val in vals)
            {
                tubeValues.Add(val);
            }
        }

        public List<double> tubeValues;
    }

    public class OutputClass
    {
        public OutputClass()
        {
            warningsCards = new List<string>();
            warningsTubes = new List<string>();
            result = "N/A";
        }

        public List<string> warningsCards;
        public List<string> warningsTubes;

        public string result;
    }

    public class Interpreter
    {
        public OutputClass GetOutput(InputClass input)
        {
            OutputClass output = new OutputClass();

            double sum = 0;

            foreach (double val in input.tubeValues)
            {
                if (val % 2 == 0)
                {
                    output.warningsCards.Add("Divisible by 2!");
                }

                if (val % 3 == 0)
                {
                    output.warningsTubes.Add("Divisible by 3!");
                }

                sum += val;
            }

            output.result = sum.ToString();

            return output;
        }
    }
}

I then take that file, put it into a string, and use the CSharpCodeProvier to compile an assembly from it.

Once I have the assembly, I simply call the GetOutput method through it with the object of the custom class that I've been using. Like so:

public object ExecuteMethod(object input)
{
    object compiledAssembly = results.CompiledAssembly.CreateInstance("ScriptInterpreter.Interpreter");

    MethodInfo methodInfo = compiledAssembly.GetType().GetMethod("GetOutput");

    object output = methodInfo.Invoke(compiledAssembly, new object[] { input });

    return output;
}

But here's the problem! The types are different! Although their fields and methods are identical, in C# they are different because they are located in different assemblies and namespaces!

So, this is where I'm stuck right now... I don't really know how to inform these two assemblies of the existence of each other or.. you know... make the thing work! I know there might be a hacky solution, like, converting all data in a class into a string and the pass that string back and forth passing and to create objects, but that's the last thing I want to do to be honest... I think there must be a better way of solving this problem!

If it possible!

I would really appreciate your help with the stuff I already have. Because this is exactly the API I was planning on making in the first place...

If you're really interested in that, here's the full code of my Interpreter class.

And sample usage, along with IO Classes from some project and the custom method for those classes.





Aucun commentaire:

Enregistrer un commentaire