Skip to content

Remove runtime generic construction from module hosting path - #41

Merged
fw2568 merged 1 commit into
mainfrom
refactor/module-registration-aot
Jul 21, 2026
Merged

Remove runtime generic construction from module hosting path#41
fw2568 merged 1 commit into
mainfrom
refactor/module-registration-aot

Conversation

@fw2568

@fw2568 fw2568 commented Jul 21, 2026

Copy link
Copy Markdown
Contributor

First step toward trimming/Native-AOT friendliness (part A of the reflection cleanup). No behavior change, no public-API break.

What

Module hosting previously reconstructed the closed generic types from a stored Type at runtime:

  • ModulesHostBuilder used typeof(IModuleHost<>).MakeGenericType(module.Key) (and ModulesHostService<>, plus a second site for bootstrap) to register/resolve the per-module host.
  • The two Generic*Adapter helpers built their per-module filter via MakeGenericType + Activator.CreateInstance.

These runtime generic constructions are the main obstacle to Native AOT (arbitrary generic instantiations that aren't statically visible).

How

  • Introduce IModuleRegistration / ModuleRegistration<TModule>, captured at HostModule<TModule>(). The module type stays a static generic parameter, so IModuleHost<TModule>, ModulesHostService<TModule> and the bootstrap call are all statically visible — no MakeGenericType.
  • Construct the three filter adapters that already live in generic classes (DefaultBootstrapHostHandler<TModule>, BootstrapHostFilter<TModule>) directly as closed generics instead of via the reflective Create(Type) helper.

Residual (intentional, documented)

Reflection remains only on two legacy paths, both clearly scoped:

  • the non-generic HostModule(Type, ...) overload (module type only known at runtime), and
  • the non-generic AddSimpleInjectorModuleServicesFilter adapter.

Tests

All existing suites pass unchanged on net6.0/net8.0/net9.0 (behavior-preserving refactor).

Module hosting reconstructed the closed generic types (IModuleHost<TModule>,
ModulesHostService<TModule>, module bootstrap) from a stored Type via
MakeGenericType, and the two Generic*Adapter helpers built their per-module
filter via MakeGenericType + Activator.CreateInstance. These runtime
constructions are the main obstacle to trimming/Native AOT.

Introduce a generic ModuleRegistration<TModule> captured at HostModule<TModule>()
so all closed generics are statically visible, and construct the three filter
adapters that live in generic classes (DefaultBootstrapHostHandler<TModule>,
BootstrapHostFilter<TModule>) directly instead of reflectively.

Behavior is unchanged. Reflection remains only on two clearly-scoped legacy
paths: the non-generic HostModule(Type, ...) overload and the non-generic
AddSimpleInjectorModuleServicesFilter adapter.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Refactors module hosting to avoid runtime closed-generic construction (reflection-based MakeGenericType/Activator.CreateInstance) on the primary hosting path, improving trimming / Native AOT friendliness while aiming to preserve behavior and public API shape.

Changes:

  • Replaces per-module runtime generic reconstruction in ModulesHostBuilder with IModuleRegistration / ModuleRegistration<TModule> captured at HostModule<TModule>().
  • Updates bootstrap filter pipelines to use directly constructed closed-generic adapters instead of reflective adapter factories.
  • Introduces ModuleRegistration<TModule> to centralize module + host registration and bootstrapping without MakeGenericType on the generic path.

Reviewed changes

Copilot reviewed 4 out of 4 changed files in this pull request and generated 1 comment.

File Description
src/Hosuto.SimpleInjector/Modules/Hosting/BootstrapHostFilter.cs Replaces reflective adapter creation with direct closed-generic adapter instances in SimpleInjector bootstrap filters.
src/Hosuto.Hosting/Modules/Hosting/ModulesHostBuilder.cs Switches module tracking to registrations and removes reflection-based host registration on the generic path.
src/Hosuto.Hosting/Modules/Hosting/Internal/ModuleRegistration.cs Adds IModuleRegistration and ModuleRegistration<TModule> to encapsulate module/host registration and bootstrapping.
src/Hosuto.Hosting/Modules/Hosting/DefaultBootstrapHostHandler.cs Replaces reflective adapter creation with direct closed-generic adapter construction for module services filters.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines 35 to 39
moduleContext.Advanced.FrameworkServices.GetServices<IConfigureContainerFilter>()
.Append(GenericModuleContextFilterAdapter<Container>.Create(typeof(IConfigureContainerFilter<>))),
.Append(configureContainerAdapter),
(ctx, c) =>
{
ModuleMethodInvoker.CallOptionalMethod(ctx, "ConfigureContainer", container);

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pre-existing behavior, not changed by this PR — the diff here only swaps the adapter construction, not this delegate (same pattern in UseSimpleInjector/ConfigureServices). In practice the container/options filters mutate the passed instance rather than substituting a different one, so c == the captured container. Leaving it out of scope of this no-behavior-change refactor; can be cleaned up separately.

@fw2568
fw2568 merged commit 31a3d41 into main Jul 21, 2026
4 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants