Dynamically load, compile, run, and interact with C# code

Introduction

Today, we will look into how we can dynamically load C# code into our solution, compile this code, run it, and then interact with it to call methods, etc. This might be useful when we want to add plugin code etc.

Create the C# code

The first step is to create some C# code which we will be dynamically loading into our solution. For this, create a simple text file and place it on your machine. Add the below code to it.

using System;
public class LibClass
{
    public void LibMethod()
    {
        Console.WriteLine("Hello, from code library!");
    }
}

Create the C# solution

Next, create a console application in Visual Studio 2022 using the latest .NET 7. The solution looks like the below:

Dynamically load, compile, run, and interact with C# code

Now, add the below code to the “Program.cs” file:

using System.Reflection;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;

public static class Program
{
    public static void Main()
    {
        LibraryRunner.RunLibraryCodeFromFile("C:\\Temp\\code.txt");
    }
}


public static class LibraryRunner
{
    public static void RunLibraryCodeFromFile(string filePath)
    {
        string code = File.ReadAllText(filePath);

        // Set up the compilation options
        var compilationOptions = new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary);

        // Add necessary references
        var references = new[]
        {
            MetadataReference.CreateFromFile(typeof(object).Assembly.Location),
            MetadataReference.CreateFromFile(typeof(Console).Assembly.Location),
            MetadataReference.CreateFromFile(typeof(System.Runtime.AssemblyTargetedPatchBandAttribute).Assembly.Location),
            MetadataReference.CreateFromFile(Assembly.Load("System.Runtime").Location)
        };

        // Compile the code
        var syntaxTree = SyntaxFactory.ParseSyntaxTree(code);
        var compilation = CSharpCompilation.Create("LibraryAssembly")
            .WithOptions(compilationOptions)
            .AddReferences(references)
            .AddSyntaxTrees(syntaxTree);

        using var ms = new MemoryStream();
        var emitResult = compilation.Emit(ms);

        if (!emitResult.Success)
        {
            // Handle compilation errors
            foreach (var diagnostic in emitResult.Diagnostics)
            {
                Console.WriteLine(diagnostic.ToString());
            }
        }
        else
        {
            ms.Seek(0, SeekOrigin.Begin);

            // Load the compiled assembly
            var assembly = Assembly.Load(ms.ToArray());

            // Execute the library code
            var libraryClassType = assembly.GetType("LibClass");
            var libraryInstance = Activator.CreateInstance(libraryClassType);

            var libraryMethod = libraryClassType.GetMethod("LibMethod");
            libraryMethod.Invoke(libraryInstance, null);
        }
    }
}

The code is quite simple. We load the code from a text file. Then we set the values for the main library that does all the work. This is the “CSharpCompilation” library. We set the value for compilation options. Here, we are generating a dynamic linked library. We then set any references that may be required via the references array. We then set the syntax tree by passing in the code we loaded to be parsed. After that the “CSharpCompilation” class is called with these options and the library is created. In this example we do not save it as a DLL to disk. We keep it in a memory stream and load it from there. After that we select the class and method using reflection and run the method.

Dynamically load, compile, run, and interact with C# code

The complete code for this example can be found at the below:

https://github.com

Summary

In today’s article we looked into how we can load C# code dynamically, compile it, run it, and finally interact with it. This might be useful when we want to add plug-in type code which is developed by external teams or something we want to change on the fly without having to re-compile the complete main application. 


Similar Articles