前言:

在本部分中,我们将深入探讨如何使用C# Emit 来创建动态程序集。

动态程序集是在运行时生成的,为我们提供了一种灵活、动态地构建和加载程序集的能力。

1. 程序集的概念

程序集是.NET中的基本部署单位,它包含了可执行代码、资源、元数据等信息,是.NET应用程序的基本组成单元之一。

与静态程序集(即程序集持久化)不同,动态程序集是在运行时生成的,使得我们可以根据需要动态地构建和加载程序集。

动态程序集的重要性体现在多个方面,包括但不限于模块化、版本控制、代码复用等。

通过动态程序集,我们可以更好地管理和组织代码,实现更灵活的程序设计和开发。

2、Emit 构建程序集支持情况:

.Net 系列:动态程序集:支持;程序集持久化:支持。

.Net Core 系列:动态程序集:支持;程序集持久化:不支持
 

3、Emit 创建动态程序集:

在C#中,操作程序集的核心类是:AssemblyBuilder。

在这过程,我们可以使用  AssemblyName 来定义程序集的名称和版本等信息。

AssemblyName assemblyName = new AssemblyName("MyDllName") { Version = new Version("1.0.0.0") };

创建程序集对象,在 .Net 和 .Net Core 中有所不同:

.NET 代码:通过 AppDomain.DefineDynamicAssembly 来创建动态程序集对象:

AssemblyBuilder assemblyBuilder = AppDomain.CurrentDomain.DefineDynamicAssembly(assemblyName, AssemblyBuilderAccess.Run);

AssemblyBuilderAccess 在 .Net 系列中的属性:

AssemblyBuilderAccess.Run; 表示程序集可被执行,但不能被保存。  
AssemblyBuilderAccess.Save; 表示程序集可被保存,但不能被执行。  
AssemblyBuilderAccess.RunAndSave; 表示程序集可被保存并能被执行。
AssemblyBuilderAccess.ReflectionOnly; 表示程序集只能用于反射上下文环境中,不能被执行。 
AssemblyBuilderAccess.RunAndCollect; 表示程序集可以被卸载并且内存会被回收

.NET Core 代码:通过 AssemblyBuilder.DefineDynamicAssembly 来创建动态程序集对象:

AssemblyBuilder ab = AssemblyBuilder.DefineDynamicAssembly(assemblyName, AssemblyBuilderAccess.Run);

AssemblyBuilderAccess 在 .Net Core 系列中的属性:

AssemblyBuilderAccess.Run; 表示程序集可被执行,但不能被保存。  
AssemblyBuilderAccess.Collect; 表示程序集可以被卸载并且内存会被回收
 

可以看到,目前版本是不支持持久化功能。

4:总结一:

在本章节中,我们讨论了如何使用 C# Emit 来构建动态程序集,以获得 AssemblyBuilder 这个程序集构建器,开启构建程序集的第一步。

在之后的文章,我们会讲解如何用它的 DefineDynamicModule 来定义模块,以之后构建类、成员、方法等,来实现构建完整的程序集。

当然,对于构建动态程序集,本文章到此会显的有点简单,为了避免被大伙说文章太水。

于是,打算追加一下对于构建静态程序集(即持久化程序集)的相关知识。

构建静态程序集的三种方式:

.Net 系列:使用 EmitCodeDom(System.CodeDom.Compiler 系统集成)

.Net Core 系列:Roslyn(Microsoft.CodeAnalysis.CSharp 需要引用组件包)

5、Emit 构建静态程序集:

在上述代码中,我们通过 AssemblyName 来指定程序集名称和版本号。

如果我们希望持久化程序集,并将程序集分发给其它人调用,则需要补充更多的相关信息:

比如:Taurus.MVC 微服务组件中就实现了动态生成 Rpc 调用程序集,以供其它微服务节点调用。

在定义完程序集名称和版本后(AssemblyBuilderAccess 需要指定为 RunAndSave )。

我们可以通过 AssemblyBuilder  的 SetCustomAttribute 方法,来添加自定义属性,最后通过 Save 方法,来持久化程序集:

internal class Program
{
static void Main(string[] args)
{
string dllName = "MyDynamic";
string title = "This is a dynamic title."; AssemblyName assemblyName = new AssemblyName(dllName) { Version = new Version("3.0.0.0") };
AssemblyBuilder assemblyBuilder = AppDomain.CurrentDomain.DefineDynamicAssembly(assemblyName, AssemblyBuilderAccess.RunAndSave); // 添加自定义属性到程序集var titleAttr = new CustomAttributeBuilder(
typeof(AssemblyTitleAttribute).GetConstructor(new Type[] { typeof(string) }),
new object[] { title }
);
assemblyBuilder.SetCustomAttribute(titleAttr); assemblyBuilder.DefineVersionInfoResource(); assemblyBuilder.Save(dllName + ".dll"); Console.WriteLine("程序集已生成");
}
}

自定义支持哪些属性,可以参考工程目录下的:AssemblyInfo.cs 信息:

没有指定完整的保存路径时,程序运行之后默认在运行目录下可以看到生成的程序集:

通过反编绎工具,可以看到程序集的相关信息:

在上面的示例代码中,我们追加了一行代码如下:

assemblyBuilder.DefineVersionInfoResource();

它让生成的程序集在 Window 平台可以看到相关信息,(不写入的话,相关内容为空):

6、使用 CodeDom 生成静态程序集

CodeDom 的使用比较简单,无需要引用组件,代码编写也比较简洁,唯一的缺点,就是不支持 .NET Core 系列。

使用示例代码来自Taurus.MVC 微服务RPC代码生成(CodeDom 版本):

更多源码:https://github.com/cyq1162/Taurus.MVC/tree/master/src/Taurus.Core/Plugin/MicroService/Client/Proxy/DotNet

 /// <summary>
/// Rpc 客户端代理生成类
/// </summary>
internal static class RestProxyCreator
{
/// <summary>
/// 构建并保存程序集
/// </summary>
/// <param name="assName">程序集名称</param>
/// <param name="savePath">保存路径</param>
public static bool BuildAssembly(string assName, string savePath)
{
if (string.IsNullOrEmpty(assName)) { assName = "RpcProxy"; }
if (string.IsNullOrEmpty(savePath))
{
savePath = AppConst.WebRootPath + assName + ".dll";
}
else if (!savePath.StartsWith(AppConst.WebRootPath))
{
savePath = AppConst.WebRootPath + savePath;
}
string version;
var code = RestProxyCoder.CreateCode(assName, out version);
var title = assName + " for Taurus MicroService, build on .net " + Environment.Version.ToString();
var versionCode = RestProxyCoder.CreateVersionCode(title, title, version); CodeDomProvider provider = CodeDomProvider.CreateProvider("C#");
CompilerParameters cp = new CompilerParameters();
cp.ReferencedAssemblies.Add("System.dll");
cp.ReferencedAssemblies.Add("System.Web.dll");
cp.ReferencedAssemblies.Add("System.Xml.dll");
cp.ReferencedAssemblies.Add("System.Data.dll");
cp.ReferencedAssemblies.Add(AppConst.AssemblyPath + "CYQ.Data.dll");
cp.ReferencedAssemblies.Add(AppConst.AssemblyPath + "Taurus.Core.dll");
cp.GenerateExecutable = false;
cp.GenerateInMemory = false;
cp.OutputAssembly = savePath;
CompilerResults cr = provider.CompileAssemblyFromSource(cp, versionCode, code);
if (cr == null || cr.Errors.Count > 0) { return false; }
return true; }
}

7、 使用 Roslyn 生成静态程序集

Roslyn 需要引用组件,根据自己要支持的最低版本进行引用(如 4.1.0 就是最低支持.NET Core 2.1的版本):

而且使用麻烦,两个距坑:

1、Win32 资源信息如何显示。

2、程序集引用路径错误问题。

为解决这两个距坑问题,费了不少时间和精力,毕竟网上国内外只有零散的引导信息,还有那不靠谱的 GPT 系列。

使用示例代码来自 Taurus.MVC 微服务RPC代码生成(Roslyn 版本):

更多源码:https://github.com/cyq1162/Taurus.MVC/tree/master/src/Taurus.Core/Plugin/MicroService/Client/Proxy/NetCore

/// <summary>
/// Rpc 客户端代理生成类
/// </summary>
internal static class RestProxyCreator
{
/// <summary>
/// 构建并保存程序集
/// </summary>
/// <param name="assName">程序集名称</param>
/// <param name="savePath">保存路径</param>
public static bool BuildAssembly(string assName, string savePath)
{
if (string.IsNullOrEmpty(assName)) { assName = "RpcProxy"; }
if (string.IsNullOrEmpty(savePath))
{
savePath = AppConst.WebRootPath + assName + ".dll";
}
else if (!savePath.StartsWith(AppConst.WebRootPath))
{
savePath = AppConst.WebRootPath + savePath;
}
string version;
var code = RestProxyCoder.CreateCode(assName, out version);
var title = assName + " for Taurus MicroService, build on .netcore " + Environment.Version.ToString();
var versionCode = RestProxyCoder.CreateVersionCode(title, title, version); SyntaxTree syntaxVersionTree = CSharpSyntaxTree.ParseText(versionCode);
SyntaxTree syntaxCodeTree = CSharpSyntaxTree.ParseText(code); // 定义编译选项
CSharpCompilationOptions compilationOptions = new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary)
.WithOptimizationLevel(OptimizationLevel.Release); // 设置优化级别 // 创建 Compilation
CSharpCompilation compilation = CSharpCompilation.Create(assName,
new[] { syntaxVersionTree, syntaxCodeTree },
references: AddRef(),
options: compilationOptions); // 编译并生成程序集
using (MemoryStream ms = new MemoryStream())
{
using (Stream win32resStream = compilation.CreateDefaultWin32Resources(
versionResource: true, // 生成版本号。
noManifest: false,
manifestContents: null,
iconInIcoFormat: null))
{ EmitResult result = compilation.Emit(ms, win32Resources: win32resStream); if (!result.Success)
{
//foreach (var diagnostic in result.Diagnostics)
//{
// Console.WriteLine(diagnostic);
//}
//Console.WriteLine("fail....");
return false;
}
else
{
// 保存程序集到文件
using (FileStream file = new FileStream(savePath, FileMode.Create))
{
ms.Seek(0, SeekOrigin.Begin);
ms.CopyTo(file);
}
//Console.WriteLine("OK....");
}
}
} return true; } /// <summary>
/// 添加程序集引用
/// </summary>
/// <returns></returns>
private static PortableExecutableReference[] AddRef()
{
List<PortableExecutableReference> exeRefs = new List<PortableExecutableReference>(); string path = typeof(object).Assembly.Location; #region 引用路径修正
path = Path.GetDirectoryName(path);
if (path.Contains("\\shared\\Microsoft.NETCore.App"))
{
// windows 平台,这是nuget 实现程序集路径
string refPath = path.Replace("\\shared\\Microsoft.NETCore.App", "\\\\packs\\Microsoft.NETCore.App.Ref") + "\\ref";
if (Directory.Exists(refPath))
{
string[] files = Directory.GetDirectories(refPath);
if (files.Length > 0) { path = files[0] + "\\"; }
}
}
#endregion
foreach (string dllFilePath in Directory.GetFiles(path, "*.dll"))
{
var dll = Path.GetFileName(dllFilePath); if (dll.Split(".").Length > 4) { continue; }
if (dll.StartsWith("Microsoft.")) { continue; }
if (dll.StartsWith("System.Drawing.")) { continue; }
if (dll.StartsWith("System.IO.")) { continue; }
if (dll.StartsWith("System.Linq.")) { continue; }
if (dll.StartsWith("System.Net.")) { continue; }
if (dll.StartsWith("System.Reflection.")) { continue; }
if (dll.StartsWith("System.Security.")) { continue; }
if (dll.StartsWith("System.Text.")) { continue; }
if (dll.StartsWith("System.Threading.")) { continue; }
if (dll.StartsWith("System.Globalization.")) { continue; }
if (dll.StartsWith("System.Resources.")) { continue; }
if (dll.StartsWith("System.Transactions.")) { continue; }
if (dll.StartsWith("System.Memory.")) { continue; }
if (dll.StartsWith("System.Formats.")) { continue; }
if (dll.StartsWith("System.ComponentModel.")) { continue; }
if (dll.StartsWith("System.Windows.")) { continue; }
if (dll.StartsWith("System.Diagnostics.")) { continue; }
if (dll.Contains("VisualBasic")) { continue; } exeRefs.Add(MetadataReference.CreateFromFile(dllFilePath));
} //添加引用程序集
string cyqdata = AppConst.AssemblyPath + "CYQ.Data.dll";
string taurus = AppConst.AssemblyPath + "Taurus.Core.dll"; exeRefs.Add(MetadataReference.CreateFromFile(cyqdata));
exeRefs.Add(MetadataReference.CreateFromFile(taurus));
return exeRefs.ToArray();
}
}

8、总结:

在本章节中,我们讨论了如何使用 C# Emit 来构建动态程序集,以获得 AssemblyBuilder 这个程序集构建器,开启构建程序集的第一步。

同时我们也讨论了如何使用 C# Emit 来构建动态程序集以及程序集的持久化。

同时还分享了自己的干货,如何使用 CodeDom 和 Roslyn 来构建静态程序集。

通过本章节的内容,读者可以掌握如何利用 C# Emit 技术构建动态程序集和静态程序集,从而实现灵活、动态的程序设计和开发。

动态程序集的应用能够提升代码的模块化性、版本控制和代码复用性,为.NET应用程序的开发提供更多可能性。

.NET Emit 入门教程:第二部分:构建动态程序集(追加构建静态程序集教程)的更多相关文章

  1. esri-leaflet入门教程(5)- 动态要素加载

    esri-leaflet入门教程(5)- 动态要素加载 by 李远祥 在上一章节中已经说明了esr-leaflet是如何加载ArcGIS Server提供的各种服务,这些都是服务本身来决定的,API脚 ...

  2. [ABP教程]第二章 图书列表页面

    Web应用程序开发教程 - 第二章: 图书列表页面 关于本教程 在本系列教程中, 你将构建一个名为 Acme.BookStore 的用于管理书籍及其作者列表的基于ABP的应用程序. 它是使用以下技术开 ...

  3. 使用Expression Tree构建动态LINQ查询

    这篇文章介绍一个有意思的话题,也是经常被人问到的:如何构建动态LINQ查询?所谓动态,主要的意思在于查询的条件可以随机组合,动态添加,而不是固定的写法.这个在很多系统开发过程中是非常有用的. 我这里给 ...

  4. 动态共享库(so)开发精悍教程

    动态共享库(so)开发精悍教程 翻译并根据实际情况进行了小小修改,仅关注Linux下动态共享库(Dynamic shared library .so)的开发. 1 简单的so实例 源文件 //test ...

  5. Android Studio 入门级教程(三):gradle项目构建

    声明 生命壹号:http://www.cnblogs.com/smyhvae/ 文章来源:http://www.cnblogs.com/smyhvae/p/4456420.html [系列] Andr ...

  6. 使用 FirewallD 构建动态防火墙

    使用 FirewallD 构建动态防火墙 FirewallD 提供了支持网络/防火墙区域(zone)定义网络链接以及接口安全等级的动态防火墙管理工具.它支持 IPv4, IPv6 防火墙设置以及以太网 ...

  7. axure rp教程(四)动态面板滑动效果

    转载自: http://www.iaxure.com/74.html 实现目标: 1.  点击登录滑出登录面板 2.  点击确定滑出动态面板 最终效果如下: 这种效果可以通过两种方法实现: 首先准备需 ...

  8. javascript进阶教程第二章对象案例实战

    javascript进阶教程第二章对象案例实战 一.学习任务 通过几个案例练习回顾学过的知识 通过案例练习补充几个之前没有见到或者虽然讲过单是讲的不仔细的知识点. 二.具体实例 温馨提示 面向对象的知 ...

  9. EnjoyingSoft之Mule ESB开发教程第二篇:Mule ESB基本概念

    目录 1. 使用Anypoint Studio开发 2. Mule ESB Application Structure - Mule ESB应用程序结构 3. Mule ESB Application ...

  10. ASP动态网页(网站)设计教程

    ASP动态网页(网站)设计教程 文件名 大小 ASP获取时间函数大全 35KB 项目6 ASP数据表数据操作功能设计.pptx 3.34 MB 项目5 WEB数据库与数据库管理.pptx 3.34 M ...

随机推荐

  1. JS Leetcode 155. 最小栈 题解分析

    壹 ❀ 引 本题来自LeetCode155. 最小栈,难度简单,题目描述如下: 设计一个支持 push ,pop ,top 操作,并能在常数时间内检索到最小元素的栈. push(x) -- 将元素 x ...

  2. NC50493 石子合并

    题目链接 题目 题目描述 将n堆石子绕圆形操场排放,现要将石子有序地合并成一堆.规定每次只能选相邻的两堆合并成新的一堆,并将新的一堆的石子数记做该次合并的得分. 请编写一个程序,读入堆数n及每堆的石子 ...

  3. Python OS.mkdirs与OS.makedirs的区别

    os.mkdir只创建最外层目录,如果创建多级目录,报错"FileNotFoundError: [WinError 3] 系统找不到指定的路径".目录存在报错. os.makedi ...

  4. java 从零开始手写 redis(11)clock时钟淘汰算法详解及实现

    前言 java从零手写实现redis(一)如何实现固定大小的缓存? java从零手写实现redis(三)redis expire 过期原理 java从零手写实现redis(三)内存数据如何重启不丢失? ...

  5. VUE2的前端学习笔记

    名词笔记 Node node.js 是一切的基础, 其他的名词都是基于这个运行的, 相当于一个JavaScript的容器或者虚机 NPM 安装node.js时赠送的包管理器, 类似于apt, yum, ...

  6. spring boot结合ehcache防止恶意刷新请求

    说明 我们在把开发好的网站上线之前一定要考虑到别人恶意刷新你的网页这种情况,最大限度的去限制他们.否则往往这将搞垮你的应用服务器,想象一下某个恶意用户利用众多肉鸡在1分钟内请求你网页几十万次是个什么情 ...

  7. 【Android逆向】frida hook so 函数

    1. apk来自52pojie 链接:https://pan.baidu.com/s/1vKC1SevvHfeI7f0d2c6IqQ 密码:u1an 2.apktool反编译apk,拿到so文件 ja ...

  8. 01、etcd基础介绍

    互联网技术发展真的快,层出不穷的新技术.最近项目使用到了etcd,自己之前在部署k8s集群的时候玩过,但是并没有系统的学习.正好趁这个机会,系统性的学习下.文章中的内容有些是来自官方文档,有些是来自网 ...

  9. Html飞机大战(十二): canvas写字(结束状态的编辑)

    好家伙,基本的功能都做完了,来补充一个结束状态的游戏结束文案   上代码: case END: //给我的画笔设置一个字的样式 //后面写出来的字都是这个样式的 context.font = &quo ...

  10. 【Azure App Service】同一个App Service下创建多个测试站点的方式

    问题描述 在一个App Service中,部署多个应用,每个应用相互独立,类似与IIS中在根目录下创建多个子应用的情况. 问题解答 可以的.通过App Service Configuration页面, ...