fix(cqrs): 修复 HasRegistration 评审回归

- 修复 HasRegistration(Type) 的服务键判定,避免将仅按具体类型注册的行为误判为接口已注册

- 补充 strict mock 场景与 HasRegistration 回归测试,并修复 PR #340 暴露的 stream context validation 失败

- 更新 IoC 与 benchmark 文档注释,同步 cqrs-rewrite tracking/trace 到 PR #340 / RP-103
This commit is contained in:
gewuyou 2026-05-08 10:54:37 +08:00
parent 5da4a5893b
commit 32eeb41f29
9 changed files with 160 additions and 9 deletions

View File

@ -257,10 +257,13 @@ public interface IIocContainer : IContextAware, IDisposable
/// </summary>
/// <param name="type">要检查的服务类型。</param>
/// <returns>若存在显式注册或开放泛型映射可满足该服务类型,则返回 <see langword="true" />;否则返回 <see langword="false" />。</returns>
/// <exception cref="ArgumentNullException">当 <paramref name="type" /> 为 <see langword="null" /> 时抛出。</exception>
/// <exception cref="ObjectDisposedException">当调用 <see cref="HasRegistration(Type)" /> 时容器已被释放时抛出。</exception>
/// <remarks>
/// 该入口面向“先判断是否值得解析实例”的热路径优化场景。
/// 与 <see cref="Contains{T}" /> 不同,它不会为了判断结果而激活服务实例,因此可避免把瞬态对象创建、
/// 多服务枚举或日志分配混入仅需存在性判断的调用链中。
/// 该方法按服务键与开放泛型映射判断可见性,不会把“仅以实现类型自身注册”的实例误判成其所有可赋值接口都已注册。
/// </remarks>
bool HasRegistration(Type type);

View File

@ -445,6 +445,21 @@ public class MicrosoftDiContainerTests
Assert.That(_container.HasRegistration(typeof(IPipelineBehavior<HasRegistrationRequest, int>)), Is.True);
}
/// <summary>
/// 测试 HasRegistration 不会把仅以具体实现类型自注册的服务误判成其接口服务键也已注册。
/// </summary>
[Test]
public void HasRegistration_Should_ReturnFalse_For_Interface_When_Only_Concrete_Service_Key_Is_Registered()
{
_container.GetServicesUnsafe.AddSingleton(typeof(SelfRegisteredConcreteBehavior), typeof(SelfRegisteredConcreteBehavior));
Assert.That(_container.HasRegistration(typeof(IPipelineBehavior<HasRegistrationRequest, int>)), Is.False);
_container.Freeze();
Assert.That(_container.HasRegistration(typeof(IPipelineBehavior<HasRegistrationRequest, int>)), Is.False);
}
/// <summary>
/// 测试当实例存在时检查实例包含关系应返回 true 的功能
/// </summary>
@ -956,4 +971,21 @@ public class MicrosoftDiContainerTests
return next(request, cancellationToken);
}
}
/// <summary>
/// 供 HasRegistration 服务键判定回归使用的最小封闭 pipeline 行为。
/// </summary>
private sealed class SelfRegisteredConcreteBehavior : IPipelineBehavior<HasRegistrationRequest, int>
{
/// <summary>
/// 透传到下一个 pipeline 节点,不额外改变请求语义。
/// </summary>
public ValueTask<int> Handle(
HasRegistrationRequest request,
MessageHandlerDelegate<HasRegistrationRequest, int> next,
CancellationToken cancellationToken)
{
return next(request, cancellationToken);
}
}
}

View File

@ -1103,7 +1103,9 @@ public class MicrosoftDiContainer(IServiceCollection? serviceCollection = null)
/// <returns>若当前注册可用于解析 <paramref name="requestedType" />,则返回 <see langword="true" />。</returns>
private static bool CanSatisfyServiceType(Type registeredServiceType, Type requestedType)
{
if (registeredServiceType == requestedType || requestedType.IsAssignableFrom(registeredServiceType))
// 这里刻意与 Get/GetAll 的“按服务键解析”语义保持一致:
// 只有注册时声明的服务类型本身命中,或开放泛型服务键能闭合到请求类型时,才视为存在可见注册。
if (registeredServiceType == requestedType)
{
return true;
}

View File

@ -76,14 +76,14 @@ internal static class BenchmarkHostFactory
}
/// <summary>
/// 创建承载 `ai-libs/Mediator` source-generated concrete mediator 的最小对照宿主。
/// 创建承载 NuGet `Mediator` source-generated concrete mediator 的最小对照宿主。
/// </summary>
/// <param name="configure">补充当前场景的显式服务注册。</param>
/// <returns>可直接解析 generated `Mediator.Mediator` 的 DI 宿主。</returns>
/// <remarks>
/// 当前 benchmark 只把 `Mediator` 作为单例 steady-state 对照组接入,
/// 因为它的 lifetime 由 source generator 在编译期塑形;若后续需要 `Transient` / `Scoped` 矩阵,
/// 应按 `ai-libs/Mediator/benchmarks` 的做法拆成独立 build config而不是在同一编译产物里混用多个 lifetime。
/// 因为它的 lifetime 由 source generator 在编译期塑形;若后续需要 `Transient` / `Scoped` 矩阵,
/// 应按 `Mediator` 官方 benchmark 的做法拆成独立 build config而不是在同一编译产物里混用多个 lifetime。
/// </remarks>
internal static ServiceProvider CreateMediatorServiceProvider(Action<IServiceCollection>? configure)
{

View File

@ -21,7 +21,7 @@ using GeneratedMediator = Mediator.Mediator;
namespace GFramework.Cqrs.Benchmarks.Messaging;
/// <summary>
/// 对比单个 request 在直接调用、GFramework.CQRS runtime、`ai-libs/Mediator` 与 MediatR 之间的 steady-state dispatch 开销。
/// 对比单个 request 在直接调用、GFramework.CQRS runtime、NuGet `Mediator` 与 MediatR 之间的 steady-state dispatch 开销。
/// </summary>
[Config(typeof(Config))]
public class RequestBenchmarks
@ -161,6 +161,16 @@ public class RequestBenchmarks
return ValueTask.FromResult(new BenchmarkResponse(request.Id));
}
/// <summary>
/// 处理 NuGet `Mediator` request。
/// </summary>
ValueTask<BenchmarkResponse> Mediator.IRequestHandler<BenchmarkRequest, BenchmarkResponse>.Handle(
BenchmarkRequest request,
CancellationToken cancellationToken)
{
return Handle(request, cancellationToken);
}
/// <summary>
/// 处理 MediatR request。
/// </summary>

View File

@ -30,6 +30,9 @@ internal sealed class CqrsDispatcherContextValidationTests
container
.Setup(currentContainer => currentContainer.Get(typeof(IRequestHandler<ContextAwareRequest, int>)))
.Returns(new ContextAwareRequestHandler());
container
.Setup(currentContainer => currentContainer.HasRegistration(typeof(IPipelineBehavior<ContextAwareRequest, int>)))
.Returns(false);
container
.Setup(currentContainer => currentContainer.GetAll(typeof(IPipelineBehavior<ContextAwareRequest, int>)))
.Returns(Array.Empty<object>());
@ -73,6 +76,9 @@ internal sealed class CqrsDispatcherContextValidationTests
container
.Setup(currentContainer => currentContainer.Get(typeof(IStreamRequestHandler<ContextAwareStreamRequest, int>)))
.Returns(new ContextAwareStreamHandler());
container
.Setup(currentContainer => currentContainer.HasRegistration(typeof(IStreamPipelineBehavior<ContextAwareStreamRequest, int>)))
.Returns(false);
container
.Setup(currentContainer => currentContainer.GetAll(typeof(IStreamPipelineBehavior<ContextAwareStreamRequest, int>)))
.Returns(Array.Empty<object>());
@ -97,6 +103,9 @@ internal sealed class CqrsDispatcherContextValidationTests
container
.Setup(currentContainer => currentContainer.Get(typeof(IStreamRequestHandler<ContextAwareStreamRequest, int>)))
.Returns(new PassthroughStreamHandler());
container
.Setup(currentContainer => currentContainer.HasRegistration(typeof(IStreamPipelineBehavior<ContextAwareStreamRequest, int>)))
.Returns(true);
container
.Setup(currentContainer => currentContainer.GetAll(typeof(IStreamPipelineBehavior<ContextAwareStreamRequest, int>)))
.Returns([new ContextAwareStreamBehavior()]);

View File

@ -7,9 +7,9 @@ CQRS 迁移与收敛。
## 当前恢复点
- 恢复点编号:`CQRS-REWRITE-RP-102`
- 恢复点编号:`CQRS-REWRITE-RP-103`
- 当前阶段:`Phase 8`
- 当前 PR 锚点:`PR #339`
- 当前 PR 锚点:`PR #340`
- 当前结论:
- `GFramework.Cqrs` 已完成对外部 `Mediator` 的生产级替代,当前主线已从“是否可替代”转向“仓库内部收口与能力深化顺序”
- `dispatch/invoker` 生成前移已扩展到 request / stream 路径,`RP-077` 已补齐 request invoker provider gate 与 stream gate 对称的 descriptor / descriptor entry runtime 合同回归
@ -37,9 +37,10 @@ CQRS 迁移与收敛。
- `RP-099` 已补齐 `GFramework.Cqrs` 的最小 stream pipeline seam新增 `IStreamPipelineBehavior<,>` / `StreamMessageHandlerDelegate<,>``RegisterCqrsStreamPipelineBehavior<TBehavior>()`、dispatcher 侧 stream pipeline executor 缓存与 generated stream invoker 兼容回归,以及 `Architecture` 公开注册入口与对应文档说明
- 当前 `RP-100` 已使用 `$gframework-pr-review` 复核 `PR #339` latest-head review收口 `RegisterCqrsStreamPipelineBehavior<TBehavior>()` 的异常契约文档、为 `StreamPipelineInvocation.GetContinuation(...)` 补齐并发 continuation 缓存说明、抽取 `MicrosoftDiContainer` 的 CQRS 行为注册公共逻辑,并顺手修复当前 branch diff 内 `ICqrsRequestInvokerProvider.cs` 的 XML 缩进格式问题
- 当前 `RP-101` 已按用户新增 benchmark 诉求收口 request 热路径:为 `IIocContainer` 新增不激活实例的 `HasRegistration(Type)`、让 dispatcher 在 `0 pipeline` 场景下跳过空行为解析,并为 `MicrosoftDiContainer` 的热路径查询补齐 debug-level 守卫,避免无效日志字符串分配
- 当前 `RP-101` 已把 `GFramework.Cqrs.Benchmarks``Mediator` 对照组收口为官方 NuGet 引用(`Mediator.Abstractions` / `Mediator.SourceGenerator` `3.0.2`),不再使用本地 `ai-libs/Mediator` project reference`RequestBenchmarks` 现已新增 source-generated concrete `Mediator` 对照方法,并通过 `RequestLifetimeBenchmarks` 复核 hot path 收口后的新基线
- 当前 `RP-102` 已把 `GFramework.Cqrs.Benchmarks``Mediator` 对照组收口为官方 NuGet 引用(`Mediator.Abstractions` / `Mediator.SourceGenerator` `3.0.2`),不再使用本地 `ai-libs/Mediator` project reference`RequestBenchmarks` 现已新增 source-generated concrete `Mediator` 对照方法,并通过 `RequestLifetimeBenchmarks` 复核 hot path 收口后的新基线
- 当前 `RP-102` 已将 `BenchmarkDotNet.Artifacts/` 收口为默认忽略路径,并把 request steady-state / lifetime benchmark 复跑升级为 CQRS 性能相关改动的默认回归门槛;当前阶段目标明确为“持续逼近 source-generated `Mediator`,并至少稳定超过反射版 `MediatR`
- `ai-plan` active 入口现以 `RP-102` 为最新恢复锚点;`PR #339``PR #334``PR #331``PR #326``PR #323``PR #307` 与其他更早阶段细节均以下方归档或说明为准
- 当前 `RP-103` 已使用 `$gframework-pr-review` 复核 `PR #340` latest-head review修复 `CreateStream_Should_Throw_When_Stream_Pipeline_Behavior_Context_Does_Not_Implement_IArchitectureContext` 因 strict mock 未配置 `HasRegistration(Type)` 产生的 CI 失败,收紧 `MicrosoftDiContainer.HasRegistration(Type)` 到与 `GetAll(Type)` 一致的服务键可见性语义,补齐 `IIocContainer.HasRegistration(Type)` 的异常/XML 契约与 `docs/zh-CN/core/ioc.md` 的用户接入说明,并同步 benchmark 注释与 active tracking/trace 到当前 PR 锚点
- `ai-plan` active 入口现以 `RP-103` 为最新恢复锚点;`PR #340``PR #339``PR #334``PR #331``PR #326``PR #323``PR #307` 与其他更早阶段细节均以下方归档或说明为准
## 当前活跃事实
@ -52,6 +53,9 @@ CQRS 迁移与收敛。
- 当前 request steady-state benchmark 已形成 baseline / `Mediator` / `MediatR` / `GFramework.Cqrs` 四方对照:约 `5.300 ns / 32 B``4.964 ns / 32 B``57.993 ns / 232 B``83.823 ns / 32 B`
- 当前 request lifetime benchmark 已从旧坏值显著收敛:`Singleton``GFramework.Cqrs``83.183 ns / 32 B`(旧值 `301.731 ns / 440 B``Transient` 下约 `86.243 ns / 56 B`(旧值 `287.863 ns / 464 B`
- 本轮已验证旧 benchmark 劣化的两个主热点:`0 pipeline` 场景下仍解析空行为列表,以及容器查询热路径在 debug 禁用时仍构造日志字符串;两者收口后,`GFramework.Cqrs` request 路径不再出现额外数百字节分配
- `HasRegistration(Type)` 现在只把“同一服务键已注册”或“开放泛型服务键可闭合到目标类型”视为命中,不再把“仅以具体实现类型自注册”的行为误判为接口服务已注册;该语义与 `Get(Type)` / `GetAll(Type)` 已重新对齐
- `GFramework.Cqrs.Tests/Cqrs/CqrsDispatcherContextValidationTests.cs` 已同步适配 `HasRegistration(Type)` fast-path避免 strict mock 因缺少新调用配置而在上下文失败语义断言前提前抛出 `Moq.MockException`
- `docs/zh-CN/core/ioc.md` 已新增 `HasRegistration(Type)` 的使用语义、热路径用途与“按服务键而非可赋值关系判断”的示例说明
- 当前 request steady-state 仍落后于 source-generated `Mediator``MediatR`,但差距已从“额外数百字节分配 + 近 300ns”收敛到“零 pipeline fast-path 仍慢约 `31ns` / `3.6x``Mediator`”;下一批若继续压 request dispatch应优先评估默认路径吸收 generated invoker/provider 的空间
- 当前性能回归门槛已收紧为:只要改动触达 `GFramework.Cqrs` request dispatch、DI 热路径、invoker/provider、pipeline 或 benchmark 宿主,就必须至少复跑 `RequestBenchmarks.SendRequest_*``RequestLifetimeBenchmarks.SendRequest_*`
- 当前阶段的性能验收目标已明确为:默认 request steady-state 路径不要求超过 source-generated `Mediator`,但必须持续逼近它,并至少稳定快于基于反射 / 扫描的 `MediatR`
@ -102,9 +106,25 @@ CQRS 迁移与收敛。
- `LegacyBridgePipelineTracker` 仍是进程级静态测试辅助;虽然现在已在相关 fixture 清理阶段重置并补充线程安全说明,但若将来扩大并行 bridge fixture 数量,仍要继续控制共享状态扩散
- stream pipeline 当前只在“单次建流”层面包裹 handler 调用;若后续需要 per-item 拦截、元素级重试或流内 metrics 聚合,仍需额外设计更细粒度 contract而不是把本轮 seam 直接等同于元素级 middleware
- `PR #339` 在 GitHub 上仍有 1 个已本地失效但未 resolve 的 stale test-thread若后续 head 再次变化,需要重新抓取 latest-head review 确认未解决线程是否收敛
- 若后续继续依赖 `HasRegistration(Type)` 做热路径短路,新增测试替身或 strict mock 时必须同步配置该调用,否则容易在真正业务断言之前被 mock 框架短路成环境性失败
## 最近权威验证
- `python3 .agents/skills/gframework-pr-review/scripts/fetch_current_pr_review.py --json-output /tmp/current-pr-review.json`
- 结果:通过
- 备注:确认当前分支对应 `PR #340`latest-head 当前显示 `CodeRabbit 2` / `Greptile 2` open thread`CTRF` 报告中唯一失败测试为 `CreateStream_Should_Throw_When_Stream_Pipeline_Behavior_Context_Does_Not_Implement_IArchitectureContext`
- `dotnet build GFramework.Core.Abstractions/GFramework.Core.Abstractions.csproj -c Release`
- 结果:通过,`0 warning / 0 error`
- `dotnet build GFramework.Core/GFramework.Core.csproj -c Release`
- 结果:通过,`0 warning / 0 error`
- `dotnet build GFramework.Cqrs.Tests/GFramework.Cqrs.Tests.csproj -c Release`
- 结果:通过,`0 warning / 0 error`
- `dotnet build GFramework.Cqrs.Benchmarks/GFramework.Cqrs.Benchmarks.csproj -c Release`
- 结果:通过,`0 warning / 0 error`
- `dotnet test GFramework.Core.Tests/GFramework.Core.Tests.csproj -c Release --no-build --filter "FullyQualifiedName~MicrosoftDiContainerTests"`
- 结果:通过,`52/52` passed
- `dotnet test GFramework.Cqrs.Tests/GFramework.Cqrs.Tests.csproj -c Release --no-build --filter "FullyQualifiedName~CqrsDispatcherContextValidationTests"`
- 结果:通过,`4/4` passed
- `dotnet build GFramework.Cqrs.Benchmarks/GFramework.Cqrs.Benchmarks.csproj -c Release`
- 结果:通过,`0 warning / 0 error`
- `dotnet run --project GFramework.Cqrs.Benchmarks/GFramework.Cqrs.Benchmarks.csproj -c Release -- --filter "*RequestBenchmarks.SendRequest_*" --job short --warmupCount 1 --iterationCount 1 --launchCount 1`
@ -117,6 +137,7 @@ CQRS 迁移与收敛。
- 结果:通过
- `git diff --check`
- 结果:通过
- 备注:当前仅保留 `GFramework.sln` 的历史 CRLF 警告,无本轮新增 diff 格式错误
- `dotnet pack GFramework.sln -c Release --no-restore -o /tmp/gframework-pack-validation -p:IncludeSymbols=false`
- 结果:通过
- 备注:当前本地产物仅包含 14 个预期发布包,未生成 `GFramework.Cqrs.Benchmarks.*.nupkg`

View File

@ -2,6 +2,42 @@
## 2026-05-08
### 阶段PR #340 latest-head review 收口CQRS-REWRITE-RP-103
- 使用 `$gframework-pr-review` 抓取 `feat/cqrs-optimization` 当前公开 PR并确认当前锚点已从 `PR #339` 更新为 `PR #340`
- 本轮 latest-head review 结论:
- `CodeRabbit` 当前显示 `2` 个 nitpick / open thread`Greptile` 显示 `2` 个 open thread`MegaLinter` 仍只有 `dotnet-format restore` 环境噪音
- `CTRF` 当前显示 `2321` 项测试中 `1` 项失败,失败用例为 `CreateStream_Should_Throw_When_Stream_Pipeline_Behavior_Context_Does_Not_Implement_IArchitectureContext`
- 失败原因不是业务断言退化,而是 `CqrsDispatcher` 新增 `HasRegistration(Type)` fast-path 后,严格 mock 的上下文校验测试没有同步配置该调用,导致在命中上下文注入失败断言前先抛出 `Moq.MockException`
- `MicrosoftDiContainer.HasRegistration(Type)` 当前实现用 `requestedType.IsAssignableFrom(registeredServiceType)` 判断命中,会把“仅以实现类型自身注册”的服务误判成接口服务键也已注册,这与 `Get(Type)` / `GetAll(Type)` 的服务键语义不一致,属于仍成立的运行时缺陷
- `IIocContainer.HasRegistration(Type)` XML 文档缺少异常契约;`docs/zh-CN/core/ioc.md` 也还未解释该新公开入口的用途与语义边界;`BenchmarkHostFactory` / `RequestBenchmarks` 中仍残留旧 `ai-libs/Mediator` 注释或隐式共享 handler 合同,属于仍成立的文档/维护性问题
- 本轮主线程决策:
- 在 `CqrsDispatcherContextValidationTests` 为受影响的 request / stream pipeline mock 显式补 `HasRegistration(Type)` 配置,确保上下文失败语义测试不会被 strict mock 噪音短路
- 把 `MicrosoftDiContainer.CanSatisfyServiceType(...)` 收窄为“服务键完全命中”或“开放泛型服务键可闭合到目标类型”,并新增回归覆盖“仅按具体实现类型自注册时,接口服务键应返回 false”
- 为 `IIocContainer.HasRegistration(Type)``<exception>` / `<remarks>`,并在 `docs/zh-CN/core/ioc.md` 新增用户接入说明,明确该入口按服务键而不是按可赋值关系判断可见性
- 更新 benchmark 相关注释到 NuGet `Mediator` 语义,并为 `BenchmarkRequestHandler` 增补显式 `Mediator.IRequestHandler<,>` 实现,降低未来升级时的契约漂移诊断成本
- 本轮权威验证:
- `dotnet build GFramework.Core.Abstractions/GFramework.Core.Abstractions.csproj -c Release`
- 结果:通过,`0 warning / 0 error`
- `dotnet build GFramework.Core/GFramework.Core.csproj -c Release`
- 结果:通过,`0 warning / 0 error`
- `dotnet build GFramework.Cqrs.Tests/GFramework.Cqrs.Tests.csproj -c Release`
- 结果:通过,`0 warning / 0 error`
- `dotnet build GFramework.Cqrs.Benchmarks/GFramework.Cqrs.Benchmarks.csproj -c Release`
- 结果:通过,`0 warning / 0 error`
- `dotnet test GFramework.Core.Tests/GFramework.Core.Tests.csproj -c Release --no-build --filter "FullyQualifiedName~MicrosoftDiContainerTests"`
- 结果:通过,`52/52` passed
- `dotnet test GFramework.Cqrs.Tests/GFramework.Cqrs.Tests.csproj -c Release --no-build --filter "FullyQualifiedName~CqrsDispatcherContextValidationTests"`
- 结果:通过,`4/4` passed
- `env GIT_DIR=... GIT_WORK_TREE=... python3 scripts/license-header.py --check`
- 结果:通过
- `git diff --check`
- 结果:通过
- 备注:仅剩 `GFramework.sln` 的历史 CRLF 警告,无本轮新增格式问题
- 下一步:
- 关注 `PR #340` 重新索引后的 latest-head open thread 是否随本轮提交自然收敛,尤其是 `HasRegistration(Type)` 相关 runtime / docs 线程
- 若后续继续压 request hot path可从 `CqrsDispatcher` 默认 request 路径与 generated invoker/provider 的进一步吸收空间继续下钻
### 阶段:性能回归门槛收紧与 benchmark 产物忽略收口CQRS-REWRITE-RP-102
- 延续 `RP-101` 后的 benchmark 基线,本轮没有继续改 runtime 热路径,而是先把性能治理规则补齐,避免后续优化波次出现“功能通过但 steady-state request 变慢”的回退

View File

@ -287,6 +287,7 @@ Architecture (架构层)
│ ├── GetRequired<T>() // 获取必需实例
│ ├── GetAllSorted<T>() // 排序获取
│ ├── Contains<T>() // 检查存在性
│ ├── HasRegistration() // 检查服务键是否已注册
│ ├── ContainsInstance() // 检查实例
│ ├── Clear() // 清空容器
│ └── Freeze() // 冻结容器
@ -588,6 +589,43 @@ if (!container.Contains<ISettingsService>())
- 检查依赖是否可用
- 动态功能开关
### `HasRegistration(Type type)`
检查某个服务键或开放泛型映射是否已经注册,但不会为了判断结果先解析实例。
```csharp
public bool HasRegistration(Type type)
```
**参数:**
- `type`:要检查的服务类型
**返回值:**
- 若存在显式服务键注册,或开放泛型注册可以闭合到该服务类型,则返回 `true`
- 若只注册了实现类型自身、但没有把对应接口作为服务键注册,则返回 `false`
**特点:**
- 适合 request / pipeline 等热路径上的“先判断再解析”场景
- 不会激活瞬态实例,也不会触发多服务枚举
- 语义与 `Get(Type)` / `GetAll(Type)` 保持一致,按服务键而不是按“可赋值关系”判断可见性
**使用示例:**
```csharp
var behaviorServiceType = typeof(IPipelineBehavior<CreatePlayerRequest, PlayerCreated>);
if (container.HasRegistration(behaviorServiceType))
{
foreach (var behavior in container.GetAll(behaviorServiceType))
{
Console.WriteLine(behavior.GetType().Name);
}
}
```
### `ContainsInstance(object instance)`
判断容器中是否包含某个具体的实例对象。