域与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. fcntl 加锁模块

    #!/usr/bin/python # coding:utf8 import os import sys import time import fcntl # 导入模块 class FLOCK(obj ...

  2. 请注意JS方法,方法同名,参数个数不一样是不能区分方法的,

    请注意JS方法,方法同名,参数个数不一样是不能区分方法的, 所以要区分方法,只能利用方法名不同来区分,而不能利用参数个数与参数类型来分.

  3. IDEA把Main方法打包成jar包

    创建一个maven项目 写一个main方法 Module:选择main方法所在的模块,我这里只有一个模块 所以默认选中 Main Class:选择main方法所在的类 Directory for ME ...

  4. TempCache 临时内存缓存器

    TempCache.h /* *************************************************** * Copyright(c) Xiamen AutoNavi Co ...

  5. 【LeetCode】245. Shortest Word Distance III 解题报告 (C++)

    作者: 负雪明烛 id: fuxuemingzhu 个人博客:http://fuxuemingzhu.cn/ 目录 题目描述 题目大意 解题方法 字典+暴力检索 日期 题目地址:https://lee ...

  6. 【LeetCode】723. Candy Crush 解题报告 (C++)

    作者: 负雪明烛 id: fuxuemingzhu 个人博客:http://fuxuemingzhu.cn/ 目录 题目描述 题目大意 解题方法 暴力 日期 题目地址:https://leetcode ...

  7. 【LeetCode】01 Matrix 解题报告

    [LeetCode]01 Matrix 解题报告 标签(空格分隔): LeetCode 题目地址:https://leetcode.com/problems/01-matrix/#/descripti ...

  8. 【LeetCode】443. String Compression 解题报告(Python)

    作者: 负雪明烛 id: fuxuemingzhu 个人博客: http://fuxuemingzhu.cn/ 目录 题目描述 题目大意 解题方法 使用额外空间 不使用额外空间 日期 题目地址:htt ...

  9. 【LeetCode】665. 非递减数列 Non-decreasing Array(Python & C++)

    作者: 负雪明烛 id: fuxuemingzhu 公众号:每日算法题 本文关键词:数组,array,非递减,遍历,python,C++ 目录 题目描述 题目大意 解题方法 一.错误代码 二.举例分析 ...

  10. 第三十六个知识点:Index Calculus算法

    第三十六个知识点:Index Calculus算法 我们这篇博客继续描述一种数学攻击,这种数学攻击被叫做Index Calculus(IC)算法. 注意这里Index Calculus算法没有找到合适 ...