Dynamic compilation technology based on .NET6 in C#

A few days ago, I had to solve a dynamic calculation problem and tried different methods. The problem is that given a string containing calculations, the calculation results are obtained while the program is running. At that time, dynamic compilation was considered and I checked some information on the Internet to complete this function. However, the programming codes used on different .NET platforms were different. I didn’t use it because I found it troublesome. I used the three conventional methods, namely: using DataTable, using JavaScript, and using Excel table cell calculations.

It is worthwhile to understand this technology, because my project is based on .NET6, and dynamic compilation based on .NET6 is used to complete the dynamic compilation and result output of calculated strings.

 ⑴Solving citation problems

Modify project files while closing the project.

<Project Sdk="Microsoft.NET.Sdk">

  <PropertyGroup>
    <OutputType>WinExe</OutputType>
    <TargetFramework>net6.0-windows</TargetFramework>
    <Nullable>enable</Nullable>
    <UseWindowsForms>true</UseWindowsForms>
    <ImplicitUsings>enable</ImplicitUsings>
  </PropertyGroup>

<ItemGroup>
   <PackageReference Include="Microsoft.Net.Compilers" Version="3.12.0" PrivateAssets="all" />
   <PackageReference Include="Microsoft.Net.Compilers.Toolset" Version="3.12.0" PrivateAssets="all" />
</ItemGroup>

</Project>

Among them, the ItemGroup node and content are added.

After saving, open the project to write code.

⑵Add reference

using System;
using System.CodeDom.Compiler;
using System.Reflection;
using System.Text;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.Emit;

⑶Code writing

 private void button1_Click(object sender, EventArgs e)
        {
            string StrInputCode=textBox1.Text.Trim();
            string CompileCode = @"
                    using System;
                    public class Calculator
                    {
                        public static double CalculateResult()
                        {
                            double result = " + StrInputCode + @";
                            return result;
                        }
                    }";

            // Create a syntax tree that represents the structure and syntax in the code
            SyntaxTree syntaxTree = CSharpSyntaxTree.ParseText(CompileCode);

            //Created a C# compilation instance, defined compilation options, and added compilation references
            CSharpCompilation compilation = CSharpCompilation.Create("DynamicAssembly")
                .WithOptions(new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary))
                .AddReferences(MetadataReference.CreateFromFile(typeof(object).GetTypeInfo().Assembly.Location))
                .AddReferences(MetadataReference.CreateFromFile(typeof(Action<string>).GetTypeInfo().Assembly.Location)) // Add a reference to Action
                .AddReferences(MetadataReference.CreateFromFile(typeof(string).GetTypeInfo().Assembly.Location)) // Add a reference to string
                .AddSyntaxTrees(syntaxTree);

            // Compile code
            using (MemoryStream ms = new MemoryStream())
            {
                //Use the compilation.Emit method to compile dynamically generated code. CompileResult contains the compilation results.
                EmitResult CompileResult = compilation.Emit(ms);

                if (CompileResult.Success)
                {
                    ms.Seek(0, SeekOrigin.Begin);
                    //Use the Assembly.Load method to load the compiled assembly.
                    Assembly assembly = Assembly.Load(ms.ToArray());
                    //Get type information
                    Type type = assembly.GetType("Calculator");
                    //Get method information
                    MethodInfo method = type.GetMethod("CalculateResult");
                    //Get calculation results
                    double result1 = (double)method.Invoke(null, null);
                    // Output the calculation results to TextBox2
                    OutputStr(result1.ToString());
                }
                else
                {
                    string StrFalse="";
                    foreach (Diagnostic diagnostic in CompileResult.Diagnostics)
                    {
                        StrFalse + = diagnostic.ToString();
                    }
                    //Output compilation error message
                    textBox2.Text = StrFalse;
                }
            }

        }


        private void OutputStr(string text)
        {
            
            if (textBox2.InvokeRequired)
            {
                textBox2.Invoke((MethodInvoker)delegate { textBox2.Text = text; });
            }
            else
            {
                textBox2.Text = text;
            }
        }

Although the correct result can be obtained, there may be result errors because double-precision variables are used to receive the results. For example, if you input 1 + 3-2.2, the correct result should be 1.8, but the actual output is 1.7999999999999998; in addition, the compilation speed is not ideal. , because the amount of operations involved in the program is relatively large, this is very problematic.

Because of this, I was worried about calculation bias, so I did not use this technology in the program. It was safer to use DataTable.

The above program can also be modified to meet more needs:

Get the calculation formula and define the user method:

 string StrInputCode =textBox1.Text.Trim();
            string CompileCode = @"
                using System;

                public class UserClass
                {
                    public static void UserMethod(Action<string> OutputStr)
                    {
                        double Result=" + StrInputCode + @";
                        string StrResult=Result.ToString();
                        OutputStr(StrResult);
                    }
                }";

Get the output after successful compilation:

 ms.Seek(0, SeekOrigin.Begin);
                    Assembly assembly = Assembly.Load(ms.ToArray());
                    Type type = assembly.GetType("UserClass");
                    MethodInfo method = type.GetMethod("UserMethod", new Type[] { typeof(Action<string>) });
                    method.Invoke(null, new object[] { new Action<string>(OutputStr) });

The program can also run normally and obtain correct results.

I originally wanted to use this technology to deal with some subsequent demand changes, but the implementation is still not ideal. You can also use other methods to deal with demand changes, such as dependency injection or using delegation to define methods and parameters and compile these methods into a DLL. , you only need to modify the method code and then compile it.