C#脚本化(Roslyn):如何在运行时引入nuget包
假设我们开发了一个C#脚本编辑器,利用Roslyn去执行用户所编写的脚本。这时候,如果用户想要引用一个nuget包,应该如何实现呢?
我们想要引用nuget包的话,只要能得到nuget包及其依赖包的所有程序集和资源文件就可以了。如何引用程序集,可以看这一篇:使用Roslyn脚本化C#时如何调用不包含在运行时中的程序集
朴素思路
一种朴素的思路是:下载所需的nuget包,然后从nuget包中解压出程序集和其他资源文件,再遍历nuget包的所有依赖,执行同样的操作,最后得到程序集,就可以让Roslyn来引用它们了。
这个方法思路简单清晰,但同时,操作起来也比较复杂,要处理好两个问题:
- nuget包的所有输出都要放到正确的目录下
- nuget包的依赖不能有遗漏
另一种解法
在引用nuget包来编译项目时,nuget相关的程序集都会输出到输出目录下,基于这一点,对本文所述的问题就有另一种解法。这个方法的主要思路是,创建一个空的C#项目,然后在这个项目中引用在C#脚本中需要调用的nuget包,再用dotnet.exe去编译这个项目,那么nuget包及其依赖项所包含的所有程序集和资源文件都会输出到输出目录下面去,那么我们在这个目录下面去执行C#脚本,自然就能够找到所有所需的程序集和资源文件了。
下面我们来详细说明一下这个方法的具体流程。
第一步 创建辅助C#项目
我们需要一个空的C#项目,来辅助我们获取我们所需的nuget包相关的文件。
首先根据csproj文件的模板创建一个新的csproj文件,通过PackageReference标签引用我们想要引入的nuget包。OutputType设置为Exe,这样被引用的nuget的程序集才会输出到目录。
<Project Sdk="Microsoft.NET.Sdk">
<OutputType>Exe</OutputType>
...
<ItemGroup>
<PackageReference Include="newtonsoft.json" Version="13.0.3" />
</ItemGroup>
...
</Project>
然后,像普通项目一样,添加一个Program.cs文件,在文件里写一些简单的代码,注意不要引入其他不必要的包。例如:
using System;
namespace Application
{
class Program
{
static void Main(string[] args)
{
_ = 0;
}
}
}
如果我们选择的.NET和C#版本支持顶级语句和global using特性,那么也可以只写
_ = 0;
第二步 调用dotnet编译
我们可以使用dotnet.exe去编译这个辅助项目,命名如下
dotnet build <csproj file path>
编译成功后,输出目录下面就能找到nuget包输出的程序集和资源文件,下面以一个既有依赖包,又有资源文件的nuget包为例,可以看到,输出目录下包含了nuget包本身的程序集、资源文件,以及依赖包的程序集和资源文件。

我们得到了这些程序集,就可以引用它们了,效果跟我们引用nuget包一样。
代码实战
下面用一个简化版的代码来验证这一方法,在demo中我们通过Rosly来执行一段使用Newtonsoft.Json来执行序列化的C#脚本。
using System.Diagnostics;
using System.Text;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp.Scripting;
using Microsoft.CodeAnalysis.Scripting;
namespace ConsoleApp1
{
internal class Program
{
static void Main(string[] args)
{
var refs = LoadNuget("Newtonsoft.Json", "13.0.3").GetAwaiter().GetResult();
Console.WriteLine($"Reference assembly:\n");
foreach (var @ref in refs)
{
Console.WriteLine(@ref);
}
Console.WriteLine("\n");
var code2 = @"
using Newtonsoft.Json;
record Person(string Name, int Age);
var p = new Person(""Jack"", 19);
var str = JsonConvert.SerializeObject(p);
System.Console.WriteLine(str);
";
Console.WriteLine("Script result:\n");
try
{
var metas = refs.Select(r => MetadataReference.CreateFromFile(r));
var options = ScriptOptions.Default.AddReferences(metas);
CSharpScript.Create(code2, options).RunAsync().GetAwaiter().GetResult();
}
catch (Exception ex)
{
Console.WriteLine(ex);
throw;
}
}
public static async Task<IEnumerable<string>> LoadNuget(string package_id, string version)
{
Directory.CreateDirectory(".restore");
//创建dotnet控制台项目
await Process.Start(@"C:\Program Files\dotnet\dotnet.exe", "new console -o .restore\\program").WaitForExitAsync();
//添加nuget包引用
await Process.Start(@"C:\Program Files\dotnet\dotnet.exe", $"add .restore\\program package {package_id} -v {version}").WaitForExitAsync();
//编译
await Process.Start(@"C:\Program Files\dotnet\dotnet.exe", "build .restore\\program --interactive --nologo -o .restore\\program\\bin").WaitForExitAsync();
//找出输出路径下的dll
var dlls = Directory.GetFiles(Path.Combine(Directory.GetCurrentDirectory(), ".restore\\program\\bin"), "*.dll");
return dlls.Where(d => !Path.GetFileName(d).ToLower().Equals("program.dll"));
}
}
}
输出结果如下:
Reference assembly:
D:\Projects\CSharp Projects\RebootAndScript\ConsoleApp1\bin\Debug\net8.0\.restore\program\bin\Newtonsoft.Json.dll
Script result:
{"Name":"Jack","Age":19}
C#脚本化(Roslyn):如何在运行时引入nuget包的更多相关文章
- 深入理解脚本化CSS系列第四篇——脚本化样式表
× 目录 [1]CSSStyleSheet [2]CSSRule 前面的话 关于脚本化CSS,查询样式时,查询的是计算样式:设置单个样式时,设置的是行间样式:设置多个样式时,设置的是CSS类名.脚本化 ...
- Spark on Yarn运行时加载的jar包
spark on yarn运行时会加载的jar包有如下: spark-submit中指定的--jars $SPARK_HOME/jars下的jar包 yarn提供的jar包 spark-submit通 ...
- iNeuOS工业互联网操作系统,脚本化实现设备运行时长和效率计算与统计
目 录 1. 概述... 2 2. 实时采集开停状态... 2 3. 增加虚拟设备... 2 4. 脚本统计和计算设备运行时长... 4 5. ...
- 使用Roslyn脚本化C#代码,C#动态脚本实现方案
[前言] Roslyn 是微软公司开源的 .NET 编译器. 编译器支持 C# 和 Visual Basic 代码编译,并提供丰富的代码分析 API. Roslyn不仅仅可以直接编译输出,难能可贵的就 ...
- JavaScript 客户端JavaScript之 脚本化浏览器窗口
1.计时器 客户端Javascript以全局函数setTimeOut().clearTimeOut().setInterval().clearInterval()提供这一功能. 前者是从运行的那一 ...
- JavaScript 客户端JavaScript之 脚本化文档
客户端JavaScript的存在把静态HTML转变为交互式的Web应用程序,脚本化Web页面的内容正是JavaScript存在的理由. 一个文档对象模型或者说DOM就是一个API,它定义了如何访问 ...
- 权威指南之脚本化jquery
jqury函数 jquery()($())有4种不同的调用方式 第一种是最常用的调用方式是传递css选择器(字符串)给$()方法.当通过这种方式调用时,$()方法会返回当前文档中匹配该选择器的元素集. ...
- Javascript学习7 - 脚本化浏览器窗口
原文:Javascript学习7 - 脚本化浏览器窗口 本节讨论了文档对象模型.客户端Javascript下Window中的各项属性,包括计时器.Location对象.Histroy对象.窗口.浏览器 ...
- Java 脚本化编程指南
Java 脚本化编程指南 Java脚本化API为谁准备? 脚本语言的一些有用的特性是: 方便:大多数脚本语言都是动态类型的.您通常可以创建新的变量,而不声明变量类型,并且您可以重用变量来存储不同类型的 ...
- JavaScript权威指南--脚本化CSS
知识要点 客户端javascript程序员对CSS感兴趣的是因为样式可以通过脚本编程.脚本化css启用了一系列有趣的视觉效果.例如:可以创建动画让文档从右侧“滑入”.创造这些效果的javascript ...
随机推荐
- 洛谷P4198 楼房重建 题解
Part1.自己一开始是怎么想的 我一开始的想法是先考虑什么情况下是看不见的. 如果是 \(i < j\) 的话可以直接看 \(j\) 的斜率和 \(i\) 的斜率就是比较 \(\frac{h_ ...
- 成都信息工程大学天梯赛 L2-2 不要刁难我们了
赛时板子没写对,原因就是标记数组的位置放错了,放到了while外面,导致这样距离数组是不会更新的,板子还是要自己多写少看 #include <bits/stdc++.h> #define ...
- jmeter从csv文件中取数据设置变量的方法
从csv取数据是参数化方法之一 首先,CSV数据文件设置,选择数据文件,点击http请求,右键-添加-配置元件-csv data set config,添加CSV数据文件设置 添加后可对设置名称进行修 ...
- 暂时永久免费高配云服务可跑32b模型
谷歌IDX免费云主机,16核CPU,64G内存,300G硬盘! 需要谷歌账号一个,且能google,无需绑卡. 到手第一时间安装一个ollama+qwen2.5-coder:32b, 无限cursor ...
- 题解:UVA12517 Digit Sum
数位 dp 模板题-- 题目大意 给你两个数 l,rl,rl,r,求出 l,rl,rl,r 之间所有数的数位和. 什么是数位和? 举个栗子,有一个数 123123123,那么他的数位和,即各个数位的和 ...
- hadoop部署安装(三)zookeeper+yarn
1. 配置zookeeper 3.1 解压存放指定目录 [root@bogon src]# tar xf zookeeper-3.4.10.tar.gz [root@bogon src]# mv zo ...
- ragflow k8s部署详细过程
一.概述 ragflow官方提供的安装方式是docker-compose方式部署的,单机运行. k8s部署方式,暂未提供. 不过我们可以通过工具,结合docker-compose.yaml,来推演出对 ...
- Flutter制作桌面图标长按展示操作项面板
@charset "UTF-8"; .markdown-body { line-height: 1.75; font-weight: 400; font-size: 15px; o ...
- C#之引用局部变量语法(ref local)
ref Particle p = ref _particles[i]; 是 C# 的引用局部变量语法(ref local),它的作用是直接引用数组中某一项的内存地址,而不是创建该元素的副本.这在性能敏 ...
- 取余(rem)和取模(mod)的区别
设 A rem B || A mod B 生成机制 取余:采取fix()函数,向0方向取整 取模:采取floor()函数,向无穷小方向取整 当A,B异号时(其实同号也是这个规律-) 取余:结果和A同号 ...