记一次dotnet拆分包,并希望得大佬指点
记一次dotnet拆分包,并希望得大佬指点
之前做了一个用于excel导入导出的包, 定义了一些接口, 然后基于 NPOI EPPlus MiniExcel 做了三种实现
接口大概长下面这样(现在可以在接口里面写静态函数了!)
public interface IExcelReader
{
// 根据一些条件返回下面的实现
public static IExcelReader GetExcelReader(string filePath, <params>)
{
}
}
然后有对应三种实现
public class NPOIReader: IExcelReader
{}
public class EPPlusReader: IExcelReader
{}
public class MiniExcel: IExcelReader
{}
在使用时
using var reader = IExcelReader.GetExcelReader("ExcelReader.xlsx", <一堆杂七杂八的条件>)
根据需要获取实例, 而不必去管什么 NPOI EPPlus MiniExcel
用起来可以极大的降低心智负担, 也可以使用我认为比较 "人性化" 的操作
这一堆东西都是写在一起的, 然后碰到了一些我比较在意的问题
- 如果我只是更新了NPOI包的实现, 然后push了一个新的版本, 这就相当于其他的实现也被"升级"了, 尽管另外的实现没有任何变化, 我认为这样是不好的
- 如果我只想使用 MiniExcel 的内容, 但由于三个实现写在了一起, NPOI 和 EPPlus 会被一起引入, 我认为这样是不好的
- 如果我修改了接口
IExcelReader
, 那我必定需要同时修改对应的三个实现, 但是由于三个实现写在一起, 我必须将三个实现都改完测完, 然后才能push发包, 我认为这样是不好的
因为这样那样的问题, 我开始考虑拆包了
初步构想
一开始的想法是
先把统一的接口和操作什么的东西抽出来, 做成一个Core包
然后 NPOI EPPlus MiniExcel 相关的实现做成三个包, 都引用这个 Core
如果代码中只用 IExcelReader
这样的接口进行操作, 可以在不改变代码的前提下, 通过更换包引用(比如NPOI的包改为MiniExcel的包)轻松改变实现, 达成不同的效果
但由于Core包是被引用的, 所以理论上来说 IExcelReader
并不能像之前那样直接创建这三种实例
碰到这种"我知道, 但是身不由己"的情况, 我想到了用委托来做
// (大概是这么个感觉, 实际上我现在用的是字典)
public static List<Func<string, IExcelReader>> Selector;
在Core中搞一个静态委托集合, 然后在那三个包中将创建对象的委托加到这个集合里, 之后在使用 IExcelReader.GetExcelReader("**.xlsx")
时, 就可以通过这个委托集合获取到对应的实现了
以上是我的大致思路
第一种尝试-静态构造函数
我最先想到的就是静态构造函数
毕竟微软的文档上说了
静态构造函数用于初始化任何静态数据,或执行仅需执行一次的特定操作。 将在创建第一个实例或引用任何静态成员之前自动调用静态构造函数。
看描述还挺符合我的想法, 然后就有了如下代码
public class NPOIExcelReader : IExcelReader
{
static NPOIExcelReader()
{
Selector.Add((path) =>
{
// 假装下面做了一堆事情
// ......
return new NPOIExcelReader(path);
});
}
}
看着好像还行, 试了一下结果GG
如果我只是使用 IExcelReader.GetExcelReader("**.xlsx")
, 则无法触发这个构造函数, 除非我在这之前调用一次 NPOIExcelReader
, 但这与我的设想差挺多的, 所以暂时放弃了这个方案, 另寻他法
第二种尝试-ModuleInitializer
我觉得可能是因为 class
太 "低" 了, 所以才无法触发静态构造函数
然后我又想到了 ModuleInitializer
, 感觉这个总比 class
"高"一些, 不知道能不能实现我的想法
internal class Init
{
[ModuleInitializer]
public static void InitSelector()
{
Selector.Add((path) =>
{
// 假装下面做了一堆事情
// ......
return new NPOIExcelReader(path);
});
}
}
在NPOI包里写完上面的初始化之后我又尝试了一次, 结果还是GG......
碰到了类似的问题, 如果不调用NPOI包内的东西, 则无法初始化
第三种尝试-AppDomain.CurrentDomain.Load
后来查看了AppDomain.CurrentDomain.GetAssemblies()
, 发现程序运行时并没有加载 NPOI包 的程序集, 我觉得可能是因为这个原因才导致扑街的
所以尝试在Core中用反射获取程序集(因为在代码中使用了IExcelReader.GetExcelReader
, 所以可以触发Core包的ModuleInitializer
初始化), 然后使用 AppDomain.CurrentDomain.load
来加载
public static class Init
{
[ModuleInitializer]
public static void InitCellReader()
{
var files = Directory.GetFiles(AppDomain.CurrentDomain.BaseDirectory, "我那几个包的实现.dll");
if (files.IsEmpty())
return;
var newAsses = files.Select(item => Assembly.LoadFrom(item)).ToList();
newAsses.ForEach(item => AppDomain.CurrentDomain.Load(item.FullName));
}
}
运行之后打个断点, 确实执行了, 也确实加载到 AppDomain.CurrentDomain
中了, 但是...还是没用, 全都木大木大了
绝望的尝试-反射+Activator
既然发现问题出在 "不调用就不初始化" 上, 那我就调用一下...
基于上面的第三种尝试, 尝试创建NPOI包中的实现, 能不能创建无所谓, 重要的是摆出一副 "我要调你" 的感觉, 然后初始化自己动起来
还是写在Core包中
public static class Init
{
[ModuleInitializer]
public static void InitCellReader()
{
var files = Directory.GetFiles(AppDomain.CurrentDomain.BaseDirectory, "我那几个包的实现.dll");
if (files.IsEmpty())
return;
var newAsses = files.Select(item => Assembly.LoadFrom(item)).ToList();
newAsses.ForEach(item => AppDomain.CurrentDomain.Load(item.FullName));
var types = newAsses.SelectMany(s => s.GetTypes().Where(item => item.HasInterface(typeof(IExcelReader))));
types.ForEach(item =>
{
try
{
Activator.CreateInstance(item);
}
catch { }
});
}
}
然后配合其他包的 Init
, 最后终于算是实现了我想要的效果
但是实现的方式太丑陋了...不知道有没有更好, 更优雅的方式
记一次dotnet拆分包,并希望得大佬指点的更多相关文章
- 配置中心的设计-nacos vs apollo
简介 前面我们分析了携程的 apollo(见 详解apollo的设计与使用),现在再来看看阿里的 nacos. 和 apollo 一样,nacos 也是一款配置中心,同样可以实现配置的集中管理.分环境 ...
- Json操作问题总结
大家都知道,Json是一种轻量级的数据交换格式,对JS处理数据来说是很理想滴! 熟练写过xxx.json文件和操作的小伙伴来说,我说的问题都不是什么大问题啦,可以忽略本宝宝的文章,更希望各位大佬指点一 ...
- .Net Core建站(3):搭建三层架构
啊,终于到写三层架构的时候了,老实说,我都不知道自己这个算不算三层架构,姑且就当它是吧,具体属于哪一个体系,希望有大佬指点一下(^o^)/ 不晓得有人注意到没有,我写了三篇博客,然后就改了三次标题ヽ( ...
- 分享我在 vue 项目中关于 api 请求的一些实现及项目框架
本文主要简单分享以下四点 如何使用 axios 如何隔离配置 如何模拟数据 分享自己的项目框架 本文主要目的为以下三点 希望能够帮到一些人 希望能够得到一些建议 奉上一个使用Vue的模板框架 我只是把 ...
- jsp里面不能使用${pageContext.request.contextPath}解决方案
1.在jsp中使用${pageContext.request.contextPath}获取相对路径,可是最后路径变为:http://localhost:8080/oneself/$%7BpageCon ...
- 利用顺序栈解决括号匹配问题(c++)-- 数据结构
题目: 7-1 括号匹配 (30 分) 给定一串字符,不超过100个字符,可能包括括号.数字.字母.标点符号.空格,编程检查这一串字符中的( ) ,[ ],{ }是否匹配. 输入格式: 输入在一行 ...
- 调试.NET CORE代码
前言 core也用了很长一段时间了,发现很多小伙伴不知道如何调试core的代码. 可想而知,以前使用mvc的时候,不需要发布代码,直接iis地址指向项目源码,然后附加到进程w3wp.exe就可以调试了 ...
- NETCore 调试
https://www.cnblogs.com/MingQiu/p/8227644.html https://www.cnblogs.com/shumin/p/9967854.html 前言 core ...
- 【技术累积】【点】【sql】【15】MySQL的TEXT和SELECT问题
说明 只是TEXT和SELECT两个东西相关的问题,并不是两者之间的关系. TEXT TEXT类型,大文本类型,细分起来还有BIGTEXT,TINYTEXT等: 总体而言,就是处理mysql中存储大文 ...
随机推荐
- laravel7 数据迁移及填充数据
1:置迁移数据表,创建生成模型和迁移文件: php artisan make:model Article -m 2:接着,在框架中的database/migrations文件夹中找到刚创建的用户表Ar ...
- 微信小程序授权获取手机号
wxml: <text>pages/logins/logins.wxml</text> // <button open-type="getPhoneNumber ...
- Mysql 8.0 配置主从备份
my.ini文件的位置 mysql 8.0安装完过后没有my.ini疑惑了我好久,最后发现,配置文件在,C盘的一个隐藏文件夹里面 具体路径如下图 主库配置 修改主库INI文件 在[mysqld]节点添 ...
- [源码解析] TensorFlow 分布式环境(1) --- 总体架构
[源码解析] TensorFlow 分布式环境(1) --- 总体架构 目录 [源码解析] TensorFlow 分布式环境(1) --- 总体架构 1. 总体架构 1.1 集群角度 1.1.1 概念 ...
- ArcMap操作随记(15)
1.字段顺序的调整 [图层属性]→[字段]([高级排序]功能也在这里) 2.所谓热点地区: 本身是高值,邻居也是高值,才成为热点地区(这是操作中总结出的,但其实了解原理就很简单了,详细请查看空间自相关 ...
- 使用pyautogui自动在某网站投票的脚本
网页自动投票: # !/usr/bin/env python # -*- coding:utf-8 -*- # Author:Hiuhung Wan import re import pypercli ...
- 5月16日 python学习总结 DBUtils模块、orm 和 cookie、session、token
一.DBUtils模块 介绍 The DBUtils suite is realized as a Python package containing two subsets of modules, ...
- [SPDK/NVMe存储技术分析]015 - 理解内存注册(Memory Registration)
使用RDMA, 必然关系到内存区域(Memory Region)的注册问题.在本文中,我们将以mlx5 HCA卡为例回答如下几个问题: 为什么需要注册内存区域? 注册内存区域有嘛好处? 注册内存区域的 ...
- GeneralUpdate20220323里程碑版本发布
大家好我是juster,GeneralUpdate的开源项目作者.这次将发布GeneralUpdate里程碑版本,该版本发生了巨大改变历时4个月的时间终于要和大家见面了.开源不易希望大家能多多支持.可 ...
- FPGA驱动LCD显示红绿蓝彩条
实验目的:先简单熟悉LCD灯的驱动和时序图的代码实现.设计功能是让LCD显示红绿蓝三种颜色,即三个彩带.本次实验比较容易实现,主要是对LCD驱动时序图的理解和时序参数的配置. 实验条件:1.LCD原理 ...