C#中基于.NET6的动态编译技术

  前几天要解决动态计算问题,尝试着使用了不同的方法。问题是给定一个包含计算的字符串,在程序运行中得到计算结果,当时考虑了动态编译,在网上查了一些资料完成了这项功能,可是基于不同的.NET平台使用的编程代码相差比较大,觉得麻烦就没有使用,用了常规的三种方法,分别是:使用DataTable、使用JavaScript、使用Excel表单元格的计算。

  了解这项技术还是值得的,因为我的项目基于.NET6,也就使用了基于.NET6的动态编译来完成计算字符串的动态编译和结果输出。

**  ⑴解决引用问题**

  在关闭项目的情况下修改项目文件。

复制代码
<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>

  其中ItemGroup节点及内容是添加的。

  保存后再打开项目进行代码编写。

 ** ⑵添加引用**

复制代码
using System;
using System.CodeDom.Compiler;
using System.Reflection;
using System.Text;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.Emit;

**  ⑶代码编写**

复制代码
        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;
                        }
                    }";

            // 创建表示代码中的结构和语法的语法树
            SyntaxTree syntaxTree = CSharpSyntaxTree.ParseText(CompileCode);

            // 创建了一个C#编译实例,定义编译选项,添加编译引用
            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)) // 添加对Action的引用
                .AddReferences(MetadataReference.CreateFromFile(typeof(string).GetTypeInfo().Assembly.Location)) // 添加对string的引用
                .AddSyntaxTrees(syntaxTree);

            // 编译代码
            using (MemoryStream ms = new MemoryStream())
            {
                //使用compilation.Emit方法对动态生成的代码进行编译。CompileResult包含编译结果。
                EmitResult CompileResult = compilation.Emit(ms);

                if (CompileResult.Success)
                {
                    ms.Seek(0, SeekOrigin.Begin);
                    //使用Assembly.Load方法加载编译后的程序集。
                    Assembly assembly = Assembly.Load(ms.ToArray());
                    //得到类型信息
                    Type type = assembly.GetType("Calculator");
                    //得到方法信息
                    MethodInfo method = type.GetMethod("CalculateResult");
                    //获取计算结果
                    double result1 = (double)method.Invoke(null, null); 
                    // 将计算结果输出到TextBox2中
                    OutputStr(result1.ToString()); 
                }
                else
                {
                    string StrFalse="";
                    foreach (Diagnostic diagnostic in CompileResult.Diagnostics)
                    {
                        StrFalse+= diagnostic.ToString();
                    }
                    //输出编译错误信息
                    textBox2.Text = StrFalse;
                }
            }

        }


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

  虽然可以得到正确的结果,但是因为使用的是双精度变量接收结果可能出现结果误差,比如输入1+3-2.2,正确结果应该是1.8,实际输出却是1.7999999999999998;另外,编译的速度也不理想,因为程序中参与运算的量比较大,这一点很成问题了。

  也因为如此,担心计算偏差,在程序中我没有使用这项技术,使用DataTable比较稳妥。

  上面的程序也可以修改,以便完成更多的需求:

  获取计算公式并定义用户方法:

复制代码
            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);
                    }
                }";

  在编译成功后获取输出:

复制代码
                    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) });

  程序也可以正常运行并获取正确结果。

  本来是想通过这项技术应对一些后面的需求变更,但是实现起来还是不理想,应对需求变更也可以使用其他的方法,比如依赖注入或者使用委托定义好方法和参数并将这些方法编译到一个DLL中,后面只需要修改方法代码再编译就可以了。

相关推荐
软件黑马王子6 小时前
20.Resources资源加载:基本实现
开发语言·前端框架·c#
张人玉7 小时前
基于 C# / .NET 8 + Windows Forms + SQLite 的桌面银行业务演示系统——华夏示范银行管理系统
windows·sqlite·c#·.net
1314lay_10077 小时前
C#调用Sql Server存储过程,并且使用传入表结构
数据库·经验分享·笔记·sqlserver·c#
慧都小妮子7 小时前
.NET 报表控件选型对比:Stimulsoft 与 FastReport 怎么选
c#·.net·报表开发·数据可视化·报表控件·报表引擎选型
1314lay_10079 小时前
C#调用Sql Server存储过程,有返回值的
数据库·sqlserver·c#
我是唐青枫9 小时前
C#.NET PInvoke 深入解析:封送、内存布局与原生互操作实战
开发语言·c#·.net
Am-Chestnuts9 小时前
AI 生成的数学公式,怎么用 DS随心转 导出成 Word 里能改的公式
人工智能·c#·word
geovindu10 小时前
CSharp: Strategy Pattern
开发语言·后端·c#·.net·.netcore·策略模式·行为模式
古少侠10 小时前
AI 内容粘到 Word 就乱?用 DS随心转 把复制的内容直接变成排版好的一段
人工智能·c#·word
geovindu11 小时前
CSharp: Task Scheduler
开发语言·后端·c#·.net·.netcore