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

news/2024/11/30 15:30:16/

  前几天要解决动态计算问题,尝试着使用了不同的方法。问题是给定一个包含计算的字符串,在程序运行中得到计算结果,当时考虑了动态编译,在网上查了一些资料完成了这项功能,可是基于不同的.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中,后面只需要修改方法代码再编译就可以了。


http://www.ppmy.cn/news/1205505.html

相关文章

博捷芯BJCORE:划片机在划切工艺中需要注意以下几点

划片机在划切工艺中需要注意以下几点&#xff1a; 1. 测高时工作台上不能有任何物品&#xff0c;以免影响测高精度。 2. 切割前检查参数是否正确选择&#xff0c;包括切割速度、切割深度等。 3. 更换刀片时&#xff0c;检查刀片是否平稳旋转&#xff0c;确保刀片安装牢固。 …

【Java 进阶篇】JSTL 详解

Java JSTL&#xff08;JavaServer Pages Standard Tag Library&#xff09;是用于简化在 JSP 页面上的开发工作的 Java 标签库。它提供了在 JSP 页面上使用的标准标签&#xff0c;可以帮助开发人员更轻松地访问和操作数据&#xff0c;而无需编写大量的 Java 代码。Java JSTL 是…

各种位置编码

目录 一、绝对位置编码 1.1 训练式 1.2 三角式 二、相对位置编码 三、旋转式位置编码 &#xff08;Rotary Position Embedding) 四、Alibi 位置编码&#xff08;Attention with Linear Biases&#xff09; 五、T5 Bias Position Embedding 六、KERPLE(Kernelized Relati…

egg.js sequelize数据库操作配置

egg.js sequelize数据库操作配置 文章目录 egg.js sequelize数据库操作配置1. 数据库配置2. 迁移配置3.数据表设计和迁移4.模型创建 1. 数据库配置 安装并配置egg-sequelize插件&#xff08;它会辅助我们将定义好的 Model 对象加载到 app 和 ctx 上&#xff09;和mysql2模块&a…

C/C++轻量级并发TCP服务器框架Zinx-游戏服务器开发006:基于redis查找玩家姓名+游戏业务实现总结

文章目录 1 Redis的安装与API的使用1.1 安装目录及环境变量1.2 设置远程客户端连接和守护进程1.3 启动redis1.4 Hiredis API的使用1.5 我的动态库和头文件 2 Redis的使用2.1 初始化时候2.2 结束的时候 3 测试4 Makefile5 游戏业务总结 1 Redis的安装与API的使用 1.1 安装目录及…

设计模式--开篇

什么是设计模式 设计模式是软件开发过程中面临的通用问题的解决方案。 使用设计模式是为了可重用代码、让代码更容易被他人理解、保证代码可靠性 按使用目的分类 创建型–主要用于创建对象 单例模式-某个类只能有一个实例&#xff0c;提供一个全局的访问点工厂方法模式-创建…

Linux系统生成免密码登录,保姆级教程

1、节点规划&#xff0c;我们这里为了简单&#xff0c;就直接采用root账号&#xff0c;生产中&#xff0c;需要创建专门的用户组和用户。 节点名称用户用户组密码ipnode1rootroot123456192.168.42.139node2rootroot123456192.168.42.140node3rootroot123456192.168.42.141 2、…

安卓 车轮视图 WheelView kotlin

安卓 车轮视图 WheelView kotlin 前言一、代码解析1.初始化2.初始化数据3.onMeasure4.onDraw5.onTouchEvent6.其他 6.ItemObject二、完整代码总结 前言 有个需求涉及到类似这个视图&#xff0c;于是在网上找了个轮子&#xff0c;自己改吧改吧用&#xff0c;拿来主义当然后&…