0

I want to load a class form a .cs file and use it in another code.
Assume I have a .cs file which contains code like that:

//some imports

public class Commands
{

     //a lot of commands

}

What I am trying is to load this class from a file using CSharpCodeProvider or whatever and create a list of Commands.

A piece of code from a console app.

list<Commands> lst;

The question is how can I load Commands class dynamically (at runtime) (without restarting the console app or starting VS) and create the list of Commands?

Al Kepp
  • 5,831
  • 2
  • 28
  • 48
  • 1
    Note that this is an extreme security risk. – Al Kepp Jun 05 '18 at 20:35
  • 2
    Regardless of whether it's a good idea or not (and we don't know the environment it's in) it is an interesting question and I wish people wouldn't downvote and run without explaining what's WRONG with the question. – Jim W Jun 05 '18 at 20:44
  • What is a "command" supposed to be? It seems to me like you could just define your `Commands` class and then just deserialize some json or whatever into your list. Of course if your `Commands` are actually something fluid/dynamic in behavior, this obviously won't work. – Broots Waymb Jun 05 '18 at 20:46
  • Relevant? https://stackoverflow.com/questions/826398/is-it-possible-to-dynamically-compile-and-execute-c-sharp-code-fragments – BJ Myers Jun 05 '18 at 20:47
  • what exactly do you mean when you say "a lot of commands"? What's a command? – Andrei Dragotoniu Jun 05 '18 at 20:53

3 Answers3

7

Try this example, which I have put together and tested:

Build program.cs as a .Net Framework Console App in e.g. Visual Studio.

// program.cs
using System;
using System.IO;
using System.CodeDom.Compiler;
using System.Reflection;

namespace RuntimeCompile
{
    class Program
    {
        static void Main(string[] args)
        {
            // Get a path to the file(s) to compile.
            FileInfo sourceFile = new FileInfo("mySource.cs");
            Console.WriteLine("Loading file: " + sourceFile.Exists);

            // Prepary a file path for the compiled library.
            string outputName = string.Format(@"{0}\{1}.dll",
                Environment.CurrentDirectory,
                Path.GetFileNameWithoutExtension(sourceFile.Name));

            // Compile the code as a dynamic-link library.
            bool success = Compile(sourceFile, new CompilerParameters()
            {
                GenerateExecutable = false, // compile as library (dll)
                OutputAssembly = outputName,
                GenerateInMemory = false, // as a physical file
            });

            if (success)
            {
                // Load the compiled library.
                Assembly assembly = Assembly.LoadFrom(outputName);

                // Now, since we didn't have reference to the library when building
                // the RuntimeCompile program, we can use reflection to create 
                // and use the dynamically created objects.
                Type commandType = assembly.GetType("Command");

                // Create an instance of the loaded class from its type information.
                object commandInstance = Activator.CreateInstance(commandType);

                // Invoke the method by name.
                MethodInfo sayHelloMethod = commandType.GetMethod("SayHello", BindingFlags.Public | BindingFlags.Instance);
                sayHelloMethod.Invoke(commandInstance, null); // no arguments, no return type
            }

            Console.WriteLine("Press any key to exit...");
            Console.Read();
        }

        private static bool Compile(FileInfo sourceFile, CompilerParameters options)
        {
            CodeDomProvider provider = CodeDomProvider.CreateProvider("CSharp");

            CompilerResults results = provider.CompileAssemblyFromFile(options, sourceFile.FullName);

            if (results.Errors.Count > 0)
            {
                Console.WriteLine("Errors building {0} into {1}", sourceFile.Name, results.PathToAssembly);
                foreach (CompilerError error in results.Errors)
                {
                    Console.WriteLine("  {0}", error.ToString());
                    Console.WriteLine();
                }
                return false;
            }
            else
            {
                Console.WriteLine("Source {0} built into {1} successfully.", sourceFile.Name, results.PathToAssembly);
                return true;
            }
        }
    }
}

In the output directory (bin), next to the console app executable place a text file named mySource.cs with this content:

// mySource.cs
using System;

internal class Program
{
    static void Main()
    {
        Console.WriteLine("Hello from mySource!");
        Console.ReadLine();
    }
}

public class Command
{
    public void SayHello()
    {
        Console.WriteLine("Hello (Command)");
    }
}

Then run the first console app and observe it's output. It should log "Hello (Command)", showing that the code was correctly compiled, loaded and executed.

The example shows how to use the CodeDom.Compiler to compile a cs-file at runtime and then load it as dll to run code within it. Be aware, that almost no error handling was implemented.

This should answer the question, but there may still be better approaches to handling your use-case. In case of plugin loading it makes sense to use interfaces which are added as a reference to both assemblies to avoid the use of reflection, etc.

Xarbrough
  • 1,393
  • 13
  • 23
1

There is probably a better way to achieve your overall goal, like dependency injection.

However, you can do it with the ICodeCompiler.

See this article https://support.microsoft.com/en-ca/help/304655/how-to-programmatically-compile-code-using-c-compiler

Jim W
  • 4,866
  • 1
  • 27
  • 43
-2

To load the c# class from another c# class you need to use "using"

using Commands;
public class class1
{
    private list<Commands>lst;
    //...
}
Omer Eli
  • 19
  • 7