2024年4月

一、前言

在自己的项目中,涉及到实时心率曲线的绘制,项目上的曲线绘制,一般很难找到能直接用的第三方库,而且有些还是定制化的功能,所以还是自己绘制比较方便。很多人一听到自己画就害怕,感觉很难,今天就分享一个完整的实时心率数据绘制心率曲线图的例子;之前的博客也分享给DrawingVisual绘制曲线的方法,这个例子也是通过同样的方式;

二、正文

1、实时心率监护这种项目,场景一般是硬件设备采集到心率数据,然后推送给后台汇总,然后后台通过网络再推送给客户端,客户端再进行展示。

2、这里通过加载准备好的模拟心率数据,然后结合使用定时器定时推送数据,来模拟后端给客户端推送数据的场景。数据包里包含了三个心率数据,和一个时长的Index,一秒钟推送4个数据。

private voidInit()
{
//加载心率模拟数据 using StreamReader reader = new("fhrdata.txt");stringline;while ((line = reader.ReadLine()) != null)
{
fhrDatas.Add(
int.Parse(line));
}
//定时器模拟后台定时推送心率数据 pushFhrDataTimer = newDispatcherTimer
{
Interval
= TimeSpan.FromSeconds(1),
};
pushFhrDataTimer.Tick
+=PushFhrDataTimer_Tick;
pushFhrDataTimer.Start();
}
private void PushFhrDataTimer_Tick(objectsender, EventArgs e)
{
//一秒钟推送4个数据 var fhrs = new FhrModel[4];for (int i = 0; i < fhrs.Length; i++)
{
fhrs[i]
= newFhrModel
{
Index
=timeIndex,
Fhr1
=fhrDatas[dataIndex],
Fhr2
=fhrDatas[dataIndex],
Fhr3
=fhrDatas[dataIndex],
};

timeIndex
++;
dataIndex
++;if (dataIndex >= fhrDatas.Count) dataIndex = 0;
}
//推送数据到心率曲线控件 chart.AddFhrData(fhrs);
}

3、自定义的心率曲线控件,这里对实际项目中的进行了简化,界面如下图所示,这里的参考线通过网格的形式绘制,纵坐标也直接绘制在网格中,横坐标是时间,绘制在底下,左上角也实时显示当前最新心率值;

4、实时心率绘制,就会涉及到曲线的一直更新绘制和移动,这里是通过每次收到一次后端推送的数据,就重新触发滑动条滚动到最新位置的事件,然后通过滑动条的滑动变化事件里面触发曲线的重新绘制。这里的绘制还分为从左边开始和右边开始,称之为走纸方向,如下图。

5、曲线绘制的具体细节还有自定义曲线控件里涉及到的逻辑这里就不具体描述,后面会分享出代码的地址,并且尽量附上一些注释,最终实现的效果如下,曲线图会自动走纸绘制,支持拖动,支持拖动结束后,一段时间后自动恢复走纸。百万级别数据也不会卡,因为绘制的部分始终只有可见区域;

6、代码地址:
https://gitee.com/liulang_g/draw-curve-demo

一:背景

1. 讲故事

上个月有个朋友在微信上找到我,说他们的软件在客户那边隔几天就要崩溃一次,一直都没有找到原因,让我帮忙看下怎么回事,确实工控类的软件环境复杂难搞,朋友手上有一个崩溃的dump,刚好丢给我来分析一下。

二:WinDbg分析

1. 程序为什么会崩溃

windbg 有一个厉害之处在于双击之后可以帮你自动定位到崩溃处,输出如下:


................................................................
................................................................
.......................................................
This dump file has an exception of interest stored in it.
The stored exception information can be accessed via .ecxr.
(1418.89c): Access violation - code c0000005 (first/second chance not available)
For analysis of this file, run !analyze -v
clr!WKS::gc_heap::background_mark_simple1+0x5a1:
000007fe`f5316d40 41f70300000080  test    dword ptr [r11],80000000h ds:00000000`00000000=????????

从卦中信息看,这不是一件好事情,崩溃居然落在bgc线程上,此时bgc线程正在做后台对象标记,依据过往经验有可能是托管堆损坏了,可以用
!verifyheap
命令来验证下。


0:038> !verifyheap 
Object 000000000476a0a8 has an invalid method table.
Last good object: 000000000476A058.

0:038> !lno 000000000476a0a8
Before:  000000000476a058           80 (0x50)	System.Text.RegularExpressions.RegexWriter
After:   000000000476a0c0          152 (0x98)	System.Int32[]

0:038> dp 000000000476a058
00000000`0476a058  000007fe`f0e47390 00000000`0476a0c0
00000000`0476a068  00000000`0476a1d0 00000000`0476a158
00000000`0476a078  00000000`0476a1a8 00000000`00000000
00000000`0476a088  0000001a`00000000 00000006`0000001a
00000000`0476a098  00000000`00000000 00000000`00000018
00000000`0476a0a8  00000000`00000000 00000000`00000000
00000000`0476a0b8  00000000`00000000 000007fe`f2ce9250
00000000`0476a0c8  00000000`00000020 00000004`00000000

从卦中看确实有一个对象处于有损状态,它的
mt=0 ,objheader=0x18
,这个信息还是蛮奇怪的,接下来观察下内存地址的布局,找出有损地址空间,截图如下:

上面圈出来的地址段也不像是 C++ 故意改的,那到底是真破坏还是假破坏呢?因为这一点确实可以决定后续的分析方向。

2. 托管堆真假破坏之谜

熟悉 bgc 运转逻辑的朋友都知道会有三个阶段,分别为
初始标记
,
并发标记
,
最终标记
,所以接下来的问题是当前这个程序处于什么阶段呢? 这个观察手段比较多,除了看bgc流程也可以
!t -special
看看
SuspendEE
标记。


0:038> !t -special
ThreadCount:      40
       ID OSID ThreadOBJ           State GC Mode     GC Alloc Context                  Domain           Count Apt Exception
   24   12 174c 000000001e71c210  1029220 Cooperative 0000000000000000:0000000000000000 000000000027ba40 2     MTA (GC) (Threadpool Worker) 
   ...
   38   13  89c 000000001e529130    21220 Preemptive  0000000000000000:0000000000000000 000000000027ba40 0     Ukn System.ExecutionEngineException 00000000022f1228

          OSID Special thread type
        24 174c SuspendEE ThreadpoolWorker 

从卦中可以清晰的看到24号线程触发了一个
前台GC
,同时38号的 bgc抛了
执行引擎异常
,这个灾难性的异常也是程序崩溃的原因,接下来观察下 24号正在做什么。


0:024> k 10
 # Child-SP          RetAddr               Call Site
00 00000000`205ebcf0 000007fe`f52fb444     clr!GcInfoDecoder::EnumerateLiveSlots+0x4df
01 00000000`205ec140 000007fe`f52fbd81     clr!EECodeManager::EnumGcRefs+0xc4
02 00000000`205ec2a0 000007fe`f5380aee     clr!GcStackCrawlCallBack+0x171
03 00000000`205ec3a0 000007fe`f52fbc28     clr!Thread::StackWalkFrames+0x172
04 00000000`205ed820 000007fe`f53141f0     clr!CNameSpace::GcScanRoots+0x144
05 00000000`205ed8c0 000007fe`f5310a8e     clr!WKS::gc_heap::relocate_phase+0x40
06 00000000`205ed930 000007fe`f5310fd0     clr!WKS::gc_heap::plan_phase+0xa01
07 00000000`205edb90 000007fe`f530df61     clr!WKS::gc_heap::gc1+0x9f
08 00000000`205edbe0 000007fe`f530dccd     clr!WKS::gc_heap::garbage_collect+0x222
09 00000000`205edc70 000007fe`f530e220     clr!WKS::GCHeap::GarbageCollectGeneration+0xdd
0a 00000000`205edcc0 000007fe`f516ae69     clr!WKS::GCHeap::Alloc+0x29d
0b 00000000`205edd10 000007fe`f2b2ab2a     clr!FramedAllocateString+0x139
...

从卦中可以清晰的看到此时的GC正在
relocate_phase
重定位阶段,重定位阶段通俗来说就是
兵马未动,粮草先行
,这是一种非原子性的操作,简单来说bgc拿到的可能是移动后的新地址(粮草),但对象(兵马)还是在老地方,所以刚才看到的托管堆那块空间是初始化状态,同时对象头的 0x18 应该是一种中间状态的对象标记,这个暂时不去深挖了,但不管怎么说,此时的托管堆大概率是没有问题的。

既然托管堆没有问题,后面的研究方向就得深挖 bgc 的逻辑了。

3. bgc线程到底怎么了

要知道 bgc 到底怎么了,必须要观察它附近的汇编代码,可以用 ub 命令,输出如下:


0:024> r
Last set context:
rax=000000000001b83b rbx=0000000020e5e5b0 rcx=0000000000370707
rdx=00000000029b78a0 rsi=0000000000000000 rdi=0000000020e5e0c0
rip=000007fef5316d40 rsp=0000000020e5e680 rbp=000000001a30a2fc
 r8=0000000003707670  r9=000007fef2c94438 r10=00000000029b88b0
r11=0000000000000000 r12=0000000000000010 r13=000007fef2c94438
r14=0000000000291808 r15=0000000000001f60
iopl=0         nv up ei pl zr na po nc
cs=0033  ss=002b  ds=002b  es=002b  fs=0053  gs=002b             efl=00010244
clr!WKS::gc_heap::background_mark_simple1+0x5a1:
000007fe`f5316d40 41f70300000080  test    dword ptr [r11],80000000h ds:00000000`00000000=????????

0:024> ub 000007fe`f5316d40
clr!WKS::gc_heap::background_mark_simple1+0x584:
000007fe`f5316d23 48c1e809        shr     rax,9
000007fe`f5316d27 80e11f          and     cl,1Fh
000007fe`f5316d2a 41d3e3          shl     r11d,cl
000007fe`f5316d2d 44855c8500      test    dword ptr [rbp+rax*4],r11d
000007fe`f5316d32 7548            jne     clr!WKS::gc_heap::background_mark_simple1+0x5f3 (000007fe`f5316d7c)
000007fe`f5316d34 44095c8500      or      dword ptr [rbp+rax*4],r11d
000007fe`f5316d39 4d8b18          mov     r11,qword ptr [r8]
000007fe`f5316d3c 4983e3fe        and     r11,0FFFFFFFFFFFFFFFEh

从卦中数据来看,崩溃的原因是 r11=0 导致的,r11 的值又来自于 r8,这里有一个硬编码值
0FFFFFFFFFFFFFFFEh
,这个值在代码中应该是 -2 或者是 ~1 这两种情况,接下来观察下 background_mark_simple1() 的方法源码,尼玛。。。 还真给我找到了,简化后如下:


void gc_heap::background_mark_simple1(uint8_t * oo THREAD_NUMBER_DCL)
{
    ...
    uint8_t* start = oo;
    if ((size_t)oo & 1)
    {
        oo = (uint8_t*)((size_t)oo & ~1);
        start = *(--background_mark_stack_tos);
        dprintf(4, ("oo: %Ix, start: %Ix\n", (size_t)oo, (size_t)start));
    }
    ...
}

先简单解释下代码的意思,
oo & ~1
是将 oo 对象的
gcmark标记
给去掉,后面的 background_mark_stack_tos 就是深度优先遍历过程中使用的栈结构,再结合汇编代码,我们知道 r8 其实就是 oo,那这个 oo 到底是独立存在还是归属于谁呢?

4. oo 到底是何方神圣

要想知道这个答案,可以用
!lno
命令观察下 r8 附近内存来寻找蛛丝马迹,输出如下:


0:024> r r8
Last set context:
r8=0000000003707670

0:024> !lno 0000000003707670
Before:  0000000003707658           80 (0x50)	xxx.ComponentData
After:   00000000037076a8         1416 (0x588)	Free
Heap local consistency confirmed.

0:024> ? 0000000003707670 - 0000000003707658
Evaluate expression: 24 = 00000000`00000018

0:024> !DumpObj /d 0000000003707658
Name:        xxx.ComponentData
MethodTable: 000007fe95c0b058
EEClass:     000007fe95c2bbb0
Size:        80(0x50) bytes
File:        C:\xxxDriver.dll
Fields:
              MT    Field   Offset                 Type VT     Attr            Value Name
000007fef2cecf58  400008c       38      System.DateTime  1 instance 0000000003707690 wWfscFiguq
000007fef2cffc90  400008d       18        System.Double  1 instance 0.000000         I9ss25rNyt
000007fe95c0a888  400008e       20         System.Int32  1 instance                9 AxCs5sGCxm
000007fe95c0af00  400008f       24         System.Int32  1 instance                9 fK9sgqZ2qY
...

这个卦看起来就非常奇怪,无语了。。。 我苦苦寻找的
oo
对象居然是一个 int 类型的 fK9sgqZ2qY 字段,这不是乱套了吗? int 类型在这里也没有装箱,怎么可能会提取出 mt 呢? 真的是莫名奇怪。

5. int 为什么当 引用类型 了

线索到了这里越来越不可思议了,基本就可以断定这是 BGC 标记的错乱,直白的说就是 BGC 出现了 bug,再看下当前的操作系统和framework的版本,依次是 windows7 和 4.0 。


0:024> vertarget
Windows 7 Version 7601 (Service Pack 1) MP (4 procs) Free x64
Product: WinNt, suite: SingleUserTS
Edition build lab: kernel32.dll version: 6.1.7601.17514 (win7sp1_rtm.106666669-1850)

0:024> !eeversion
4.0.30319.18408 free
Workstation mode
In plan phase of garbage collection
SOS Version: 4.0.30319.18408 retail build

综合来说是 bgc 在老环境下做后台标记的时候出现了 bug,这种 bug 非我能力之所及,但我能做的就是把它切除掉,即把 bgc 模式给关掉,不走这个逻辑不就行了吗? 参考如下:


<configuration>
   <runtime>
      <gcConcurrent enabled="false"/>
   </runtime>
</configuration>

三:总结

很多时候分析下来发现是CLR内部的bug所致,这种真的很无奈,我能做的也只能是手术切除。

图片名称

Matlab进阶绘图

在这次的绘图练习中,我们需要考虑一次性将所有数据文件逐一读入,然后对每幅图图片进行放大处理。

参数设置

这里包括每幅图的标题,图例,读入文件的名称,等等

title_d   = {'SMOP1','SMOP3','SMOP7','SMOP8'};              %指定每幅图的标题
figname = {'SECSO','SPS','SNSGAII','LERD','TS2SparseEA'};   %指定需要读入的文件名
led = {'S-ECSO','SPS','S-NSGA-II','LERD','TS2-SparseEA'};   %指定每幅图片显示的图例(这里每幅图片包括多个曲线)
Marker = ['s','*','s','d','v'];                             %指定图片里每条曲线的形状
idx = [1,3,7,8];                                            %指定子文件夹名称,配合后面代码使用
dimension = {'1000d','3000d'};                              %指定子文件夹名称,配合后面代码使用

除此之外,还有一项关键内容,就是需要指定放大的区域。

xl = {[0.4 0.5], [0.4 0.5],[0.4 0.5],[0.4 0.5]};            %指定每幅图x轴放大的区域
yl = {[0.5 0.75], [0.5 0.75],[0.8 1.2], [0.8 1.5]};         %指定每幅图y轴放大的区域

至此我们完成大致准备工作
这里

数据导入

这里我们暂时只考虑一幅图片的数据导入,之后我会把完整代码(多文件数据读取)放出。这里我们的数据来源是Matlab中的Fig文件,当然如果你有直接数据,然后可以忽略这一操作步骤。

xdata = [];
ydata = [];
% 数据导入
for j = 1:5
    %打开Fig, ['./MyPlot/Objective_Value_Distribution/figs/TS2SparseEA/',dimension{d},'/SMOP',num2str(idx(i)),'/',figname{j},'.fig']表示把数据里的元素拼接成一个路径。
    %invisible表示打开图片时,不显示图片,若该为visible则会打开此图片(建议不要开)
    fig = openfig(['./MyPlot/Objective_Value_Distribution/figs/TS2SparseEA/',dimension{d},'/SMOP',num2str(idx(i)),'/',figname{j},'.fig'],'invisible');
    lines    = findobj(fig, 'type', 'line'); %找到fig里面所有的曲线
    xdata    = [xdata;get(lines, 'XData')];  %get(lines, 'XData'/'YData')表示获取所有曲线的横坐标和纵坐标
    ydata    = [ydata;get(lines, 'YData')];
    end

绘制全图

%由于我们要在一个fig里面绘制两幅图(原图和放大图),因此需要两个坐标轴
figure;
% 创建全图的坐标轴,并指定其位置'Position'
ax1 = axes('Position', [0.15 0.15 0.75 0.75]);

%设置当前轴为全图的坐标轴
axes(ax1);

%曲线绘制,共有5条,box on表示显示图的框线,可以自己去掉看看变化。hold on表示不创建新的fig继续在当前fig上绘制其他曲线。
for j = 1:5
    scatter(xdata(j, :), ydata(j, :),'Marker',Marker(j),'SizeData',60,LineWidth=1.5);
    box on;
    hold on;
end
title(title_d{i});

%设置当前图的字体等信息
set(gca,'FontName','Times New Roman','FontSize',17);

%设置xlabel和ylabel的文字,这里使用latex显示
xlabel('$f_1$', 'Interpreter', 'latex');
ylabel('$f_2$', 'Interpreter', 'latex');
%显示当前轴的图例
legend(led);

绘制放大图

有时候,由于原图里面各个曲线直接的距离很近,因此无法显出显著区别,在这种情况下,我们就需要对原图进行局部区域放大并绘制放大图以体现各曲线之间的细微区别。

%设置放大图的坐标轴
ax2 = axes('Position', [0.5 0.2 0.3 0.35]);
%将当前轴设置为放大轴
axes(ax2);
for j = 1: 5
    %找到符合x轴放大区域和y轴放大区域的x坐标下标
    zoom_in_idx = (xdata(j, :) >= xl{i}(1)) & (xdata(j, :) <= xl{i}(2)) & (ydata(j, :) >= yl{i}(1)) & (ydata(j, :) <= yl{i}(2));
    %得到x坐标和y坐标
    xdata_zoom_in{j} = xdata(j, zoom_in_idx);
    ydata_zoom_in{j} = ydata(j, zoom_in_idx);
end

%局部放大曲线绘制,hold off表示第一张fig已经画完了,下面再次绘图时会创建一个新的fig而不是当前的。
for j = 1:5
    scatter(xdata_zoom_in{j}, ydata_zoom_in{j},'Marker',Marker(j),'SizeData',150,LineWidth=1.5);
    box on;
    hold on;
end
%隐藏放大图的x轴和y轴,不显示其他信息,可以自己去掉看看变化。
set(gca, 'xtick',[], 'ytick',[]);
hold off;

最终效果展示

图片名称
图片名称
图片名称
图片名称

完整代码

clear;
clc;
% for TS2-SparseEA
title_d   = {'SMOP1','SMOP3','SMOP7','SMOP8'};
figname = {'SECSO','SPS','SNSGAII','LERD','TS2SparseEA'};
led = {'S-ECSO','SPS','S-NSGA-II','LERD','TS2-SparseEA'};
Marker = ['s','*','s','d','v'];
idx = [1,3,7,8];
dimension = {'1000d','3000d'};

% for TSD-MOEA
% title_d   = {'Sparse NN','Sparse SR','Sparse PO'};
% figname = {'MOEAPSL','SECSO','PMMOEA','TSDMOEA'};
% led = {'MOEA/PSL','S-ECSO','PM-MOEA','TSD-MOEA'};
% Marker = ['s','*','s','d'];
% color = {[214/255,232/255,121/255],[247/255,202/255,69/255],[82/255,198/255,160/255],[246/255,177/255,120/255]};
% idx = [1,3,7];
% dimension = {'3000d','RealWorld'};
% problem = {'NN','SR','PO'};
d = 2;

% zoom range
%self define
% TS2
xl = {[0.4 0.5], [0.4 0.5],[0.4 0.5],[0.4 0.5]};
yl = {[0.5 0.75], [0.5 0.75],[0.8 1.2], [0.8 1.5]};

% TSD
% xl = {[0 0.03], [0.05 0.1],[1.57e-7 8.33e-7]};
% yl = {[0.15 0.25], [0.78 0.82],[0.01 0.02]};
% label for each fig
% x_label = {'Complexity of neural network', 'Sparsity of reconstructed signal', 'Risk'};
% y_label = {'Training error', 'Loss of reconstructed signal', 'Return'};


for i = 1:4
    
    xdata = [];
    ydata = [];
    % 数据导入
    for j = 1:5
        fig = openfig(['./MyPlot/Objective_Value_Distribution/figs/TS2SparseEA/',dimension{d},'/SMOP',num2str(idx(i)),'/',figname{j},'.fig'],'invisible');
        % TSD
        % fig = openfig(['./MyPlot//Objective_Value_Distribution/figs/TSDMOEA/',dimension{d},'/',problem{i},'/',figname{j},'.fig'],'invisible');
        lines    = findobj(fig, 'type', 'line');
        xdata    = [xdata;get(lines, 'XData')];
        ydata    = [ydata;get(lines, 'YData')];
    end

    % 绘制全图
    figure;
    ax1 = axes('Position', [0.15 0.15 0.75 0.75]);
    axes(ax1);
    for j = 1:5
        scatter(xdata(j, :), ydata(j, :),'Marker',Marker(j),'SizeData',60,LineWidth=1.5);
        box on;
        hold on;
    end
    title(title_d{i});
    set(gca,'FontName','Times New Roman','FontSize',17);
    xlabel('$f_1$', 'Interpreter', 'latex');
    ylabel('$f_2$', 'Interpreter', 'latex');

    % TSD
    % xlabel(x_label{i});
    % ylabel(y_label{i});
    legend(led);


    % 绘制放大图
    ax2 = axes('Position', [0.5 0.2 0.3 0.35]);
    axes(ax2);

    % 设置要放大的x轴范围
    % 找到范围内的数据点
    for j = 1: 5
        zoom_in_idx = (xdata(j, :) >= xl{i}(1)) & (xdata(j, :) <= xl{i}(2)) & (ydata(j, :) >= yl{i}(1)) & (ydata(j, :) <= yl{i}(2));
        xdata_zoom_in{j} = xdata(j, zoom_in_idx);
        ydata_zoom_in{j} = ydata(j, zoom_in_idx);
    end

    for j = 1:5
        scatter(xdata_zoom_in{j}, ydata_zoom_in{j},'Marker',Marker(j),'SizeData',150,LineWidth=1.5);
        box on;
        hold on;
    end
    set(gca, 'xtick',[], 'ytick',[]);
    hold off;
end

前言

.NET生态中有许多依赖注入容器。在大多数情况下,微软提供的内置容器在易用性和性能方面都非常优秀。外加ASP.NET Core默认使用内置容器,使用很方便。

但是笔者在使用中一直有一个头疼的问题:服务工厂无法提供请求的服务类型相关的信息。这在一般情况下并没有影响,但是内置容器支持注册开放泛型服务,此时会导致无法实现某些需求。

ASP.NET Core目前推荐使用上下文池访问EF Core上下文,但是某些功能需要直接使用上下文(例如Identity Core)。官方文档建议使用自定义工厂通过上下文池获取上下文。这其实是一种服务转发(或委托),可以确保服务实例只有一个最终提供点,简化管理。

但是当希望转发的服务是开放泛型时就会出现问题。在实际请求服务时,无法通过自定义工厂得知请求的泛型服务的实际类型参数,也就无法实现对开放泛型类型的转发。官方仓库也有一个相关Issue:
Dependency Injection of Open Generics via factory #41050
。然而几年过去后微软依然没有打算解决这个问题。键控服务这种完全新增的功能都做了,这个举手之劳确一直放着,我不理解。一番研究后笔者确定可以通过简单的改造来实现支持,因此有了本篇文章。

新书宣传

有关新书的更多介绍欢迎查看
《C#与.NET6 开发从入门到实践》上市,作者亲自来打广告了!
image

正文

本来笔者打算使用继承来扩展功能,但是几经周折后发现微软把关键类型设置为内部类和密封类,彻底断了这条路。无奈只能克隆仓库直接改代码,好死不死这个库是运行时仓库的一部分,完整仓库包含大量无关代码,直接fork也会带来一堆麻烦,最后只能克隆仓库后复制需要的部分来修改。

CoreDX.Extensions.DependencyInjection.Abstractions

这是基础抽象包,用于扩展
ServiceDescriptor
为后续改造提供基础支持。

TypedImplementationFactoryServiceDescriptor

要实现能从自定义工厂获取服务类型的功能,自定义工厂需要一个
Type
类型的参数来传递类型信息,那么就需要
ServiceDescriptor
提供相应的构造方法重载。原始类型显然不可能,好在这是个普通公共类,可以继承,因此笔者继承内置类并扩展了相应的成员来承载工厂委托。

/// <inheritdoc />
[DebuggerDisplay("{DebuggerToString(),nq}")]
public class TypedImplementationFactoryServiceDescriptor : ServiceDescriptor
{
    private object? _typedImplementationFactory;

    /// <summary>
    /// Gets the typed factory used for creating service instances.
    /// </summary>
    public Func<IServiceProvider, Type, object>? TypedImplementationFactory
    {
        get
        {
            if (IsKeyedService)
            {
                throw new InvalidOperationException("This service descriptor is keyed. Your service provider may not support keyed services.");
            }
            return (Func<IServiceProvider, Type, object>?)_typedImplementationFactory;
        }
    }

    private object? _typedKeyedImplementationFactory;

    /// <summary>
    /// Gets the typed keyed factory used for creating service instances.
    /// </summary>
    public Func<IServiceProvider, object?, Type, object>? TypedKeyedImplementationFactory
    {
        get
        {
            if (!IsKeyedService)
            {
                throw new InvalidOperationException("This service descriptor is not keyed.");
            }
            return (Func<IServiceProvider, object?, Type, object>?)_typedKeyedImplementationFactory;
        }
    }

    /// <summary>
    /// Don't use this!
    /// </summary>
    /// <inheritdoc />
    public TypedImplementationFactoryServiceDescriptor(
        Type serviceType,
        object instance)
        : base(serviceType, instance)
    {
        ThrowCtor();
    }

    /// <summary>
    /// Don't use this!
    /// </summary>
    /// <inheritdoc />
    public TypedImplementationFactoryServiceDescriptor(
        Type serviceType,
        [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] Type implementationType,
        ServiceLifetime lifetime)
        : base(serviceType, implementationType, lifetime)
    {
        ThrowCtor();
    }

    /// <summary>
    /// Don't use this!
    /// </summary>
    /// <inheritdoc />
    public TypedImplementationFactoryServiceDescriptor(
        Type serviceType,
        object? serviceKey,
        object instance)
        : base(serviceType, serviceKey, instance)
    {
        ThrowCtor();
    }

    /// <summary>
    /// Don't use this!
    /// </summary>
    /// <inheritdoc />
    public TypedImplementationFactoryServiceDescriptor(
        Type serviceType,
        Func<IServiceProvider, object> factory,
        ServiceLifetime lifetime)
        : base(serviceType, factory, lifetime)
    {
        ThrowCtor();
    }

    /// <summary>
    /// Don't use this!
    /// </summary>
    /// <inheritdoc />
    public TypedImplementationFactoryServiceDescriptor(
        Type serviceType,
        object? serviceKey,
        [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] Type implementationType,
        ServiceLifetime lifetime)
        : base(serviceType, serviceKey, implementationType, lifetime)
    {
        ThrowCtor();
    }

    /// <summary>
    /// Don't use this!
    /// </summary>
    /// <inheritdoc />
    public TypedImplementationFactoryServiceDescriptor(
        Type serviceType,
        object? serviceKey,
        Func<IServiceProvider, object?, object> factory,
        ServiceLifetime lifetime)
        : base(serviceType, serviceKey, factory, lifetime)
    {
        ThrowCtor();
    }

    /// <summary>
    /// Initializes a new instance of <see cref="TypedImplementationFactoryServiceDescriptor"/> with the specified factory.
    /// </summary>
    /// <param name="serviceType">The <see cref="Type"/> of the service.</param>
    /// <param name="factory">A factory used for creating service instances. Requested service type is provided as argument in parameter of factory.</param>
    /// <param name="lifetime">The <see cref="ServiceLifetime"/> of the service.</param>
    /// <exception cref="ArgumentNullException"></exception>
    /// <inheritdoc />
    public TypedImplementationFactoryServiceDescriptor(
        Type serviceType,
        Func<IServiceProvider, Type, object> factory,
        ServiceLifetime lifetime)
        : base(serviceType, ThrowFactory, lifetime)
    {
        CheckOpenGeneric(serviceType);
        _typedImplementationFactory = factory ?? throw new ArgumentNullException(nameof(factory));
    }

    /// <summary>
    /// Initializes a new instance of <see cref="TypedImplementationFactoryServiceDescriptor"/> with the specified factory.
    /// </summary>
    /// <param name="serviceType">The <see cref="Type"/> of the service.</param>
    /// <param name="serviceKey">The <see cref="ServiceDescriptor.ServiceKey"/> of the service.</param>
    /// <param name="factory">A factory used for creating service instances. Requested service type is provided as argument in parameter of factory.</param>
    /// <param name="lifetime">The <see cref="ServiceLifetime"/> of the service.</param>
    /// <exception cref="ArgumentNullException"></exception>
    /// <inheritdoc />
    public TypedImplementationFactoryServiceDescriptor(
        Type serviceType,
        object? serviceKey,
        Func<IServiceProvider, object?, Type, object> factory,
        ServiceLifetime lifetime)
        : base(serviceType, serviceKey, ThrowKeyedFactory, lifetime)
    {
        CheckOpenGeneric(serviceType);
        _typedKeyedImplementationFactory = factory ?? throw new ArgumentNullException(nameof(factory));
    }

    /// <summary>
    /// Creates an instance of <see cref="TypedImplementationFactoryServiceDescriptor"/>
    /// with the specified service in <paramref name="implementationFactory"/> and the <see cref="ServiceLifetime.Singleton"/> lifetime.
    /// </summary>
    /// <param name="serviceType">The <see cref="Type"/> of the service.</param>
    /// <param name="implementationFactory">A factory used for creating service instances. Requested service type is provided as argument in parameter of factory.</param>
    /// <returns>A new instance of <see cref="TypedImplementationFactoryServiceDescriptor"/>.</returns>
    public static TypedImplementationFactoryServiceDescriptor Singleton(
        Type serviceType,
        Func<IServiceProvider, Type, object> implementationFactory)
    {
        return new(serviceType, implementationFactory, ServiceLifetime.Singleton);
    }

    /// <summary>
    /// Creates an instance of <see cref="TypedImplementationFactoryServiceDescriptor"/>
    /// with the specified service in <paramref name="implementationFactory"/> and the <see cref="ServiceLifetime.Singleton"/> lifetime.
    /// </summary>
    /// <typeparam name="TService">The type of the service.</typeparam>
    /// <param name="implementationFactory">A factory used for creating service instances. Requested service type is provided as argument in parameter of factory.</param>
    /// <returns>A new instance of <see cref="TypedImplementationFactoryServiceDescriptor"/>.</returns>
    public static TypedImplementationFactoryServiceDescriptor Singleton<TService>(
        Func<IServiceProvider, Type, object> implementationFactory)
        where TService : class
    {
        return new(typeof(TService), implementationFactory, ServiceLifetime.Singleton);
    }

    /// <summary>
    /// Creates an instance of <see cref="TypedImplementationFactoryServiceDescriptor"/>
    /// with the specified service in <paramref name="implementationType"/> and the <see cref="ServiceLifetime.Singleton"/> lifetime.
    /// </summary>
    /// <param name="serviceType">The <see cref="Type"/> of the service.</param>
    /// <param name="serviceKey">The <see cref="ServiceDescriptor.ServiceKey"/> of the service.</param>
    /// <param name="implementationType">A factory used for creating service instances. Requested service type is provided as argument in parameter of factory.</param>
    /// <returns>A new instance of <see cref="TypedImplementationFactoryServiceDescriptor"/>.</returns>
    public static TypedImplementationFactoryServiceDescriptor KeyedSingleton(
        Type serviceType,
        object? serviceKey,
        Func<IServiceProvider, object?, Type, object> implementationType)
    {
        return new(serviceType, serviceKey, implementationType, ServiceLifetime.Singleton);
    }

    /// <summary>
    /// Creates an instance of <see cref="TypedImplementationFactoryServiceDescriptor"/>
    /// with the specified service in <paramref name="implementationType"/> and the <see cref="ServiceLifetime.Singleton"/> lifetime.
    /// </summary>
    /// <typeparam name="TService">The type of the service.</typeparam>
    /// <param name="serviceKey">The <see cref="ServiceDescriptor.ServiceKey"/> of the service.</param>
    /// <param name="implementationType">A factory used for creating service instances. Requested service type is provided as argument in parameter of factory.</param>
    /// <returns>A new instance of <see cref="TypedImplementationFactoryServiceDescriptor"/>.</returns>
    public static TypedImplementationFactoryServiceDescriptor KeyedSingleton<TService>(
        object? serviceKey,
        Func<IServiceProvider, object?, Type, object> implementationType)
        where TService : class
    {
        return new(typeof(TService), serviceKey, implementationType, ServiceLifetime.Singleton);
    }

    /// <summary>
    /// Creates an instance of <see cref="TypedImplementationFactoryServiceDescriptor"/>
    /// with the specified service in <paramref name="implementationType"/> and the <see cref="ServiceLifetime.Scoped"/> lifetime.
    /// </summary>
    /// <param name="serviceType">The <see cref="Type"/> of the service.</param>
    /// <param name="implementationType">A factory used for creating service instances. Requested service type is provided as argument in parameter of factory.</param>
    /// <returns>A new instance of <see cref="TypedImplementationFactoryServiceDescriptor"/>.</returns>
    public static TypedImplementationFactoryServiceDescriptor Scoped(
        Type serviceType,
        Func<IServiceProvider, Type, object> implementationType)
    {
        return new(serviceType, implementationType, ServiceLifetime.Scoped);
    }

    /// <summary>
    /// Creates an instance of <see cref="TypedImplementationFactoryServiceDescriptor"/>
    /// with the specified service in <paramref name="implementationFactory"/> and the <see cref="ServiceLifetime.Scoped"/> lifetime.
    /// </summary>
    /// <typeparam name="TService">The type of the service.</typeparam>
    /// <param name="implementationFactory">A factory used for creating service instances. Requested service type is provided as argument in parameter of factory.</param>
    /// <returns>A new instance of <see cref="TypedImplementationFactoryServiceDescriptor"/>.</returns>
    public static TypedImplementationFactoryServiceDescriptor Scoped<TService>(
        Func<IServiceProvider, Type, object> implementationFactory)
        where TService : class
    {
        return new(typeof(TService), implementationFactory, ServiceLifetime.Scoped);
    }

    /// <summary>
    /// Creates an instance of <see cref="TypedImplementationFactoryServiceDescriptor"/>
    /// with the specified service in <paramref name="implementationType"/> and the <see cref="ServiceLifetime.Scoped"/> lifetime.
    /// </summary>
    /// <param name="serviceType">The <see cref="Type"/> of the service.</param>
    /// <param name="serviceKey">The <see cref="ServiceDescriptor.ServiceKey"/> of the service.</param>
    /// <param name="implementationType">A factory used for creating service instances. Requested service type is provided as argument in parameter of factory.</param>
    /// <returns>A new instance of <see cref="TypedImplementationFactoryServiceDescriptor"/>.</returns>
    public static TypedImplementationFactoryServiceDescriptor KeyedScoped(
        Type serviceType,
        object? serviceKey,
        Func<IServiceProvider, object?, Type, object> implementationType)
    {
        return new(serviceType, serviceKey, implementationType, ServiceLifetime.Scoped);
    }

    /// <summary>
    /// Creates an instance of <see cref="TypedImplementationFactoryServiceDescriptor"/>
    /// with the specified service in <paramref name="implementationType"/> and the <see cref="ServiceLifetime.Scoped"/> lifetime.
    /// </summary>
    /// <typeparam name="TService">The type of the service.</typeparam>
    /// <param name="serviceKey">The <see cref="ServiceDescriptor.ServiceKey"/> of the service.</param>
    /// <param name="implementationType">A factory used for creating service instances. Requested service type is provided as argument in parameter of factory.</param>
    /// <returns>A new instance of <see cref="TypedImplementationFactoryServiceDescriptor"/>.</returns>
    public static TypedImplementationFactoryServiceDescriptor KeyedScoped<TService>(
        object? serviceKey,
        Func<IServiceProvider, object?, Type, object> implementationType)
        where TService : class
    {
        return new(typeof(TService), serviceKey, implementationType, ServiceLifetime.Scoped);
    }

    /// <summary>
    /// Creates an instance of <see cref="TypedImplementationFactoryServiceDescriptor"/>
    /// with the specified service in <paramref name="implementationType"/> and the <see cref="ServiceLifetime.Transient"/> lifetime.
    /// </summary>
    /// <param name="serviceType">The <see cref="Type"/> of the service.</param>
    /// <param name="implementationType">A factory used for creating service instances. Requested service type is provided as argument in parameter of factory.</param>
    /// <returns>A new instance of <see cref="TypedImplementationFactoryServiceDescriptor"/>.</returns>
    public static TypedImplementationFactoryServiceDescriptor Transient(
        Type serviceType,
        Func<IServiceProvider, Type, object> implementationType)
    {
        return new(serviceType, implementationType, ServiceLifetime.Transient);
    }

    /// <summary>
    /// Creates an instance of <see cref="TypedImplementationFactoryServiceDescriptor"/>
    /// with the specified service in <paramref name="implementationFactory"/> and the <see cref="ServiceLifetime.Transient"/> lifetime.
    /// </summary>
    /// <typeparam name="TService">The type of the service.</typeparam>
    /// <param name="implementationFactory">A factory used for creating service instances. Requested service type is provided as argument in parameter of factory.</param>
    /// <returns>A new instance of <see cref="TypedImplementationFactoryServiceDescriptor"/>.</returns>
    public static TypedImplementationFactoryServiceDescriptor Transient<TService>(
        Func<IServiceProvider, Type, object> implementationFactory)
        where TService : class
    {
        return new(typeof(TService), implementationFactory, ServiceLifetime.Transient);
    }

    /// <summary>
    /// Creates an instance of <see cref="TypedImplementationFactoryServiceDescriptor"/>
    /// with the specified service in <paramref name="implementationType"/> and the <see cref="ServiceLifetime.Transient"/> lifetime.
    /// </summary>
    /// <param name="serviceType">The <see cref="Type"/> of the service.</param>
    /// <param name="serviceKey">The <see cref="ServiceDescriptor.ServiceKey"/> of the service.</param>
    /// <param name="implementationType">A factory used for creating service instances. Requested service type is provided as argument in parameter of factory.</param>
    /// <returns>A new instance of <see cref="TypedImplementationFactoryServiceDescriptor"/>.</returns>
    public static TypedImplementationFactoryServiceDescriptor KeyedTransient(
        Type serviceType,
        object? serviceKey,
        Func<IServiceProvider, object?, Type, object> implementationType)
    {
        return new(serviceType, serviceKey, implementationType, ServiceLifetime.Transient);
    }

    /// <summary>
    /// Creates an instance of <see cref="TypedImplementationFactoryServiceDescriptor"/>
    /// with the specified service in <paramref name="implementationType"/> and the <see cref="ServiceLifetime.Transient"/> lifetime.
    /// </summary>
    /// <typeparam name="TService">The type of the service.</typeparam>
    /// <param name="serviceKey">The <see cref="ServiceDescriptor.ServiceKey"/> of the service.</param>
    /// <param name="implementationType">A factory used for creating service instances. Requested service type is provided as argument in parameter of factory.</param>
    /// <returns>A new instance of <see cref="TypedImplementationFactoryServiceDescriptor"/>.</returns>
    public static TypedImplementationFactoryServiceDescriptor KeyedTransient<TService>(
        object? serviceKey,
        Func<IServiceProvider, object?, Type, object> implementationType)
        where TService : class
    {
        return new(typeof(TService), serviceKey, implementationType, ServiceLifetime.Transient);
    }

    private string DebuggerToString()
    {
        string text = $"Lifetime = {Lifetime}, ServiceType = \"{ServiceType.FullName}\"";
        if (IsKeyedService)
        {
            text += $", ServiceKey = \"{ServiceKey}\"";

            return text + $", TypedKeyedImplementationFactory = {TypedKeyedImplementationFactory!.Method}";
        }
        else
        {
            return text + $", TypedImplementationFactory = {TypedImplementationFactory!.Method}";
        }
    }

    private static void ThrowCtor()
    {
        throw new NotSupportedException($"{nameof(TypedImplementationFactoryServiceDescriptor)} only use for typed factory.");
    }

    private static object ThrowFactory(IServiceProvider serviceProvider)
    {
        throw new InvalidOperationException("Please use typed factory instead.");
    }

    private static object ThrowKeyedFactory(IServiceProvider serviceProvider, object? serviceKey)
    {
        throw new InvalidOperationException("Please use typed keyed factory instead.");
    }

    private static void CheckOpenGeneric(Type serviceType)
    {
        if (!serviceType.IsGenericTypeDefinition)
            throw new InvalidOperationException($"{nameof(TypedImplementationFactoryServiceDescriptor)} only used for generic type definition(open generic type).");
    }
}

这个类很简单,就是增加了用于保存
Func<IServiceProvider, Type, object>

Func<IServiceProvider, object?, Type, object>
工厂委托的字段和配套的构造方法和验证逻辑。基类提供的所有功能均直接抛出异常,专门负责新增功能。

ImplementationFactoryServiceTypeHolder

internal sealed class ImplementationFactoryServiceTypeHolder(Type serviceType)
{
    private readonly Func<IServiceProvider, object?> _factory = sp => sp.GetService(serviceType);

    public Func<IServiceProvider, object?> Factory => _factory;
}

internal sealed class KeyedImplementationFactoryServiceTypeHolder(Type serviceType)
{
    private readonly Func<IServiceProvider, object?, object?> _factory = (sp, key) => (sp as IKeyedServiceProvider)?.GetKeyedService(serviceType, key);

    public Func<IServiceProvider, object?, object?> Factory => _factory;
}

internal sealed class OpenGenericImplementationFactoryServiceTypeHolder(Type serviceType)
{
    private readonly Func<IServiceProvider, Type, object?> _factory = serviceType.IsGenericTypeDefinition
        ? (sp, type) =>
        {
            var closed = serviceType.MakeGenericType(type.GenericTypeArguments);
            return sp.GetService(closed);
        }
        : throw new ArgumentException($"{nameof(serviceType)} is not generic type definition.");

    public Func<IServiceProvider, Type, object?> Factory => _factory;
}

internal sealed class KeyedOpenGenericImplementationFactoryServiceTypeHolder(Type serviceType)
{
    private readonly Func<IServiceProvider, object?, Type, object?> _factory = serviceType.IsGenericTypeDefinition
        ? (sp, key, type) =>
        {
            var closed = serviceType.MakeGenericType(type.GenericTypeArguments);
            return (sp as IKeyedServiceProvider)?.GetKeyedService(closed, key);
        }
        : throw new ArgumentException($"{nameof(serviceType)} is not generic type definition.");

    public Func<IServiceProvider, object?, Type, object?> Factory => _factory;
}

这个类也很简单,只负责持有服务类型,并把新的工厂类型转换到原始工厂类型方便集成进内置容器。并且这是内部辅助类型,对开发者是无感知的。

易用性扩展

最后就是提供扩展方法提供和内置容器相似的使用体验。由于本次扩展的主要目的是实现开放发型的服务转发,因此笔者专门准备了一套用来注册服务转发的
AddForward
系列扩展方便使用。此处只列出部分预览。

/// <summary>
/// Adds a service of the type specified in <paramref name="serviceType"/> with a factory
/// specified in <paramref name="implementationFactory"/> to the specified <see cref="IServiceCollection"/>.
/// </summary>
/// <param name="services">The <see cref="IServiceCollection"/> to add the service to.</param>
/// <param name="serviceType">The type of the service to register.</param>
/// <param name="implementationFactory">The factory that creates the service.</param>
/// <param name="serviceLifetime">The <see cref="ServiceLifetime"/> of <paramref name="serviceType"/>.</param>
/// <returns>A reference to this instance after the operation has completed.</returns>
public static IServiceCollection AddTypedFactory(
    this IServiceCollection services,
    Type serviceType,
    Func<IServiceProvider, Type, object> implementationFactory,
    ServiceLifetime serviceLifetime)
{
    services.Add(new TypedImplementationFactoryServiceDescriptor(serviceType, implementationFactory, serviceLifetime));
    return services;
}

/// <summary>
/// Adds a service of the type specified in <paramref name="serviceType"/> with a factory
/// specified in <paramref name="implementationFactory"/> to the specified <see cref="IServiceCollection"/>.
/// </summary>
/// <param name="services">The <see cref="IServiceCollection"/> to add the service to.</param>
/// <param name="serviceType">The type of the service to register.</param>
/// <param name="serviceKey">The <see cref="ServiceDescriptor.ServiceKey"/> of the service.</param>
/// <param name="implementationFactory">The factory that creates the service.</param>
/// <param name="serviceLifetime">The <see cref="ServiceLifetime"/> of <paramref name="serviceType"/>.</param>
/// <returns>A reference to this instance after the operation has completed.</returns>
public static IServiceCollection AddKeyedTypedFactory(
    this IServiceCollection services,
    Type serviceType,
    object? serviceKey,
    Func<IServiceProvider, object?, Type, object> implementationFactory,
    ServiceLifetime serviceLifetime)
{
    services.Add(new TypedImplementationFactoryServiceDescriptor(serviceType, serviceKey, implementationFactory, serviceLifetime));
    return services;
}

/// <summary>
/// Adds a service of the type specified in <paramref name="serviceType"/> with a forward of the type
/// specified in <paramref name="forwardTargetServiceType"/> to the specified <see cref="IServiceCollection"/>.
/// </summary>
/// <param name="services">The <see cref="IServiceCollection"/> to add the service to.</param>
/// <param name="serviceType">The type of the service to register.</param>
/// <param name="serviceKey">The <see cref="ServiceDescriptor.ServiceKey"/> of the service.</param>
/// <param name="forwardTargetServiceType">The forward type of the service.</param>
/// <param name="serviceLifetime">The <see cref="ServiceLifetime"/> of <paramref name="serviceType"/>.</param>
/// <returns>A reference to this instance after the operation has completed.</returns>
public static IServiceCollection AddKeyedForward(
    this IServiceCollection services,
    Type serviceType,
    object? serviceKey,
    Type forwardTargetServiceType,
    ServiceLifetime serviceLifetime)
{
    ArgumentNullException.ThrowIfNull(services);
    ArgumentNullException.ThrowIfNull(serviceType);
    ArgumentNullException.ThrowIfNull(forwardTargetServiceType);

    if (serviceType.IsGenericTypeDefinition)
    {
        services.Add(new TypedImplementationFactoryServiceDescriptor(serviceType, serviceKey, new KeyedOpenGenericImplementationFactoryServiceTypeHolder(forwardTargetServiceType).Factory!, serviceLifetime));
    }
    else
    {
        services.Add(new ServiceDescriptor(serviceType, serviceKey, new KeyedImplementationFactoryServiceTypeHolder(forwardTargetServiceType).Factory!, serviceLifetime));
    }

    return services;
}

从示例可以发现如果类型不是开放泛型,是直接使用原始类型进行注册的。也就是说如果安装这个抽象包,但是不使用开放泛型的相关功能,是可以直接用原始内置容器的。

CoreDX.Extensions.DependencyInjection

这是修改后的服务容器实现,增加了对带服务类型的自定义工厂的支持,其他内置功能完全不变。

CallSiteFactory

internal sealed partial class CallSiteFactory : IServiceProviderIsService, IServiceProviderIsKeyedService
{
    // 其他原始代码

    private void Populate()
    {
        foreach (ServiceDescriptor descriptor in _descriptors)
        {
            Type serviceType = descriptor.ServiceType;

            #region 验证可识别请求类型的服务实现工厂

            if (descriptor is TypedImplementationFactoryServiceDescriptor typedFactoryDescriptor)
            {
                if(typedFactoryDescriptor.IsKeyedService && typedFactoryDescriptor.TypedKeyedImplementationFactory == null)
                {
                    throw new ArgumentException(
                        $"Keyed open generic service {serviceType} requires {nameof(typedFactoryDescriptor.TypedKeyedImplementationFactory)}",
                        "descriptors");
                }
                else if (!typedFactoryDescriptor.IsKeyedService && typedFactoryDescriptor.TypedImplementationFactory == null)
                {
                    throw new ArgumentException(
                        $"Open generic service {serviceType} requires {nameof(typedFactoryDescriptor.TypedImplementationFactory)}",
                        "descriptors");
                }
            }

            #endregion

            // 其他原始代码
        }
    }

    private ServiceCallSite? TryCreateExact(ServiceDescriptor descriptor, ServiceIdentifier serviceIdentifier, CallSiteChain callSiteChain, int slot)
    {
        if (serviceIdentifier.ServiceType == descriptor.ServiceType)
        {
            ServiceCacheKey callSiteKey = new ServiceCacheKey(serviceIdentifier, slot);
            if (_callSiteCache.TryGetValue(callSiteKey, out ServiceCallSite? serviceCallSite))
            {
                return serviceCallSite;
            }

            ServiceCallSite callSite;
            var lifetime = new ResultCache(descriptor.Lifetime, serviceIdentifier, slot);

            // 其他原始代码

            #region 为可识别请求类型的服务工厂注册服务实现工厂

            else if (TryCreateTypedFactoryCallSite(lifetime, descriptor as TypedImplementationFactoryServiceDescriptor, descriptor.ServiceType) is ServiceCallSite factoryCallSite)
            {
                callSite = factoryCallSite;
            }

            #endregion

            // 其他原始代码

            return _callSiteCache[callSiteKey] = callSite;
        }

        return null;
    }

    [UnconditionalSuppressMessage("ReflectionAnalysis", "IL2055:MakeGenericType",
        Justification = "MakeGenericType here is used to create a closed generic implementation type given the closed service type. " +
        "Trimming annotations on the generic types are verified when 'Microsoft.Extensions.DependencyInjection.VerifyOpenGenericServiceTrimmability' is set, which is set by default when PublishTrimmed=true. " +
        "That check informs developers when these generic types don't have compatible trimming annotations.")]
    [UnconditionalSuppressMessage("AotAnalysis", "IL3050:RequiresDynamicCode",
        Justification = "When ServiceProvider.VerifyAotCompatibility is true, which it is by default when PublishAot=true, " +
        "this method ensures the generic types being created aren't using ValueTypes.")]
    private ServiceCallSite? TryCreateOpenGeneric(ServiceDescriptor descriptor, ServiceIdentifier serviceIdentifier, CallSiteChain callSiteChain, int slot, bool throwOnConstraintViolation)
    {
        if (serviceIdentifier.IsConstructedGenericType &&
            serviceIdentifier.ServiceType.GetGenericTypeDefinition() == descriptor.ServiceType)
        {
            ServiceCacheKey callSiteKey = new ServiceCacheKey(serviceIdentifier, slot);
            if (_callSiteCache.TryGetValue(callSiteKey, out ServiceCallSite? serviceCallSite))
            {
                return serviceCallSite;
            }

            Type? implementationType = descriptor.GetImplementationType();
            //Debug.Assert(implementationType != null, "descriptor.ImplementationType != null"); // 延迟断言,此处可能是开放泛型工厂
            var lifetime = new ResultCache(descriptor.Lifetime, serviceIdentifier, slot);
            Type closedType;
            try
            {
                Type[] genericTypeArguments = serviceIdentifier.ServiceType.GenericTypeArguments;
                if (TypedImplementationFactoryServiceProvider.VerifyAotCompatibility)
                {
                    VerifyOpenGenericAotCompatibility(serviceIdentifier.ServiceType, genericTypeArguments);
                }

                #region 为开放式泛型服务添加可识别请求类型的服务实现工厂

                if (descriptor is TypedImplementationFactoryServiceDescriptor typedFactoryDescriptor)
                {
                    closedType = typedFactoryDescriptor.ServiceType.MakeGenericType(genericTypeArguments);
                    if (TryCreateTypedFactoryCallSite(lifetime, typedFactoryDescriptor, closedType) is ServiceCallSite factoryCallSite)
                    {
                        return _callSiteCache[callSiteKey] = factoryCallSite;
                    }
                    else
                    {
                        return null;
                    }
                }

                // 断言移动到此处
                Debug.Assert(implementationType != null, "descriptor.ImplementationType != null");

                #endregion

                closedType = implementationType.MakeGenericType(genericTypeArguments);
            }
            catch (ArgumentException)
            {
                if (throwOnConstraintViolation)
                {
                    throw;
                }

                return null;
            }

            return _callSiteCache[callSiteKey] = CreateConstructorCallSite(lifetime, serviceIdentifier, closedType, callSiteChain);
        }

        return null;
    }

    // 其他原始代码
}

这是整个改造的关键,理论上来说只要这个类是普通类的话完全可以直接通过继承把功能加上,可惜不是。此处只展示修改的部分。然后把辅助方法定义到另一个文件,方便利用部分类的特点尽量减少对原始代码的改动,方便将来同步官方代码。

internal sealed partial class CallSiteFactory
{
	/// <summary>
	/// 尝试创建可识别请求类型的工厂调用点
	/// </summary>
	/// <param name="lifetime"></param>
	/// <param name="descriptor"></param>
	/// <param name="serviceType">服务类型</param>
	/// <returns></returns>
	private static FactoryCallSite? TryCreateTypedFactoryCallSite(
		ResultCache lifetime,
		TypedImplementationFactoryServiceDescriptor? descriptor,
		Type serviceType)
	{
        ArgumentNullException.ThrowIfNull(serviceType);

        if (descriptor == null) { }
		else if (descriptor.IsKeyedService && descriptor.TypedKeyedImplementationFactory != null)
		{
			return new FactoryCallSite(lifetime, descriptor.ServiceType, descriptor.ServiceKey!, new TypedKeyedServiceImplementationFactoryHolder(descriptor.TypedKeyedImplementationFactory!, serviceType).Factory);
		}
		else if (!descriptor.IsKeyedService && descriptor.TypedImplementationFactory != null)
        {
			return new FactoryCallSite(lifetime, descriptor.ServiceType, new TypedServiceImplementationFactoryHolder(descriptor.TypedImplementationFactory!, serviceType).Factory);
		}

        return null;
	}
}

TypedServiceImplementationFactoryHolder

internal sealed class TypedServiceImplementationFactoryHolder
{
    private readonly Func<IServiceProvider, Type, object> _factory;
    private readonly Type _serviceType;

    internal TypedServiceImplementationFactoryHolder(Func<IServiceProvider, Type, object> factory, Type serviceType)
    {
        _factory = factory ?? throw new ArgumentNullException(nameof(factory));
        _serviceType = serviceType ?? throw new ArgumentNullException(nameof(serviceType));
    }

    internal Func<IServiceProvider, object> Factory => FactoryFunc;

    private object FactoryFunc(IServiceProvider provider)
    {
        return _factory(provider, _serviceType);
    }
}

internal sealed class TypedKeyedServiceImplementationFactoryHolder
{
    private readonly Func<IServiceProvider, object?, Type, object> _factory;
    private readonly Type _serviceType;

    internal TypedKeyedServiceImplementationFactoryHolder(Func<IServiceProvider, object?, Type, object> factory, Type serviceType)
    {
        _factory = factory ?? throw new ArgumentNullException(nameof(factory));
        _serviceType = serviceType ?? throw new ArgumentNullException(nameof(serviceType));
    }

    internal Func<IServiceProvider, object?, object> Factory => FactoryFunc;

    private object FactoryFunc(IServiceProvider provider, object? serviceKey)
    {
        return _factory(provider, serviceKey, _serviceType);
    }
}

因为笔者直接使用了内置类型,因此需要把工厂委托转换成内置容器支持的签名。Holder辅助类就可以把类型信息保存为内部字段,对外暴露的工厂签名就可以不需要类型参数了。

最后为避免引起误解,笔者修改了类名,但保留文件名方便比对原始仓库代码。至此,改造其实已经完成。可以看出改动真的很少,不知道为什么微软就是不改。

CoreDX.Extensions.DependencyInjection.Hosting.Abstractions

虽然经过上面的改造后,改版容器已经能用了,但是为了方便和通用主机系统集成还是要提供一个替换容器用的扩展。

TypedImplementationFactoryHostingHostBuilderExtensions

public static class TypedImplementationFactoryHostingHostBuilderExtensions
{
    /// <summary>
    /// Specify the <see cref="IServiceProvider"/> to be the typed implementation factory supported one.
    /// </summary>
    /// <param name="hostBuilder">The <see cref="IHostBuilder"/> to configure.</param>
    /// <returns>The <see cref="IHostBuilder"/>.</returns>
    public static IHostBuilder UseTypedImplementationFactoryServiceProvider(
        this IHostBuilder hostBuilder)
        => hostBuilder.UseTypedImplementationFactoryServiceProvider(static _ => { });

    /// <summary>
    /// Specify the <see cref="IServiceProvider"/> to be the typed implementation factory supported one.
    /// </summary>
    /// <param name="hostBuilder">The <see cref="IHostBuilder"/> to configure.</param>
    /// <param name="configure">The delegate that configures the <see cref="IServiceProvider"/>.</param>
    /// <returns>The <see cref="IHostBuilder"/>.</returns>
    public static IHostBuilder UseTypedImplementationFactoryServiceProvider(
        this IHostBuilder hostBuilder,
        Action<ServiceProviderOptions> configure)
        => hostBuilder.UseTypedImplementationFactoryServiceProvider((context, options) => configure(options));

    /// <summary>
    /// Specify the <see cref="IServiceProvider"/> to be the typed implementation factory supported one.
    /// </summary>
    /// <param name="hostBuilder">The <see cref="IHostBuilder"/> to configure.</param>
    /// <param name="configure">The delegate that configures the <see cref="IServiceProvider"/>.</param>
    /// <returns>The <see cref="IHostBuilder"/>.</returns>
    public static IHostBuilder UseTypedImplementationFactoryServiceProvider(
        this IHostBuilder hostBuilder,
        Action<HostBuilderContext, ServiceProviderOptions> configure)
    {
        return hostBuilder.UseServiceProviderFactory(context =>
        {
            var options = new ServiceProviderOptions();
            configure(context, options);
            return new TypedImplementationFactoryServiceProviderFactory(options);
        });
    }
}

至此,主机集成工作也完成了。本来打算就这么结束的,结果突然想起来,开放泛型问题解决了,键控服务也有了,之前一直不知道怎么办的动态代理貌似是有戏了,就又研究起来了。

CoreDX.Extensions.DependencyInjection.Proxies.Abstractions

之前动态代理不好实现主要是因为代理服务和原始服务的注册类型相同,实在是没办法。既然现在有键控服务了,那么把原始服务和代理服务用键分开就完美搞定,最后一个问题就是键要怎么处理。通过文档可知键控服务的键可以是任意
object
,只要实现合理的相等性判断即可。因此笔者决定使用专用的类型来表示代理服务的键,并通过对
string
类型的特殊处理来实现特性键指定的兼容。

ImplicitProxyServiceOriginalServiceKey

/// <summary>
/// Service key for access original service that already added as implicit proxy.
/// </summary>
public sealed class ImplicitProxyServiceOriginalServiceKey
    : IEquatable<ImplicitProxyServiceOriginalServiceKey>
#if NET7_0_OR_GREATER
    , IEqualityOperators<ImplicitProxyServiceOriginalServiceKey, ImplicitProxyServiceOriginalServiceKey, bool>
    , IEqualityOperators<ImplicitProxyServiceOriginalServiceKey, object, bool>
#endif
{
    private const int _hashCodeBase = 870983858;

    private readonly bool _isStringMode;
    private readonly object? _originalServiceKey;

    private static readonly ImplicitProxyServiceOriginalServiceKey _default = CreateOriginalServiceKey(null);
    private static readonly ImplicitProxyServiceOriginalServiceKey _stringDefault = CreateStringOriginalServiceKey(null);

    /// <summary>
    /// Prefix for access original <see cref="string"/> based keyed service that already added as implicit proxy.
    /// </summary>
    public const string DefaultStringPrefix = $"[{nameof(CoreDX)}.{nameof(Extensions)}.{nameof(DependencyInjection)}.{nameof(Proxies)}.{nameof(ImplicitProxyServiceOriginalServiceKey)}](ImplicitDefault)";

    /// <summary>
    /// Default original service key for none keyed proxy service.
    /// </summary>
    public static ImplicitProxyServiceOriginalServiceKey Default => _default;

    /// <summary>
    /// Default original service key for none <see cref="string"/> based keyed proxy service.
    /// </summary>
    public static ImplicitProxyServiceOriginalServiceKey StringDefault => _stringDefault;

    /// <summary>
    /// Service key of original service.
    /// </summary>
    public object? OriginalServiceKey => _originalServiceKey;

    public bool Equals(ImplicitProxyServiceOriginalServiceKey? other)
    {
        return Equals((object?)other);
    }

    public override bool Equals(object? obj)
    {
        if (_isStringMode && obj is string str) return $"{DefaultStringPrefix}{_originalServiceKey}" == str;
        else
        {
            var isEquals = obj is not null and ImplicitProxyServiceOriginalServiceKey other
            && ((_originalServiceKey is null && other._originalServiceKey is null) || _originalServiceKey?.Equals(other._originalServiceKey) is true);

            return isEquals;
        }
    }

    public static bool operator ==(ImplicitProxyServiceOriginalServiceKey? left, ImplicitProxyServiceOriginalServiceKey? right)
    {
        return left?.Equals(right) is true;
    }

    public static bool operator !=(ImplicitProxyServiceOriginalServiceKey? left, ImplicitProxyServiceOriginalServiceKey? right)
    {
        return !(left == right);
    }

    public static bool operator ==(ImplicitProxyServiceOriginalServiceKey? left, object? right)
    {
        return left?.Equals(right) is true;
    }

    public static bool operator !=(ImplicitProxyServiceOriginalServiceKey? left, object? right)
    {
        return !(left == right);
    }

    public static bool operator ==(object? left, ImplicitProxyServiceOriginalServiceKey? right)
    {
        return right == left;
    }

    public static bool operator !=(object? left, ImplicitProxyServiceOriginalServiceKey? right)
    {
        return right != left;
    }

    public override int GetHashCode()
    {
        return _isStringMode
            ? $"{DefaultStringPrefix}{_originalServiceKey}".GetHashCode()
            : HashCode.Combine(_hashCodeBase, _originalServiceKey);
    }

    /// <summary>
    /// Creates an instance of <see cref="ImplicitProxyServiceOriginalServiceKey"/> with the specified service key in <paramref name="originalServiceKey"/>.
    /// </summary>
    /// <param name="originalServiceKey"></param>
    /// <returns>A new instance of <see cref="ImplicitProxyServiceOriginalServiceKey"/>.</returns>
    public static ImplicitProxyServiceOriginalServiceKey CreateOriginalServiceKey(object? originalServiceKey)
    {
        return new(originalServiceKey, false);
    }

    /// <summary>
    /// Creates an instance of <see cref="ImplicitProxyServiceOriginalServiceKey"/> with the specified <see cref="string"/> based service key in <paramref name="originalServiceKey"/>.
    /// </summary>
    /// <param name="originalServiceKey"></param>
    /// <returns>A new instance of <see cref="ImplicitProxyServiceOriginalServiceKey"/>.</returns>
    public static ImplicitProxyServiceOriginalServiceKey CreateStringOriginalServiceKey(string? originalServiceKey)
    {
        return new(originalServiceKey, true);
    }

    private ImplicitProxyServiceOriginalServiceKey(object? originalServiceKey, bool isStringMode)
    {
        _originalServiceKey = originalServiceKey;
        _isStringMode = isStringMode;
    }
}

对.NET 7以上版本,把运算符实现为接口。

ProxyService

/// <summary>
/// The interface for get explicit proxy service. 
/// </summary>
/// <typeparam name="TService">The type of original service to get explicit proxy.</typeparam>
public interface IProxyService<out TService>
    where TService : class
{
    /// <summary>
    /// Get proxy service instance of type <typeparamref name="TService"/>.
    /// </summary>
    TService Proxy { get; }
}

/// <summary>
/// The type for get explicit proxy service. 
/// </summary>
/// <typeparam name="TService">The type of original service to get explicit proxy.</typeparam>
/// <param name="service">Object instance of original service to be proxy.</param>
internal sealed class ProxyService<TService>(TService service) : IProxyService<TService>
    where TService : class
{
    public TService Proxy { get; } = service;
}

除了隐式代理,笔者还准备了显式代理,这也是笔者要在内置容器上扩展而不是去用其他第三方容器的一个原因。第三方容器代理后原始服务就被隐藏了,在某些极端情况下万一要用到原始服务就没办法了。

CastleDynamicProxyDependencyInjectionExtensions

此处只展示部分预览。

/// <summary>
/// Adds a explicit proxy for the type specified in <paramref name="serviceType"/> with interceptors
/// specified in <paramref name="interceptorTypes"/> to the specified <see cref="IServiceCollection"/>.
/// </summary>
/// <param name="services">The <see cref="IServiceCollection"/> to add the service proxy to.</param>
/// <param name="serviceKey">The <see cref="ServiceDescriptor.ServiceKey"/> of the service.</param>
/// <param name="serviceType">The type of the service to add proxy.</param>
/// <param name="serviceLifetime">The <see cref="ServiceLifetime"/> of <paramref name="serviceType"/> and <paramref name="interceptorTypes"/>.</param>
/// <param name="interceptorTypes">The interceptor types of the service proxy.</param>
/// <returns>A reference to this instance after the operation has completed.</returns>
/// <remarks>Use <see cref="IProxyService{TService}"/> to get proxy service.</remarks>
public static IServiceCollection AddKeyedExplicitProxy(
    this IServiceCollection services,
    Type serviceType,
    object? serviceKey,
    ServiceLifetime serviceLifetime,
    params Type[] interceptorTypes)
{
    ArgumentNullException.ThrowIfNull(services);
    ArgumentNullException.ThrowIfNull(serviceType);
    CheckInterface(serviceType);
    CheckInterceptor(interceptorTypes);

    if (serviceType.IsGenericTypeDefinition)
    {
        services.TryAddKeyedSingleton<IStartupOpenGenericServiceProxyRegister>(serviceKey, new StartupOpenGenericServiceProxyRegister());

        var startupOpenGenericServiceProxyRegister = services
            .LastOrDefault(service => service.IsKeyedService && service.ServiceKey == serviceKey && service.ServiceType == typeof(IStartupOpenGenericServiceProxyRegister))
            ?.KeyedImplementationInstance as IStartupOpenGenericServiceProxyRegister
            ?? throw new InvalidOperationException($"Can not found keyed(key value: {serviceKey}) service of type {nameof(IStartupOpenGenericServiceProxyRegister)}");

        startupOpenGenericServiceProxyRegister?.Add(serviceType);

        services.TryAdd(new TypedImplementationFactoryServiceDescriptor(
            typeof(IProxyService<>),
            serviceKey,
            (provider, serviceKey, requestedServiceType) =>
            {
                var proxyServiceType = requestedServiceType.GenericTypeArguments[0];

                var registered = CheckKeyedOpenGenericServiceProxyRegister(provider, serviceKey, proxyServiceType.GetGenericTypeDefinition());
                if (!registered) return null!;

                var proxy = CreateKeyedProxyObject(provider, proxyServiceType, serviceKey, interceptorTypes);

                return Activator.CreateInstance(typeof(ProxyService<>).MakeGenericType(proxy.GetType()), proxy)!;
            },
            serviceLifetime));
    }
    else
    {
        services.Add(new ServiceDescriptor(
            typeof(IProxyService<>).MakeGenericType(serviceType),
            serviceKey,
            (provider, serviceKey) =>
            {
                var proxy = CreateKeyedProxyObject(provider, serviceType, serviceKey, interceptorTypes);
                return Activator.CreateInstance(typeof(ProxyService<>).MakeGenericType(proxy.GetType()), proxy)!;
            },
            serviceLifetime));
    }

    services.TryAddKeyedInterceptors(serviceKey, serviceLifetime, interceptorTypes);

    return services;
}

/// <summary>
/// Adds a implicit proxy for the type specified in <paramref name="serviceType"/> with interceptors
/// specified in <paramref name="interceptorTypes"/> to the specified <see cref="IServiceCollection"/>.
/// </summary>
/// <param name="services">The <see cref="IServiceCollection"/> to add the service proxy to.</param>
/// <param name="serviceType">The type of the service to add proxy.</param>
/// <param name="serviceKey">The <see cref="ServiceDescriptor.ServiceKey"/> of the service.</param>
/// <param name="serviceLifetime">The <see cref="ServiceLifetime"/> of <paramref name="serviceType"/> and <paramref name="interceptorTypes"/>.</param>
/// <param name="interceptorTypes">The interceptor types of the service proxy.</param>
/// <returns>A reference to this instance after the operation has completed.</returns>
/// <remarks>
/// Use key <see cref="ImplicitProxyServiceOriginalServiceKey.CreateOriginalServiceKey(object?)"/>
/// or <see cref="ImplicitProxyServiceOriginalServiceKey.CreateStringOriginalServiceKey(string?)"/> if <paramref name="serviceKey"/> is <see cref="string"/>
/// or <see cref="ImplicitProxyServiceOriginalServiceKey.DefaultStringPrefix"/> + <paramref name="serviceKey"/> if <paramref name="serviceKey"/>
/// is <see cref="string"/>(eg. Constant value for <see cref="FromKeyedServicesAttribute"/>.) to get original service.
/// </remarks>
public static IServiceCollection AddKeyedImplicitProxy(
    this IServiceCollection services,
    Type serviceType,
    object? serviceKey,
    ServiceLifetime serviceLifetime,
    params Type[] interceptorTypes)
{
    ArgumentNullException.ThrowIfNull(services);
    ArgumentNullException.ThrowIfNull(serviceType);
    CheckInterface(serviceType);
    CheckInterceptor(interceptorTypes);

    var originalServiceDescriptor = services.LastOrDefault(service => service.IsKeyedService && service.ServiceKey == serviceKey && service.ServiceType == serviceType && service.Lifetime == serviceLifetime)
        ?? throw new ArgumentException($"Not found registered keyed(key value: {serviceKey}) \"{Enum.GetName(serviceLifetime)}\" service of type {serviceType.Name}.", nameof(serviceType));

    var newServiceKey = CreateOriginalServiceKey(serviceKey);
    var serviceDescriptorIndex = services.IndexOf(originalServiceDescriptor);
    if (originalServiceDescriptor is TypedImplementationFactoryServiceDescriptor typedServiceDescriptor)
    {
        services.Insert(
            serviceDescriptorIndex,
            new TypedImplementationFactoryServiceDescriptor(
                typedServiceDescriptor.ServiceType,
                newServiceKey,
                (serviceProvider, serviceKey, requestedServiceType) =>
                {
                    Debug.Assert(serviceKey is ImplicitProxyServiceOriginalServiceKey, $"Implicit proxy not use {nameof(ImplicitProxyServiceOriginalServiceKey)}");

                    return typedServiceDescriptor.TypedKeyedImplementationFactory!(
                        serviceProvider,
                        (serviceKey as ImplicitProxyServiceOriginalServiceKey)?.OriginalServiceKey ?? serviceKey,
                        requestedServiceType);
                },
                originalServiceDescriptor.Lifetime)
            );
    }
    else if (originalServiceDescriptor.KeyedImplementationInstance is not null)
    {
        services.Insert(
            serviceDescriptorIndex,
            new ServiceDescriptor(
                originalServiceDescriptor.ServiceType,
                newServiceKey,
                originalServiceDescriptor.KeyedImplementationInstance)
            );
    }
    else if (originalServiceDescriptor.KeyedImplementationType is not null)
    {
        services.Insert(
            serviceDescriptorIndex,
            new ServiceDescriptor(
                originalServiceDescriptor.ServiceType,
                newServiceKey,
                originalServiceDescriptor.KeyedImplementationType,
                originalServiceDescriptor.Lifetime)
            );
    }
    else if (originalServiceDescriptor.KeyedImplementationFactory is not null)
    {
        services.Insert(
            serviceDescriptorIndex,
            new ServiceDescriptor(
                originalServiceDescriptor.ServiceType,
                newServiceKey,
                (serviceProvider, serviceKey) =>
                {
                    return originalServiceDescriptor.KeyedImplementationFactory(
                        serviceProvider,
                        serviceKey);
                },
                originalServiceDescriptor.Lifetime)
            );
    }
    else throw new Exception("Add proxy service fail.");

    if (serviceType.IsGenericTypeDefinition)
    {
        services.Add(new TypedImplementationFactoryServiceDescriptor(
            serviceType,
            serviceKey,
            (provider, serviceKey, requestedServiceType) =>
            {
                var newLocalServiceKey = CreateOriginalServiceKey(serviceKey);
                var proxy = CreateKeyedProxyObject(provider, requestedServiceType, newLocalServiceKey, interceptorTypes);

                return proxy;
            },
            serviceLifetime));
    }
    else
    {
        services.Add(new ServiceDescriptor(
            serviceType,
            serviceKey,
            (provider, serviceKey) =>
            {
                var newLocalServiceKey = CreateOriginalServiceKey(serviceKey);
                var proxy = CreateKeyedProxyObject(provider, serviceType, newLocalServiceKey, interceptorTypes);

                return proxy;
            },
            serviceLifetime));
    }

    services.TryAddKeyedInterceptors(newServiceKey, serviceLifetime, interceptorTypes);

    services.Remove(originalServiceDescriptor);

    return services;
}

    /// <summary>
    /// Solidify open generic service proxy register for the specified <see cref="IServiceCollection"/>.
    /// </summary>
    /// <param name="containerBuilder">The <see cref="IServiceCollection"/> to solidify register.</param>
    /// <remarks>Should call after last add proxy. If used for host, needn't call.</remarks>
    public static void SolidifyOpenGenericServiceProxyRegister(this IServiceCollection containerBuilder)
    {
        var openGenericServiceProxyRegisters = containerBuilder
            .Where(service => service.ServiceType == typeof(IStartupOpenGenericServiceProxyRegister))
            .ToList();

        var readOnlyOpenGenericServiceProxyRegisters = openGenericServiceProxyRegisters
            .Where(service => service.Lifetime == ServiceLifetime.Singleton)
            .Select(service =>
            {
                return service.IsKeyedService switch
                {
                    true => ServiceDescriptor.KeyedSingleton<IOpenGenericServiceProxyRegister>(service.ServiceKey, new OpenGenericServiceProxyRegister((service.KeyedImplementationInstance! as IStartupOpenGenericServiceProxyRegister)!)),
                    false => ServiceDescriptor.Singleton<IOpenGenericServiceProxyRegister>(new OpenGenericServiceProxyRegister((service.ImplementationInstance! as IStartupOpenGenericServiceProxyRegister)!)),
                };
            });

        foreach (var register in openGenericServiceProxyRegisters)
        {
            containerBuilder.Remove(register);
        }

        foreach (var readOnlyRegister in readOnlyOpenGenericServiceProxyRegisters)
        {
            containerBuilder.Add(readOnlyRegister);
        }
    }

    private static object CreateProxyObject(
        IServiceProvider provider,
        Type serviceType,
        Type[] interceptorTypes)
    {
        var target = provider.GetRequiredService(serviceType);
        var interceptors = interceptorTypes.Select(t => GetInterceptor(provider.GetRequiredService(t))).ToArray();
        var proxyGenerator = provider.GetRequiredService<IProxyGenerator>();

        var proxy = proxyGenerator.CreateInterfaceProxyWithTarget(serviceType, target, interceptors);
        return proxy;
    }

    private static object CreateKeyedProxyObject(
        IServiceProvider provider,
        Type serviceType,
        object? serviceKey,
        Type[] interceptorTypes)
    {
        var target = provider.GetRequiredKeyedService(serviceType, serviceKey);
        var interceptors = interceptorTypes.Select(t => GetInterceptor(provider.GetRequiredKeyedService(t, serviceKey))).ToArray();
        var proxyGenerator = provider.GetRequiredService<IProxyGenerator>();

        var proxy = proxyGenerator.CreateInterfaceProxyWithTarget(serviceType, target, interceptors);
        return proxy;
    }

    private static ImplicitProxyServiceOriginalServiceKey CreateOriginalServiceKey(object? serviceKey)
    {
        return serviceKey switch
        {
            string stringKey => ImplicitProxyServiceOriginalServiceKey.CreateStringOriginalServiceKey(stringKey),
            _ => ImplicitProxyServiceOriginalServiceKey.CreateOriginalServiceKey(serviceKey)
        };
    }

    private static void TryAddInterceptors(
        this IServiceCollection services,
        ServiceLifetime lifetime,
        params Type[] interceptorTypes)
    {
        services.TryAddSingleton<IProxyGenerator, ProxyGenerator>();

        foreach (var interceptorType in interceptorTypes)
        {
            services.TryAdd(new ServiceDescriptor(interceptorType, interceptorType, lifetime));
        }
    }

    private static void TryAddKeyedInterceptors(
        this IServiceCollection services,
        object? serviceKey,
        ServiceLifetime lifetime,
        params Type[] interceptorTypes)
    {
        services.TryAddSingleton<IProxyGenerator, ProxyGenerator>();

        foreach (var interceptorType in interceptorTypes)
        {
            services.TryAdd(new ServiceDescriptor(interceptorType, serviceKey, interceptorType, lifetime));
        }
    }

    private static IInterceptor GetInterceptor(object interceptor)
    {
        return (interceptor as IInterceptor)
            ?? (interceptor as IAsyncInterceptor)?.ToInterceptor()
            ?? throw new InvalidCastException($"{nameof(interceptor)} is not {nameof(IInterceptor)} or {nameof(IAsyncInterceptor)}.");       
    }

    private static void CheckInterface(Type serviceType)
    {
        if (!serviceType.IsInterface)
            throw new InvalidOperationException($"Proxy need interface but {nameof(serviceType)} is not interface.");
    }

    private static void CheckInterceptor(params Type[] types)
    {
        foreach (var type in types)
        {
            if (!(type.IsAssignableTo(typeof(IInterceptor)) || type.IsAssignableTo(typeof(IAsyncInterceptor))))
                throw new ArgumentException($"Exist element in {nameof(types)} is not {nameof(IInterceptor)} or {nameof(IAsyncInterceptor)}.", $"{nameof(types)}");
        }
    }

    private static bool CheckOpenGenericServiceProxyRegister(IServiceProvider serviceProvider, Type serviceType)
    {
        var register = serviceProvider.GetService<IOpenGenericServiceProxyRegister>();
        CheckOpenGenericServiceProxyRegister(register);
        return register!.Contains(serviceType);
    }

    private static bool CheckKeyedOpenGenericServiceProxyRegister(IServiceProvider serviceProvider, object? serviceKey, Type serviceType)
    {
        var register = serviceProvider.GetKeyedService<IOpenGenericServiceProxyRegister>(serviceKey);
        CheckOpenGenericServiceProxyRegister(register);
        return register!.Contains(serviceType);
    }

    private static void CheckOpenGenericServiceProxyRegister(IOpenGenericServiceProxyRegister? register)
    {
        if (register is null) throw new InvalidOperationException($"Can not found required service of type {nameof(IOpenGenericServiceProxyRegister)}. Maybe you forgot to call method named {nameof(IServiceCollection)}.{nameof(SolidifyOpenGenericServiceProxyRegister)}().");
    }

    private sealed class StartupOpenGenericServiceProxyRegister : List<Type>, IStartupOpenGenericServiceProxyRegister;

使用扩展方法把代理完全实现为普通服务注册最大限度减少对服务容器的入侵。对于隐式代理,原始服务会被原地替换为代理服务,原始服务则使用新键重新注册,因此注册代理前需要先注册原始服务。

显式代理的开放泛型处理

public interface IOpenGenericServiceProxyRegister : IReadOnlySet<Type>;

internal interface IStartupOpenGenericServiceProxyRegister : IList<Type>;

public sealed class OpenGenericServiceProxyRegister(IEnumerable<Type> types) : IOpenGenericServiceProxyRegister
{
    private readonly ImmutableHashSet<Type> _types = types.Distinct().ToImmutableHashSet();

    public int Count => _types.Count;

    public bool Contains(Type item) => _types.Contains(item);

    public bool IsProperSubsetOf(IEnumerable<Type> other) => _types.IsProperSubsetOf(other);

    public bool IsProperSupersetOf(IEnumerable<Type> other) => _types.IsProperSupersetOf(other);

    public bool IsSubsetOf(IEnumerable<Type> other) => _types.IsSubsetOf(other);

    public bool IsSupersetOf(IEnumerable<Type> other) => _types.IsSupersetOf(other);

    public bool Overlaps(IEnumerable<Type> other) => _types.Overlaps(other);

    public bool SetEquals(IEnumerable<Type> other) => _types.SetEquals(other);

    public IEnumerator<Type> GetEnumerator() => _types.GetEnumerator();

    IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
}

显式代理实际上是一个开放泛型服务,因此对于注册过代理的类型需要单独记录,否则无法判断是否应该生成相应的封闭类型。而注册记录在实例化容器后就不应该被修改,因此又提供了
SolidifyOpenGenericServiceProxyRegister
方法用来固化记录。

CoreDX.Extensions.DependencyInjection.Hosting.Proxies.Abstractions

因为代理服务有一个额外步骤,开发时可能忘记调用,为继续简化和通用主机的集成,把注册记录的固化工作放到主机的容器工厂中进行。

ProxyTypedImplementationFactoryServiceProviderFactory

public class ProxyTypedImplementationFactoryServiceProviderFactory : TypedImplementationFactoryServiceProviderFactory
{
    public ProxyTypedImplementationFactoryServiceProviderFactory() : base() { }

    public ProxyTypedImplementationFactoryServiceProviderFactory(ServiceProviderOptions options) : base(options) { }

    public override IServiceProvider CreateServiceProvider(IServiceCollection containerBuilder)
    {
        containerBuilder.SolidifyOpenGenericServiceProxyRegister();

        return base.CreateServiceProvider(containerBuilder);
    }
}

这个工厂唯一的工作就是在内部完成固化操作,这样就可以不用对
Startup
类做任何修改了。

ProxyTypedImplementationFactoryHostingHostBuilderExtensions

public static class ProxyTypedImplementationFactoryHostingHostBuilderExtensions
{
    /// <summary>
    /// Specify the <see cref="IServiceProvider"/> to be the dynamic proxy and typed implementation factory supported one.
    /// </summary>
    /// <param name="hostBuilder">The <see cref="IHostBuilder"/> to configure.</param>
    /// <returns>The <see cref="IHostBuilder"/>.</returns>
    public static IHostBuilder UseProxyTypedImplementationFactoryServiceProvider(
        this IHostBuilder hostBuilder)
        => hostBuilder.UseProxyTypedImplementationFactoryServiceProvider(static _ => { });

    /// <summary>
    /// Specify the <see cref="IServiceProvider"/> to be the dynamic proxy and typed implementation factory supported one.
    /// </summary>
    /// <param name="hostBuilder">The <see cref="IHostBuilder"/> to configure.</param>
    /// <param name="configure">The delegate that configures the <see cref="IServiceProvider"/>.</param>
    /// <returns>The <see cref="IHostBuilder"/>.</returns>
    public static IHostBuilder UseProxyTypedImplementationFactoryServiceProvider(
        this IHostBuilder hostBuilder,
        Action<ServiceProviderOptions> configure)
        => hostBuilder.UseProxyTypedImplementationFactoryServiceProvider((context, options) => configure(options));

    /// <summary>
    /// Specify the <see cref="IServiceProvider"/> to be the dynamic proxy and typed implementation factory supported one.
    /// </summary>
    /// <param name="hostBuilder">The <see cref="IHostBuilder"/> to configure.</param>
    /// <param name="configure">The delegate that configures the <see cref="IServiceProvider"/>.</param>
    /// <returns>The <see cref="IHostBuilder"/>.</returns>
    public static IHostBuilder UseProxyTypedImplementationFactoryServiceProvider(
        this IHostBuilder hostBuilder,
        Action<HostBuilderContext, ServiceProviderOptions> configure)
    {
        return hostBuilder.UseServiceProviderFactory(context =>
        {
            var options = new ServiceProviderOptions();
            configure(context, options);
            return new ProxyTypedImplementationFactoryServiceProviderFactory(options);
        });
    }
}

使用新的扩展方法替换容器工厂即可。

简单使用示例

internal class Program
{
    private static void Main(string[] args)
    {
        Explicit();
        KeyedExplicit();
        Implicit();
        KeyedImplicit();
    }

    private static void Explicit()
    {
        IServiceCollection services = new ServiceCollection();

        services.AddScoped(typeof(IB<>), typeof(B<>));
        services.AddScopedForward(typeof(IA<>), typeof(IB<>));
        services.AddScopedExplicitProxy(typeof(IA<>), typeof(MyInterceptor));
        services.AddScopedExplicitProxy(typeof(IB<>), typeof(MyInterceptor));

        services.SolidifyOpenGenericServiceProxyRegister();

        var sp = services.BuildServiceProvider();

        for(int i = 0; i < 2; i++)
        {
            object? a1 = null, b1 = null;
            using (var scope = sp.CreateScope())
            {
                var b = scope.ServiceProvider.GetRequiredService<IB<int>>();
                var a = scope.ServiceProvider.GetRequiredService<IA<int>>();
                var eqa = ReferenceEquals(a, a1);
                var eqb = ReferenceEquals(b, b1);

                a1 = a;
                b1 = b;
                var eq = ReferenceEquals(a, b);

                var pa = scope.ServiceProvider.GetRequiredService<IProxyService<IA<int>>>();
                var pb = scope.ServiceProvider.GetRequiredService<IProxyService<IB<int>>>();
            }
        }
    }

    private static void KeyedExplicit()
    {
        IServiceCollection services = new ServiceCollection();

        var serviceKey = "Keyed";
        services.AddKeyedScoped(typeof(IB<>), serviceKey, typeof(B<>));
        services.AddKeyedScopedForward(typeof(IA<>), serviceKey, typeof(IB<>));
        services.AddKeyedScopedExplicitProxy(typeof(IA<>), serviceKey, typeof(MyInterceptor));
        services.AddKeyedScopedExplicitProxy(typeof(IB<>), serviceKey, typeof(MyInterceptor));

        services.SolidifyOpenGenericServiceProxyRegister();

        var sp = services.BuildServiceProvider();

        for (int i = 0; i < 2; i++)
        {
            object? a1 = null, b1 = null;
            using (var scope = sp.CreateScope())
            {
                var b = scope.ServiceProvider.GetRequiredKeyedService<IB<int>>(serviceKey);
                var a = scope.ServiceProvider.GetRequiredKeyedService<IA<int>>(serviceKey);
                var eqa = ReferenceEquals(a, a1);
                var eqb = ReferenceEquals(b, b1);

                a1 = a;
                b1 = b;
                var eq = ReferenceEquals(a, b);

                var pa = scope.ServiceProvider.GetRequiredKeyedService<IProxyService<IA<int>>>(serviceKey);
                var pb = scope.ServiceProvider.GetRequiredKeyedService<IProxyService<IB<int>>>(serviceKey);
            }
        }
    }

    private static void Implicit()
    {
        IServiceCollection services = new ServiceCollection();

        services.AddScoped(typeof(IB<>), typeof(B<>));
        services.AddScopedForward(typeof(IA<>), typeof(IB<>));
        services.AddScopedImplicitProxy(typeof(IA<>), typeof(MyInterceptor));
        services.AddScopedImplicitProxy(typeof(IB<>), typeof(MyInterceptor));

        services.SolidifyOpenGenericServiceProxyRegister();

        var sp = services.BuildServiceProvider();

        for (int i = 0; i < 2; i++)
        {
            object? a1 = null, b1 = null;
            using (var scope = sp.CreateScope())
            {
                var b = scope.ServiceProvider.GetRequiredService<IB<int>>();
                var a = scope.ServiceProvider.GetRequiredService<IA<int>>();
                var eqa = ReferenceEquals(a, a1);
                var eqb = ReferenceEquals(b, b1);

                a1 = a;
                b1 = b;
                var eq = ReferenceEquals(a, b);

                var ra = scope.ServiceProvider.GetRequiredKeyedService<IA<int>>(ImplicitProxyServiceOriginalServiceKey.StringDefault);
                var rb = scope.ServiceProvider.GetRequiredKeyedService<IB<int>>(ImplicitProxyServiceOriginalServiceKey.DefaultStringPrefix);
            }
        }
    }

    private static void KeyedImplicit()
    {
        IServiceCollection services = new ServiceCollection();

        var serviceKey = "Keyed";
        services.AddKeyedScoped(typeof(IB<>), serviceKey, typeof(B<>));
        services.AddKeyedScopedForward(typeof(IA<>), serviceKey, typeof(IB<>));
        services.AddKeyedScopedImplicitProxy(typeof(IA<>), serviceKey, typeof(MyInterceptor));
        services.AddKeyedScopedImplicitProxy(typeof(IB<>), serviceKey, typeof(MyInterceptor));

        services.SolidifyOpenGenericServiceProxyRegister();

        var sp = services.BuildServiceProvider();

        for (int i = 0; i < 2; i++)
        {
            object? a1 = null, b1 = null;
            using (var scope = sp.CreateScope())
            {
                var b = scope.ServiceProvider.GetRequiredKeyedService<IB<int>>(serviceKey);
                var a = scope.ServiceProvider.GetRequiredKeyedService<IA<int>>(serviceKey);
                var eqa = ReferenceEquals(a, a1);
                var eqb = ReferenceEquals(b, b1);

                a1 = a;
                b1 = b;
                var eq = ReferenceEquals(a, b);

                var ra = scope.ServiceProvider.GetRequiredKeyedService<IA<int>>(ImplicitProxyServiceOriginalServiceKey.CreateStringOriginalServiceKey(serviceKey));
                var rb = scope.ServiceProvider.GetRequiredKeyedService<IB<int>>($"{ImplicitProxyServiceOriginalServiceKey.DefaultStringPrefix}{serviceKey}");

                a.M1();
                b.M1();

                ra.M1();
                rb.M1();
            }
        }
    }
}

public interface IA<T>
{
    void M1();
}

public interface IB<T> : IA<T>;

public class B<T> : IB<T>
{
    public void M1()
    {
        Console.WriteLine("B.M1");
    }
}

public class MyInterceptor : IInterceptor
{
    public void Intercept(IInvocation invocation)
    {
        Console.WriteLine("MyInterceptorBefore");
        invocation.Proceed();
        Console.WriteLine("MyInterceptorAfter");
    }
}

结语

内置容器新的键控服务把非入侵式代理的服务类型区分问题解决之后让笔者又想起来了这个一直如鲠在喉的问题,结果发现开放泛型还是搞不定,一番纠结后决定自己动手丰衣足食。做到最后感觉反正都这样了,干脆做成Nuget包发布算了,为此又整理了半天文档注释,搞得头晕眼花。

Nuget包版本和代码对应的原始包版本一致。

许可证:MIT
代码仓库:
CoreDX.Extensions.DependencyInjection - Github
Nuget:
CoreDX.Extensions.DependencyInjection
Nuget:
CoreDX.Extensions.DependencyInjection.Abstractions
(这个一般不直接用,部分功能依赖改版容器)
Nuget:
CoreDX.Extensions.DependencyInjection.Hosting.Abstractions
Nuget:
CoreDX.Extensions.DependencyInjection.Hosting.Proxies.Abstractions
Nuget:
CoreDX.Extensions.DependencyInjection.Proxies.Abstractions

QQ群

读者交流QQ群:540719365
image

欢迎读者和广大朋友一起交流,如发现本书错误也欢迎通过博客园、QQ群等方式告知笔者。

本文地址:
https://www.cnblogs.com/coredx/p/18138360.html

接下来我将持续更新“深度解读《深度探索C++对象模型》”系列,敬请期待,欢迎关注!也可以关注公众号:iShare爱分享,自动获得推文。

写作不易,请有心人到我的公众号上点点赞支持一下,增加一下热度,也好让更多的人能看到,公众号里有完整的文章列表可供阅读。

有以下三种情况,一个类对象的初始化是以同一类型的另一个对象为初值。

第一种情况,定义一个类对象时以另一个对象作为初始值,如下:

class Foo {};
Foo a;
Foo b = a;

第二种情况,当调用一个函数时,这个函数的参数要求传入一个类对象:

class Foo {};
void Bar(Foo obj) {}
Foo a;
Bar(a);

第三种情况,是函数里返回一个类的对象:

class Foo {};
Foo Bar() {
    Foo x;
	// ...
    return x;
}

这几种情况都是用一个类对象做为另一个对象的初值,假如这个类中有定义了拷贝构造函数,那么这时就会调用这个类的拷贝构造函数。但是如果类中没有定义拷贝构造函数,那么又会是怎样?很多人可能会认为编译器会生成一个拷贝构造函数来拷贝其中的内容,那么事实是否如此呢?

C++标准里描述到,如果一个类没有定义拷贝构造函数,那么编译器就会隐式地声明一个拷贝构造函数,它会判断这个拷贝构造函数是nontrivial(有用的、不平凡的)还是trivial(无用的、平凡的),只有nontrivial的才会显式地生成出来。那么怎么判断是trivial还是nontrivial的呢?编译器是根据这个类是否展现出有逐位/逐成员拷贝的语意,那什么是有逐位/逐成员拷贝的语意?来看看下面的例子。

有逐位拷贝语意的情形

#include <string.h>
#include <stdio.h>

class String {
public:
    String(const char* s) {
        if (s) {
            len = strlen(s);
            str = new char[len + 1];
            memcpy(str, s, len);
            str[len] = '\0';
        }
    }
    void print() {
        printf("str=%s, len=%d\n", str, len);
    }
private:
    char* str;
    int len;
};

int main() {
    String a("hello");
    a.print();
    String b = a;
    b.print();
    
    return 0;
}

在上面代码中,是否需要为String类生成一个显式的拷贝构造函数,以便在第25行代码构造对象b时调用它?我们可以来看看上面代码生成的汇编代码,节选main函数部分:

main:								# @main
    push    rbp
    mov     rbp, rsp
    sub     rsp, 48
    mov     dword ptr [rbp - 4], 0
    lea     rdi, [rbp - 24]
    lea     rsi, [rip + .L.str]
    call    String::String(char const*) [base object constructor]
    lea     rdi, [rbp - 24]
    call    String::print()
    mov     rax, qword ptr [rbp - 24]
    mov     qword ptr [rbp - 40], rax
    mov     rax, qword ptr [rbp - 16]
    mov     qword ptr [rbp - 32], rax
    lea     rdi, [rbp - 40]
    call    String::print()
    xor     eax, eax
    add     rsp, 48
    pop     rbp
    ret

从汇编代码中看到,除了在第8行调用了带一个参数的构造函数String(const char* s)之外,并没有调用其他的拷贝构造函数,当然全部的汇编代码中也没有见到生成的拷贝构造函数。说明这种简单的情形只需要进行逐位拷贝类对象的内容即可,不需要生成一个拷贝构造函数来做这个事情。看看程序运行输出结果:

str=hello, len=5
str=hello, len=5

这两行输出内容是上面代码第24行和第26行调用的输出,说明这时对象a和对象b的内容是一模一样的,也就是说对象b的内容完全拷贝了对象a的内容。简单解释一下上面的汇编代码,第4行是在main函数里开辟了48字节的栈空间,用于存放局部变量a和b,[rbp - 24]是对象a的起始地址,[rbp - 40]是对象b的起始地址。第11、12行就是将对象a的第一个成员先拷贝到rax寄存器,然后再拷贝给对象b的第一个成员。第13、14行就是将对象a的第2个成员(对象a的地址偏移8字节)拷贝到rax,然后再拷贝给对象b的第2个成员(对象b的地址偏移8字节)。

编译器认为这种情形只需要逐成员的拷贝对应的内容即可,不需要生成一个拷贝构造函数来完成,而且生成一个拷贝构造函数然后调用它,效率要比直接拷贝内容更低下,这种在不会产生副作用的情况下,不生成拷贝构造函数是一种更高效的做法。

上面的结果从编译器的角度来看是没有问题的,而且是合理的,它认为只需要逐成员拷贝就足够了。但是从程序的角度来看,它是有问题的。你能否看出问题出在哪里?首先我们在构造函数中申请了内存,所以需要一个析构函数来在对象销毁的时候来释放申请的内存,我们加上析构函数:

~String() {
    printf("destructor\n");
    delete[] str;
}

加上析构函数之后再运行,发现程序崩溃了。原因在于内存被双重释放了,对象a中的str指针赋值给对象b的str,这时对象a和对象b的str成员都指向同一块内存,在main函数结束后对象a和对象b先后销毁而调用了析构函数,析构函数里释放了这一块内存,所以导致了重复释放内存引起程序崩溃。这就是浅拷贝与深拷贝的问题,编译器只会做它认为正确的事情,而逻辑上是否正确是程序员应该考虑的事情,所以从逻辑上来看是否需要明确写出拷贝构造函数是程序员的责任,但是如果你认为没有必要明确定义一个拷贝构造函数,比如说不需要申请和释放内存,或者其它需要获取和释放资源的情况,只是简单地对成员进行赋值的话,那就没有必要写出一个拷贝构造函数,编译器会在背后为你做这些事情,效率还更高一些。

为了程序的正确性,我们显式地为String类定义了一个拷贝构造函数,加上之后程序运行就正常了:

// 下面代码暂时忽略了对象中str原本已经申请过内存的情况。
String(const String& rhs) {
    printf("copy constructor\n");
    if (rhs.str && rhs.len != 0) {
        len = rhs.len;
        str = new char[len + 1];
        memcpy(str, rhs.str, len);
        str[len] = '\0';
    }
}

运行输出如下,说明自定义的拷贝构造函数被调用了:

str=hello, len=5
copy constructor
str=hello, len=5
destructor
destructor

上面举例了具有逐位拷贝语意的情形,那么有哪些情形是不具有逐位拷贝语意的呢?那就是在编译器需要插入代码去做一些事情的时候以及扩展了类的内容的时候,如以下的这些情况:

  1. 类中含有类类型的成员,并且它定义了拷贝构造函数;
  2. 继承的父类中定义了拷贝构造函数;
  3. 类中定义了一个以上的虚函数或者从父类中继承了虚函数;
  4. 继承链上有一个父类是virtual base class。

下面我们按照这几种情况来一一探究。

需要调用类类型成员或者父类的拷贝构造函数的情形

如果一个类里面含有一个或以上的类类型的成员,并且这个成员的类定义中有一个拷贝构造函数;或者一个类继承了父类,父类定义了拷贝构造函数,那么如果这个类没有定义拷贝构造函数的话,编译器就会为它生成一个拷贝构造函数,用来调用类对象成员或者父类的拷贝构造函数,由于这两种情况差不多,所以放在一起分析。

如在上面的代码中,新增一个Object类,类里含有String类型的成员,见下面的代码:

// String类的定义同上

class Object {
public:
    Object(): s("default"), num(10) {}
    void print() {
        s.print();
        printf("num=%d\n", num);
    }
private:
    String s;
    int num;
};

// main函数改成如下
int main() {
    Object a;
    a.print();
    Object b = a;
    b.print();
    
    return 0;
}

运行结果如下:

str=default, len=7
num=10
copy constructor
str=default, len=7
num=10
destructor
destructor

从结果中可以看出最重要的两点:

  1. String类的拷贝构造函数被调用了;
  2. 对象b的成员num被赋予正确的值。

我们来进一步,首先看一下生成的汇编代码:

main:																		# @main
    push    rbp
    mov     rbp, rsp
    sub     rsp, 80
    mov     dword ptr [rbp - 4], 0
    lea     rdi, [rbp - 32]
    mov     qword ptr [rbp - 80], rdi		# 8-byte Spill
    call    Object::Object() [base object constructor]
    mov     rdi, qword ptr [rbp - 80]		# 8-byte Reload
    call    Object::print()
    jmp     .LBB0_1
.LBB0_1:
    lea     rdi, [rbp - 72]
    lea     rsi, [rbp - 32]
    call    Object::Object(Object const&) [base object constructor]
    jmp     .LBB0_2
.LBB0_2:
    lea     rdi, [rbp - 72]
    call    Object::print()
    jmp     .LBB0_3
.LBB0_3:
 # 以下代码省略

上面是节选main函数的部分汇编代码,执行析构函数部分省略掉。第10行对应的是main函数里的第18行a.print();,编译器会把它转换成print(&a),参数就是对象a的地址,也就是[rbp - 80],把它放到rdi寄存器中作为参数,从上面的代码中知道[rbp - 80]其实等于[rbp - 32],[rbp - 32]就是对象a的地址。第15行代码对应的就是Object b = a;这一行的代码,可见它调用了Object::Object(Object const&)这个拷贝构造函数,但C++的代码中我们并没有显式地定义这个函数,这个函数是由编译器自动生成出来的。它有两个参数,第一个参数是对象b的地址,即[rbp - 72],存放在rdi寄存器,第二个参数是对象a的地址,即[rbp - 32],存放在rsi寄存器。编译器会把上面的调用转换成Object::Object(&b, &a);。

接下来看看编译器生成的拷贝构造函数的汇编代码:

Object::Object(Object const&) [base object constructor]:	# @Object::Object(Object const&) [base object constructor]
    push    rbp
    mov     rbp, rsp
    sub     rsp, 32
    mov     qword ptr [rbp - 8], rdi
    mov     qword ptr [rbp - 16], rsi
    mov     rdi, qword ptr [rbp - 8]
    mov     qword ptr [rbp - 24], rdi       # 8-byte Spill
    mov     rsi, qword ptr [rbp - 16]
    call    String::String(String const&) [base object constructor]
    mov     rax, qword ptr [rbp - 24]       # 8-byte Reload
    mov     rcx, qword ptr [rbp - 16]
    mov     ecx, dword ptr [rcx + 16]
    mov     dword ptr [rax + 16], ecx
    add     rsp, 32
    pop     rbp
    ret

第5、6行是把对象b的地址(rdi寄存器)存放到[rbp - 8]中,把对象a的地址(rsi寄存器)存放到[rbp - 16]中。第10行代码就是去调用String类的拷贝构造函数了。第11到14行代码是用对象a中的num成员的值给对象b的num成员赋值,[rbp - 16]是对象a的起始地址,存放到rcx寄存器中,然后再加16字节的偏移量就是num成员的地址,加16字节的偏移量是为了跳过前面的String类型的成员s,它的大小为16字节。rax寄存器存放的是对象b的起始地址,[rax + 16]就是对象b中的num成员的地址。

从这里可以得出一个结论:编译器生成的拷贝构造函数除了会去调用类类型成员的拷贝构造函数之外,还会拷贝其它的数据成员,包括整形数据、指针和数组等等,它和生成的默认构造函数不一样,生成的默认构造函数不会去初始化这些数据成员。

如果类类型成员里没有定义拷贝构造函数,比如把String类中的拷贝构造函数注释掉,这时编译器就不会生成一个拷贝构造函数,因为不需要,这时它会实行逐成员拷贝的方式,若遇到成员是类类型的,则递归地执行逐成员拷贝的操作。

含有虚函数的情形

从前面的文章中我们知道,当一个类定义了一个或以上的虚函数时,或者继承链上的父类中有定义了虚函数的话,那么编译器就会为他们生成虚函数表,并会扩充类对象的内存布局,在类对象的起始位置插入虚函数表指针,以指向虚函数表。这个虚函数表指针很重要,如果没有设置好正确的值,那么将引起调用虚函数的混乱甚至引起程序的崩溃。编译器往类对象插入虚函数表指针将导致这个类不再具有逐成员拷贝的语意,当程序中没有显式定义拷贝构造函数时,编译器需要为它自动生成一个拷贝构造函数,以便在适当的时机设置好这个虚函数表指针的值。我们以下面的例子来分析一下:

#include <stdio.h>

class Base {
public:
    virtual void virtual_func() {
        printf("virtual function in Base class\n");
    }
private:
    int b;
};

class Object: public Base {
public:
    virtual void virtual_func() {
         printf("virtual function in Object class\n");
    }
private:
    int num;
};

void Foo(Base& obj) {
    obj.virtual_func();
}

int main() {
    Object a;
    Object a1 = a;
    Base b = a;
    Foo(a);
    Foo(b);
    
    return 0;
}

看下生成的汇编代码,节选main函数部分:

main:									# @main
    push    rbp
    mov     rbp, rsp
    sub     rsp, 64
    mov     dword ptr [rbp - 4], 0
    lea     rdi, [rbp - 24]
    call    Object::Object() [base object constructor]
    lea     rdi, [rbp - 40]
    lea     rsi, [rbp - 24]
    call    Object::Object(Object const&) [base object constructor]
    lea     rdi, [rbp - 56]
    lea     rsi, [rbp - 24]
    call    Base::Base(Base const&) [base object constructor]
    lea     rdi, [rbp - 24]
    call    Foo(Base&)
    lea     rdi, [rbp - 56]
    call    Foo(Base&)
    xor     eax, eax
    add     rsp, 64
    pop     rbp
    ret

上面汇编代码中的第10行对应C++代码中的第27行,这里调用的是Object类的拷贝构造函数,汇编代码中的第13行对应C++代码中的第28行,这里调用的是Base类的拷贝构造函数,这说明了编译器为Object类和Base类都生成了拷贝构造函数。继续分析这两个类的拷贝构造函数的汇编代码:

Object::Object(Object const&) [base object constructor]:	# @Object::Object(Object const&) [base object constructor]
    push    rbp
    mov     rbp, rsp
    sub     rsp, 32
    mov     qword ptr [rbp - 8], rdi
    mov     qword ptr [rbp - 16], rsi
    mov     rdi, qword ptr [rbp - 8]
    mov     qword ptr [rbp - 24], rdi       # 8-byte Spill
    mov     rsi, qword ptr [rbp - 16]
    call    Base::Base(Base const&) [base object constructor]
    mov     rax, qword ptr [rbp - 24]       # 8-byte Reload
    lea     rcx, [rip + vtable for Object]
    add     rcx, 16
    mov     qword ptr [rax], rcx
    mov     rcx, qword ptr [rbp - 16]
    mov     ecx, dword ptr [rcx + 12]
    mov     dword ptr [rax + 12], ecx
    add     rsp, 32
    pop     rbp
    ret
Base::Base(Base const&) [base object constructor]:	# @Base::Base(Base const&) [base object constructor]
    push    rbp
    mov     rbp, rsp
    mov     qword ptr [rbp - 8], rdi
    mov     qword ptr [rbp - 16], rsi
    mov     rax, qword ptr [rbp - 8]
    lea     rcx, [rip + vtable for Base]
    add     rcx, 16
    mov     qword ptr [rax], rcx
    mov     rcx, qword ptr [rbp - 16]
    mov     ecx, dword ptr [rcx + 8]
    mov     dword ptr [rax + 8], ecx
    pop     rbp
    ret

在Object类的拷贝构造函数里,上面汇编代码的第10行,调用了Base类的拷贝构造函数,这里的意思是先构造Base子类部分,在Base类的拷贝构造函数里,上面汇编代码的第27行到29行,在这里设置了Base类的虚函数表指针,因为这里构造的是Base子类的对象,所以这里设置的是Base类的虚函数表指针。然后返回到Object类的拷贝构造函数,在上面汇编代码的第12行到第14行,这里又重新设置回Object类的虚函数表指针,因为构造完Base子类之后继续构造Object类,需要重设回Object类的虚函数表指针,Base类和Object类的虚函数表是不同的两个表,所以需要为它们对应的对象设置对应的虚函数表指针。

其实同一类型的对象的赋值是可以采用逐成员拷贝的方式来完成的,比如像Object a1 = a;这行代码,因为它们的虚函数表是同一个,直接拷贝对象a的虚函数表指针给a1对象没有任何问题。但是问题出在于使用派生类的对象给父类的对象赋值时,这里会发生切割,把派生类对象中的父类子对象部分拷贝给父类对象,如果没有编译器扩充的部分(这里是虚函数表指针),只是拷贝数据部分是没有问题的,但是如果把派生类的虚函数表指针赋值给父类子对象,这将导致虚函数调用的混乱,本该调用父类的虚函数的,却调用了派生类的虚函数。所以编译器需要重设这个虚函数表指针的值,也就是说这里不能采用逐成员拷贝的手法了,当程序中没有显式地定义拷贝构造函数时编译器就会生成一个,或者在已有的拷贝构造函数中插入代码,来完成重设虚函数表指针这个工作。

再看下C++代码中的这三行代码:

Base b = a;
Foo(a);
Foo(b);

第一行的赋值语句,虽然是使用派生类Object的对象a作为初值,但是调用的却是Base类的拷贝构造函数(见main函数的汇编代码第13行),因为b的类型是Base类。这就保证了只使用了对象a中的Base子对象部分的内容,以及确保设置的虚函数表指针是指向Base类的虚函数表,这样在调用Foo函数时,分别使用对象a和b作为参数,尽管Foo函数的形参使用的是“Base&”,是使用基类的引用类型,但却不会引起调用上的混乱。第二个调用使用b作为参数,它是Base类的对象,调用的是Base类的虚函数,这两行的输出结果是:

virtual function in Object class
virtual function in Base class

继承链上有virtual base class的情形

当一个类的继承链上有一个virtual base class时,virtual base class子对象的布局会重排,内存布局的分析可以参考另一篇文章《C++对象封装后的内存布局》。为使得能支持虚继承的机制,编译器运行时需要知道虚基类的成员位置,所以编译器会在编译时生成一个虚表,这个表格里会记录成员的相对位置,在构造对象时会插入一个指针指向这个表。这使得类失去了逐成员拷贝的语意,如果一个类对象的初始化是以另一个相同类型的对象为初值,那么逐成员拷贝是没有问题的,问题在于如果是以派生类的对象赋值给基类的对象,这时候会发生切割,编译器需要计算好成员的相对位置,以避免访问出现错误,所以编译器需要生成拷贝构造函数来做这样的事情。以下面的代码为例:

#include <stdio.h>

class Grand {
public:
    int g = 1;
};

class Base1: virtual public Grand {
    int b1 = 2;
};

class Base2: virtual public Grand {
    int b2 = 3;
};

class Derived: public Base1, public Base2 {
    int d = 4;
};

int main() {
    Derived d;
    Base2* pb2 = &d;
    d.g = 11;
    pb2->g = 10;
    Base2 b2 = *pb2;
    
    return 0;
}

第25行的代码是将派生类Derived类的对象赋值给Base2父类对象,这将会发生切割,将Derived类中的Base2子对象部分拷贝过去,看下对应的汇编代码:

# 节选部分main函数汇编
mov     rsi, qword ptr [rbp - 56]
lea     rdi, [rbp - 72]
call    Base2::Base2(Base2 const&) [complete object constructor]

[rbp - 56]存放的是C++代码里的pb2的值,也就是对象d的地址,存放在rsi寄存器中,[rbp - 72]是对象b2的地址,存放到rdi寄存器中,然后将rsi和rdi寄存器作为参数传递给Base2的拷贝构造函数,然后调用它。继续看下Base2的拷贝构造函数的汇编代码:

Base2::Base2(Base2 const&) [complete object constructor]:	# @Base2::Base2(Base2 const&) [complete object constructor]
    push    rbp
    mov     rbp, rsp
    mov     qword ptr [rbp - 8], rdi
    mov     qword ptr [rbp - 16], rsi
    mov     rax, qword ptr [rbp - 8]
    mov     rcx, qword ptr [rbp - 16]
    mov     rdx, qword ptr [rcx]
    mov     rdx, qword ptr [rdx - 24]
    mov     ecx, dword ptr [rcx + rdx]
    mov     dword ptr [rax + 12], ecx
    lea     rcx, [rip + vtable for Base2]
    add     rcx, 24
    mov     qword ptr [rax], rcx
    mov     rcx, qword ptr [rbp - 16]
    mov     ecx, dword ptr [rcx + 8]
    mov     dword ptr [rax + 8], ecx
    pop     rbp
    ret

首先将两个参数(分别存放在rdi和rsi寄存器)拷贝到栈空间[rbp - 8]和[rbp - 16]中,第8到11行代码就是将对象d中的Grand子对象的成员拷贝到b2对象中,对象的前8个字节在构造对象的时候已经设置好了虚表的指针,这里将指针指向的内容存放到rdx寄存器中,第9行取得虚基类成员的偏移地址然后存放在rdx寄存器,第10行将对象的首地址加上偏移地址,取得虚基类的成员然后拷贝到ecx寄存器,在第11行代码里拷贝给[rax + 12],即b2对象的起始地址加上12字节的偏移量(8字节的虚表指针加上成员变量b2占4字节),即完成对Grand类中的成员变量g的拷贝。

所以对于有虚基类的情况,将一个派生类的对象赋值给基类对象时,不能采取逐成员拷贝的手法,需要借助虚表来计算出虚基类的成员的相对位置,以获得正确的成员地址,需要生成拷贝构造函数来完成。

抑制合成拷贝构造函数的情况

C++11标准之后新增了delete关键字,它可以指定不允许编译器生成哪些函数,比如我们不允许拷贝一个类对象,那么可以将此类的拷贝构造函数声明为=delete的。例如标准库中的iostream类,它不允许拷贝,防止两个对象同时指向同一块缓存。如果一个类的定义中有一个类类型成员,而此成员的拷贝构造函数声明为=delete的,或者类的父类中声明了拷贝构造函数为=delete的,那么这个类的拷贝构造函数也会被编译器声明为delete的,这个类的对象将不允许被拷贝,如以下的代码:

class Base {
public:
    Base() = default;
    Base(const Base& rhs) = delete;
};

class Object {
    Base b;
};

int main() {
    Object d;
    Object d1 = d;	// 此行编译错误
    
    return 0;
}

上面代码的第13行会引起编译错误,原因就是Object类没有拷贝构造函数,不允许赋值的操作,同样地,拷贝赋值运算符也将被声明为delete的。

总结

  • 拷贝赋值运算符的情况和拷贝构造函数的情况类似,可以采用上述的方法来分析。
  • 当不需要涉及到资源的分配和释放时,不需要显示地定义拷贝构造函数,编译器会为我们做好逐成员拷贝的工作,效率比去调用一个拷贝构造函数要更高效一些。
  • 当你需要为程序定义一个析构函数时,那么肯定也需要定义拷贝构造函数和拷贝赋值运算符,因为当你需要在析构函数中去释放资源的时候,说明在拷贝对象的时候需要为新对象申请新的资源,以避免两个对象同时指向同一块资源。
  • 当你需要为程序定义拷贝构造函数时,那么也同时需要定义拷贝赋值运算符,反之亦然,但是却并不一定需要定义析构函数,比如在构造对象时为此对象生成一个UUID,这时在析构对象时并不需要释放资源。

此篇文章同步发布于我的微信公众号:
深度解读《深度探索C++对象模型》之拷贝构造函数

如果您感兴趣这方面的内容,请在微信上搜索公众号iShare爱分享或者微信号iTechShare并关注,以便在内容更新时直接向您推送。
image