Skip to content

Fix System.TypeLoadException in NukedBit.NRepo global tool#127188

Draft
Copilot wants to merge 6 commits intomainfrom
copilot/dotnet-sdk-issue-fix
Draft

Fix System.TypeLoadException in NukedBit.NRepo global tool#127188
Copilot wants to merge 6 commits intomainfrom
copilot/dotnet-sdk-issue-fix

Conversation

Copy link
Copy Markdown
Contributor

Copilot AI commented Apr 20, 2026

Fixes a TypeLoadException thrown when loading old IHostBuilder implementations (e.g. from Microsoft.Extensions.Hosting v2.2.0.0) on .NET 11. These old assemblies lack the UseServiceProviderFactory(Func<HostBuilderContext, IServiceProviderFactory<TContainerBuilder>>) overload added in v3.0, causing the .NET type-loader to reject them at class-load time.

Description

Root Cause

IHostBuilder.UseServiceProviderFactory(Func<HostBuilderContext, IServiceProviderFactory<TContainerBuilder>>) was added in v3.0. Old assemblies implementing only the v2.x interface shape fail the .NET type-loader check that all interface members are implemented, resulting in a TypeLoadException.

Fix

Adds a Default Interface Method (DIM) for the Func-based overload, guarded by #if NET:

  • Old, pre-compiled implementations use the DIM as a fallback → no TypeLoadException
  • The DIM null-checks factory (throwing ArgumentNullException) then throws NotSupportedException with the concrete type name if actually invoked
  • Modern implementations (HostBuilder, HostApplicationBuilder) explicitly override this method and are unaffected — explicit overrides always win over DIMs
  • .NET Standard / .NET Framework targets are unchanged (DIMs aren't supported there)

The reference assembly intentionally keeps the plain abstract signature (no DIM body) so that new code compiling against the latest reference is still required to implement the method.

Changes

  • src/IHostBuilder.cs — adds DIM with ArgumentNullException.ThrowIfNull guard, NotSupportedException, and <exception> XML doc under #if NET
  • ref/Microsoft.Extensions.Hosting.Abstractions.cs — retains plain abstract signature (no DIM body) so callers compiling against the latest reference must still implement the method
  • tests/HostBuilderContextTests.cs — adds 3 tests under #if NET covering: NotSupportedException when DIM is the fallback, ArgumentNullException for null factory, and that the non-Func overload is unaffected

Testing

  • All existing abstraction tests pass
  • New tests validate DIM behavior:
    • UseServiceProviderFactory_FuncOverload_WithoutOverride_ThrowsNotSupportedException
    • UseServiceProviderFactory_FuncOverload_WithNullFactory_ThrowsArgumentNullException
    • UseServiceProviderFactory_NonFuncOverload_WithoutDIM_CanBeImplementedAndCalled

…prevent TypeLoadException

Adds a default interface method (DIM) for the Func-based UseServiceProviderFactory
overload in IHostBuilder for .NET Core targets. This prevents TypeLoadException when
loading old IHostBuilder implementations (e.g., from Microsoft.Extensions.Hosting
v2.2.0.0) that only implement the non-Func overload on .NET 11+.

The DIM is not added for .NET Standard or .NET Framework targets since we don't need
backward compat shims there.

Fixes: TypeLoadException in NukedBit.NRepo and other tools using old Hosting assemblies.

Agent-Logs-Url: https://github.com/dotnet/runtime/sessions/186bed25-e477-43a0-ab1f-660ba8bc7769

Co-authored-by: ericstj <8918108+ericstj@users.noreply.github.com>
Copilot AI requested review from Copilot and removed request for Copilot April 21, 2026 00:06
Copilot AI requested review from Copilot and ericstj and removed request for Copilot April 21, 2026 00:21
Copilot stopped work on behalf of ericstj due to an error April 21, 2026 00:30
@ericstj
Copy link
Copy Markdown
Member

ericstj commented Apr 21, 2026

@copilot can you address feedback and fix the PR description to be an appropriate summary, and mention that it fixes issue #126994.

…mentations to override

Agent-Logs-Url: https://github.com/dotnet/runtime/sessions/bb09ea95-ca82-4973-92b7-3d829aa23711

Co-authored-by: ericstj <8918108+ericstj@users.noreply.github.com>
Copilot AI requested review from Copilot and removed request for Copilot April 21, 2026 01:53
Copy link
Copy Markdown
Contributor Author

Copilot AI commented Apr 21, 2026

@copilot can you address feedback and fix the PR description to be an appropriate summary, and mention that it fixes issue #126994.

Done. Removed the DIM body from the reference assembly (dd524cb) and updated the PR description to be a proper summary mentioning the fix for #126994.

Copilot AI changed the title [WIP] Fix System.TypeLoadException in NukedBit.NRepo global tool Fix System.TypeLoadException in NukedBit.NRepo global tool Apr 21, 2026
Copilot AI requested a review from ericstj April 21, 2026 01:54
@dotnet-policy-service
Copy link
Copy Markdown
Contributor

Tagging subscribers to this area: @dotnet/area-extensions-hosting
See info in area-owners.md if you want to be subscribed.

Copilot AI review requested due to automatic review settings April 21, 2026 03:26
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This PR addresses a runtime TypeLoadException when older IHostBuilder implementations (pre-v3.0 interface shape) are loaded on modern .NET by introducing a .NET-only default interface method (DIM) for the newer UseServiceProviderFactory(Func<...>) overload, and adding tests to validate the fallback behavior.

Changes:

  • Add a #if NET DIM implementation for IHostBuilder.UseServiceProviderFactory(Func<HostBuilderContext, IServiceProviderFactory<T>>) that null-checks and throws NotSupportedException when used as fallback.
  • Update the test project to compile against the implementation assembly (not the ref assembly) so the DIM body can be exercised.
  • Add #if NET tests validating the DIM fallback throws, null argument behavior, and that the non-Func overload remains callable.

Reviewed changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated 2 comments.

File Description
src/libraries/Microsoft.Extensions.Hosting.Abstractions/src/IHostBuilder.cs Adds a .NET-only DIM body for the Func-based overload to prevent type-load failures for older implementations.
src/libraries/Microsoft.Extensions.Hosting.Abstractions/tests/Microsoft.Extensions.Hosting.Abstractions.Tests.csproj Switches the project reference to skip ref assemblies so tests can compile/run against the DIM implementation.
src/libraries/Microsoft.Extensions.Hosting.Abstractions/tests/HostBuilderContextTests.cs Adds #if NET tests covering DIM fallback behavior and argument validation.

Comment thread src/libraries/Microsoft.Extensions.Hosting.Abstractions/src/IHostBuilder.cs Outdated
@github-actions
Copy link
Copy Markdown
Contributor

🤖 Copilot Code Review — PR #127188

Note

This review was generated by Copilot and validated across multiple AI models (Claude Opus 4.6, GPT-5.3-Codex, extensions-reviewer agent). All models reached the same conclusions on key findings.

Holistic Assessment

Motivation: Justified. This fixes a real regression (#126994) where old IHostBuilder implementations (e.g., from Microsoft.Extensions.Hosting v2.2.0.0) throw TypeLoadException on .NET 11 because they lack the UseServiceProviderFactory(Func<...>) overload added in v3.0. Root cause confirmed by @jkotas as a regression from dotnet/extensions#637 via dotnet/dotnet#5206.

Approach: Correct. Adding a DIM is the standard .NET pattern for backward-compatible interface evolution. The design is well-considered: old binaries get a graceful NotSupportedException instead of a catastrophic TypeLoadException, while new code compiled against the ref assembly is still forced to implement the method.

Summary: ✅ LGTM. The DIM implementation is correct, the ref assembly handling is intentional and appropriate, and the tests cover the key scenarios. Two minor documentation suggestions below are non-blocking.


Detailed Findings

✅ DIM Pattern — Correct and well-designed

The #if NET guard, NotSupportedException choice, and argument-first validation are all correct:

  • #if NET is needed because DIMs require .NET Core 3.0+ (matches existing patterns in BackgroundService.cs:72, HostingAbstractionsHostExtensions.cs:100)
  • NotSupportedException correctly communicates "this implementation does not support this operation" (not NotImplementedException which means TODO)
  • ArgumentNullException.ThrowIfNull(factory) before the NotSupportedException is correct — a caller passing null should get an actionable ArgumentNullException, not a misleading NotSupportedException

Both existing IHostBuilder implementations (HostBuilder and HostApplicationBuilder.HostBuilderAdapter) already override this method with full implementations, so they are unaffected.

✅ Ref Assembly — Intentionally kept abstract

The ref assembly retains the plain abstract signature (no DIM body). This means new code compiled against the latest SDK is still required to implement the method — the DIM only serves as a runtime fallback for old pre-compiled binaries. This is the correct choice given that this method is functionally required for proper hosting behavior.

✅ Test Coverage — Adequate for the DIM behavior

The three tests cover the key scenarios: DIM throws NotSupportedException, null input throws ArgumentNullException, and the non-Func overload works normally. The SkipUseReferenceAssembly="true" in the csproj is essential to compile tests against the implementation assembly where the DIM body is visible.

✅ csproj Change — Correct

Merging the two ItemGroups and making the Hosting.Abstractions reference unconditional with SkipUseReferenceAssembly="true" is needed for the DIM tests. The .NET Framework build is unaffected since the test class is guarded by #if NET.

💡 Missing <exception cref="ArgumentNullException"> XML doc — Minor

The DIM can throw both ArgumentNullException (for null factory) and NotSupportedException, but only NotSupportedException is documented. Consider adding:

/// <exception cref="ArgumentNullException"><paramref name="factory"/> is <see langword="null"/>.</exception>

(Flagged by multiple models. Non-blocking — the existing XML doc for the non-DIM path also lacks this.)

💡 Test could assert ParamName — Minor

The null test uses Assert.Throws<ArgumentNullException> but does not verify ParamName. The existing tests in this file use AssertExtensions.Throws<ArgumentNullException>("properties", ...) for more precise assertions. For consistency:

AssertExtensions.Throws<ArgumentNullException>("factory", () =>
    builder.UseServiceProviderFactory<IServiceCollection>(factory));

(Non-blocking.)

Generated by Code Review for issue #127188 ·

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[dotnet-sdk-11.0.100-preview.4.26214.103] GlobalToolsNETCore22 throws System.TypeLoadException when running nrepo -n:newrepo

5 participants