Skip to content

Handling F# assemblies correctly by catching reflection exceptions an…#1156

Merged
jbogard merged 2 commits intomainfrom
1140-having-a-function-with-inref-parameter-crashes-assembly-scanner
Feb 22, 2026
Merged

Handling F# assemblies correctly by catching reflection exceptions an…#1156
jbogard merged 2 commits intomainfrom
1140-having-a-function-with-inref-parameter-crashes-assembly-scanner

Conversation

@jbogard
Copy link
Collaborator

@jbogard jbogard commented Feb 22, 2026

…d falling back to GetTypeInfo

@jbogard jbogard linked an issue Feb 22, 2026 that may be closed by this pull request
@jbogard
Copy link
Collaborator Author

jbogard commented Feb 22, 2026

Fix: Assembly Scanner Crashes on F# inref Parameters (Issue #1140)

Context

When an assembly being scanned by MediatR contains F# code with inref parameters (e.g. member inline _.Yield (span: inref<Span<char>>)), the assembly scanner throws:

System.Reflection.ReflectionTypeLoadException: Unable to load one or more of the requested types. A ByRef or ByRef-like type cannot be used as the type for an instance field in a non-ByRef-like type.

F# inref<T> and byref<T> are ByRef-like types. .NET reflection throws ReflectionTypeLoadException when trying to enumerate all types in an assembly containing such constructs because it cannot represent them as standard CLR instance fields during type metadata inspection.

Currently, all four type-loading sites in ServiceRegistrar.cs call a.DefinedTypes or assembly.GetTypes() directly, with no exception handling.

Fix: Add a single private helper GetLoadableDefinedTypes(this Assembly) that catches ReflectionTypeLoadException and returns only the successfully-loaded types from e.Types. Replace all four call sites with this helper.

File to Modify

src/MediatR/Registration/ServiceRegistrar.cs

Four Call Sites to Fix

  • Line 79 — a.DefinedTypes in AddMediatRClasses (multi-open interfaces scan)
  • Line 106 — a.DefinedTypes in ConnectImplementationsToTypesClosing
  • Line 236 — assembly.GetTypes() in GetConcreteRequestTypes
  • Line 428 — a.DefinedTypes in RegisterClosedBehaviorsFromAssemblies

Implementation

Note on DefinedTypes vs GetTypes()

Per the .NET docs, both return the same set of types (public, internal, private, and nested). The only difference is the return type: DefinedTypesIEnumerable<TypeInfo>, GetTypes()Type[]. Since TypeInfo inherits from Type, every property and method used in ServiceRegistrar.cs (IsClass, IsAbstract, IsAssignableFrom, GetInterfaces, etc.) is identical. Replacing the GetTypes() call at line 236 with GetLoadableDefinedTypes() has no behavior change.

1. Add helper method (after the existing Fill extension, before HasNestedGenericResponseType)

private static IEnumerable<TypeInfo> GetLoadableDefinedTypes(this Assembly assembly)
{
    try
    {
        return assembly.DefinedTypes;
    }
    catch (ReflectionTypeLoadException ex)
    {
        return ex.Types.Where(t => t != null).Select(t => t!.GetTypeInfo());
    }
}

TypeInfo inherits from Type, so this return type is compatible with all four call sites.

2. Replace the four call sites

Line 79 — multi-open interfaces scan:

// before
.SelectMany(a => a.DefinedTypes)
// after
.SelectMany(a => a.GetLoadableDefinedTypes())

Line 106ConnectImplementationsToTypesClosing:

// before
.SelectMany(a => a.DefinedTypes)
// after
.SelectMany(a => a.GetLoadableDefinedTypes())

Line 236GetConcreteRequestTypes (note: currently uses GetTypes(), not DefinedTypes):

// before
.SelectMany(assembly => assembly.GetTypes())
// after
.SelectMany(assembly => assembly.GetLoadableDefinedTypes())

Line 428RegisterClosedBehaviorsFromAssemblies:

// before
.SelectMany(a => a.DefinedTypes)
// after
.SelectMany(a => a.GetLoadableDefinedTypes())

Verification

dotnet test

All existing tests should continue to pass. The fix is purely defensive — it only changes behavior when ReflectionTypeLoadException is thrown during type enumeration, which does not happen with normal C# assemblies.

Copy link
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 pull request adds robust handling for F# assemblies and other assemblies that may throw ReflectionTypeLoadException when accessing their defined types. The changes introduce a new extension method GetLoadableDefinedTypes that gracefully catches these exceptions and returns only the successfully loaded types.

Changes:

  • Added GetLoadableDefinedTypes extension method to safely access assembly types with exception handling
  • Updated all assembly type scanning operations to use the new safe method
  • Added comprehensive test coverage for assemblies that throw ReflectionTypeLoadException

Reviewed changes

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

File Description
src/MediatR/Registration/ServiceRegistrar.cs Added GetLoadableDefinedTypes extension method that catches ReflectionTypeLoadException and returns loadable types; replaced all direct DefinedTypes accesses with calls to this new method (lines 79, 106, 236, 440)
test/MediatR.Tests/MicrosoftExtensionsDI/AssemblyResolutionTests.cs Added test ShouldNotThrowWhenAssemblyThrowsReflectionTypeLoadException with a mock BrokenAssembly class that simulates F# assembly behavior by throwing ReflectionTypeLoadException

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

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
@jbogard jbogard merged commit a63f938 into main Feb 22, 2026
5 checks passed
@jbogard jbogard deleted the 1140-having-a-function-with-inref-parameter-crashes-assembly-scanner branch February 22, 2026 18:57
@jbogard jbogard added this to the 14.1.0 milestone Feb 22, 2026
This was referenced Mar 9, 2026
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.

Having a function with inref parameter crashes assembly scanner

2 participants