域与ALC

在 Natasha 发布之后有不少小伙伴跑过来问域相关的问题, 能不能兼容 AppDomain, 如何使用 AppDomain, 为什么 CoreAPI 阉割了 AppDomain 等一系列的问题.

今天答复一下:

  • 首先 AppDomain 作为程序集隔离容器的存在, 是风靡了 .Net Framework 的各大版本, 被誉为是轻量级进程, 由 AppDomain 发展的特性和操作也很多.而 Natasha 采用的是 AssemblyLoadContext 简称 "ALC"; ALC 是 .NET Core 版本后出现的操作类, 这个类在 .NET Core 及以后的版本中, 只要加载依赖项, 就会调用它.有趣的是,你在调试代码过程中如果去观察它, 可以看到它缓存程序集的数量在增加. 因为还没运行到的程序集可以先不加载, 检测代码 AssemblyLoadContext.Default.Assemblies.Count();
  • 其次它本不是域,或者不能称为域. 它和域的区别是, FW 支持多域, 而 CORE 仅支持单域, CORE 就一个默认域. ALC 的名字翻译过来是, 程序集加载上下文, 看英文名字也是和域区分开了;
  • 最后一点区别是域的卸载是强制的, ALC 的卸载是"协商"的, 相比域而言, ALC 中的程序集所包含的元数据被保持引用,就不能被卸载, 比如你反射出来的类或者方法或者其他什么的放到了一个主域的字典中,那么字典不毁,这个ALC就没办法卸载,尽管 ALC 有 Unload 方法,卸载还是要看元数据是否被保持引用;

Natasha 设计初衷是使用隔离性较强的字眼, 用域的概念来减少 .NETCore 带来的新的理解成本, 另外之前有打算兼容 AppDomain 的想法,

这个想法的优先级不高:

  1. 是.Net Core 是在 3.0 时出现比较明显的分水岭, 包括依赖解析,上下文域识别等重要特性的支持;
  2. 是 Roslyn 对 FW 的支持不能低于(4.6.1);
  3. 是 UT测试需要区分版本来做,很麻烦, 插件部分的测试不简单;
  4. 是 个人精力原因, 还要工作, 还要维护其他项目;

这里也希望公司们都能平稳度过升级期, 早点迎接更好更实用的"未来技术";

Natasha 域的使用

插件的开发技巧

这里不得不回顾一下插件开发的知识, 它可不是像培训机构讲的编译一个 DLL 然后 Assembly.LoadFrom 就可以的.

首先要了解加载插件的两个侧重点, 插件依赖打包和插件依赖管理.

  • 插件依赖打包: 首先插件生成时,你需要把必要的引用库一起打包, 此时需要在工程文件的 PropertyGroup 节点中添加 <EnableDynamicLoading>true</EnableDynamicLoading> 让编译程序输出依赖文件, 同时不要忘了交付 "xxx.deps.json", 这是让宿主程序解析依赖的关键;
  • 插件依赖管理: 如果你的接口 IPlugin 给到插件开发人员, 让他按照这个接口去写功能, 那么当他交付插件时, 你不能再将他包里的 IPlugin 再引进来. 否则如下代码将报错, (var plugin = (IPlugin)Activtor.Create(pluginA);) 类型转换错误, 原因是代码中的 IPlugin 在主域中使用, 而 pluginA 是加载到其他域中的, 而且在那个域里也存在一个 IPlugin, 这个接口类型不同于主域的接口类型, 因此在转换时会引发类型转换的错误.
  • 解决方法1: 让插件开发人员在自己的工程添加设置,自动排除这个主要依赖. (官方的推荐做法)
<ItemGroup>
<ProjectReference Include="..\IPlugin\IPlugin.csproj">
<Private>false</Private>
<ExcludeAssets>runtime</ExcludeAssets>
</ProjectReference>
</ItemGroup>
  • 解决方法2: 在实现的 ALC 中添加过滤方法排除 IPlugin.

以上是基本的插件开发知识,如果你还不了解, 可以读一读微软插件开发文档.

单独使用 NatashaDomain :

  1. 引入包 DotNetCore.Natasha.Domain 包.

  2. 加载插件

NatashaDomain domain = new NatashaDomain("NewPluginDomain");

//加载方法: 参数1: 插件位置; 参数2: 根据 AssemblyName 排除需要加载的插件名称.
//加载插件,如果主域存在相同名字的依赖,则使用版本较高的那版.
domain.LoadPluginWithHighDependency("c:/xxx/pluginA.dll", excludeAssembliesFunc: asn => asn.Name.Contains("IPlugin")); //加载插件,如果主域存在相同名字的依赖,则使用版本较低的那版.
domain.LoadPluginWithLowDependency("c:/xxx/pluginA.dll", excludeAssembliesFunc: asn => asn.Name.Contains("IPlugin")); //加载插件,如果主域存在相同名字的依赖,则使用主域中的那版.
domain.LoadPluginUseDefaultDependency("c:/xxx/pluginA.dll"); //加载插件,不判重,全部加载.
domain.LoadPluginWithAllDependency("c:/xxx/pluginA.dll", excludeAssembliesFunc: asn => asn.Name.Contains("IPlugin")); //卸载域
domain.Dispose();

避坑指南

如果您使用以上 API 将插件加载到同一个域, 会出现很多问题:

建议:

  1. 写插件时,本身解决好引用管理问题.
  2. 如果插件过于庞大,请将插件功能解耦,并加载到不同域中反射给主域执行.
  3. 主域要对依赖使用版本检查, 请在插件加载代码之前执行一些功能. 比如 _ = typeof(Dapper.CommandDefinition); 尽管这句没有用, 但它将迫使运行时将 dapper 的程序集加载到默认上下文的缓存中, 这样在你加载插件时, 如果遇到 dapper 依赖, 将触发版本检查详见代码.

结尾

您可以自行查看案例代码. NatashaDomain 是 Natasha 动态编译的父级, Natasha 动态编译中的 NatashaReferenceDomain 继承了此类, 因此如果您想使用 Natasha 进行动态构建请使用 NatashaReferenceDomain. 下一篇将讲解 Natasha 的基本编译知识.

Natasha 4.0 探索之路系列(二) "域"与插件的更多相关文章

  1. Natasha 4.0 探索之路系列(一) 概况

    Natasha 简介 Natasha 是一个基于 Roslyn 的动态编译类库, 它以极简的 API 完成了动态编译的大部分功能, 使用它可以在程序运行时编译出新的程序集. Natasha 允许开发人 ...

  2. Natasha 4.0 探索之路系列(三) 基本的动态编译

    Natasha 的设计 动态编译 Roslyn 为开发者提供了动态编译的接口, 允许我们以 C# 代码来编写 Emit 或 表达式树生成的程序集, 但是完成一个编译需要诸多步骤, 用户参与的操作也很多 ...

  3. Natasha 4.0 探索之路系列(四) 模板 API

    Natasha 模板 Natasha 在编译单元的基础上进行了封装整理, 并提供了多种模板帮助开发者构建功能. 使用此篇的 API 前提是您对 C# 非常熟悉, 对系统的一些类型足够了解. 据此 Na ...

  4. ionic实战系列(二):使用cordova插件

    本章主要关注cordova的各种插件,利用好手机(移动设备)的原生功能.首先cordova是一个将web网页内嵌到原生app的平台(核心功能),然后cordova拥有的插件系统扩展了核心功能. Cor ...

  5. [CXF REST标准实战系列] 二、Spring4.0 整合 CXF3.0,实现测试接口(转)

    转自:[CXF REST标准实战系列] 二.Spring4.0 整合 CXF3.0,实现测试接口 文章Points: 1.介绍RESTful架构风格 2.Spring配置CXF 3.三层初设计,实现W ...

  6. 探索ASP.Net Core 3.0系列二:聊聊ASP.Net Core 3.0 中的Startup.cs

    原文:探索ASP.Net Core 3.0系列二:聊聊ASP.Net Core 3.0 中的Startup.cs 前言:.NET Core 3.0 SDK包含比以前版本更多的现成模板. 在本文中,我将 ...

  7. scrapy爬虫学习系列二:scrapy简单爬虫样例学习

    系列文章列表: scrapy爬虫学习系列一:scrapy爬虫环境的准备:      http://www.cnblogs.com/zhaojiedi1992/p/zhaojiedi_python_00 ...

  8. C语言高速入门系列(二)

    C语言高速入门系列(二) -----转载请注明出处coder-pig 本节引言: 在前面一节中我们对C语言进行了初步的了解,学会了使用IDE进行代码的编写,编译执行! 在这一节中我们会对C语言的基本的 ...

  9. Redis总结(五)缓存雪崩和缓存穿透等问题 Web API系列(三)统一异常处理 C#总结(一)AutoResetEvent的使用介绍(用AutoResetEvent实现同步) C#总结(二)事件Event 介绍总结 C#总结(三)DataGridView增加全选列 Web API系列(二)接口安全和参数校验 RabbitMQ学习系列(六): RabbitMQ 高可用集群

    Redis总结(五)缓存雪崩和缓存穿透等问题   前面讲过一些redis 缓存的使用和数据持久化.感兴趣的朋友可以看看之前的文章,http://www.cnblogs.com/zhangweizhon ...

随机推荐

  1. C笔试题:将int型数组强制转换为char*,再求strlen,涉及大小端

    1 #include<stdio.h> 2 #include<string.h> 3 int main() 4 { 5 int a[2000]; 6 char *p = (ch ...

  2. GCD - Extreme (II)(UVA11426)

    思路:欧拉函数: 欧拉函数,然后用下等差数列公式就行了. 1 #include<stdio.h> 2 #include<algorithm> 3 #include<ios ...

  3. Codeforce C. Pearls in a Row

    C. Pearls in a Row time limit per test 2 seconds memory limit per test 256 megabytes input standard ...

  4. Deepin20系统安装Nvidia驱动

    Deepin20系统安装Nvidia驱动 系统设备配置信息如下: 电脑型号:华硕天选air[ASUS-FX516P] 显卡型号:RTX 3070 移动版独显 处理器型号: 11th Gen Intel ...

  5. 使用PyTorch构建神经网络以及反向传播计算

    使用PyTorch构建神经网络以及反向传播计算 前一段时间南京出现了疫情,大概原因是因为境外飞机清洁处理不恰当,导致清理人员感染.话说国外一天不消停,国内就得一直严防死守.沈阳出现了一例感染人员,我在 ...

  6. 云南农职 - 互联网技术学院 - 美和易思大一SCME JAVA高级结业考试机试试题

    目录 一.语言和环境 二.实现功能 1.文件复制功能(IO) 2.消息接受站建设 三.评分标准 四.实现代码 一.语言和环境 实现语言:Java. 开发工具:eclipse. 使用技术:IO流+网络编 ...

  7. Java面向对象程序设计作业目录(作业笔记)

    持续更新中............. 我的大学笔记>>> 第1章 面向对象 >>> 1.1.5 编写Java程序,创建Dota游戏中的防御塔类,通过两个坐属性显示防 ...

  8. 【MySQL作业】DDL 和 DML——美和易思使用 DML 新增和更新表数据应用习题

    点击打开所使用到的数据库>>> 1.添加 easyShopping 客户数据. insert into customer values('abc111','111',' 刘一鸣 ', ...

  9. 编写Java程序,用户在网上购买商品(good),当用户买了一本书(book)、一顶帽子(hat)或者买了一双鞋子(shoe),卖家就会通过物流将商品邮寄给用户,使用简单工厂模式模拟这一过程。

    查看本章节 查看作业目录 需求说明: 编写Java程序,用户在网上购买商品(good),当用户买了一本书(book).一顶帽子(hat)或者买了一双鞋子(shoe),卖家就会通过物流将商品邮寄给用户, ...

  10. Unity——基于ShaderLab实现光照系统

    这篇主要总结Unity中ShaderLab的着色器代码实现总结,需要有一定图形学基础和ShaderLab基础: 一.着色器 1.顶点片元着色器 分顶点着色器和片元着色器,对应渲染管线的顶点变换和片元着 ...