Create dynamic proxies in the same ALC as the interface they implement#1248
Merged
Create dynamic proxies in the same ALC as the interface they implement#1248
Conversation
This corrects a bug that deviated from the original design. The idea was for all proxies to be generated into as few dynamic assemblies as possible. We have to allow for distinct dynamic assemblies for growing sets of skip visibility check attributes, but other than that we should reuse dynamic assemblies. The bug here was that we were not supplying a structural `AssemblyName` equality comparer to our `ImmutableHashSet`. Since `AssemblyName.Equals` is a reference equality check and the CLR does not de-dupe assembly names, we must supply our own equality comparison in order to get the intended behavior.
This fixes the problem that we were always creating in the 'contextual' ALC, but not filing them as associated with that ALC. As a result, multiple ALCs in a process might share a DynamicAssembly, leading to type load failures or type equivalency failures. To solve this, we are careful to never share dynamic assemblies across ALCs, and we document how callers can intentionally direct which ALC a dynamic proxy should be emitted into.
There was a problem hiding this comment.
Pull Request Overview
This PR introduces significant improvements to dynamic proxy generation in .NET environments with multiple AssemblyLoadContexts (ALCs). The main purpose is to ensure that StreamJsonRpc generates proxies in the correct ALC, preventing type resolution issues when interfaces are loaded from different contexts.
Key changes:
- Fix dynamic assembly reuse by providing a structural equality comparer for AssemblyName
- Generate proxies in the ALC that loaded the first interface rather than the ALC that loaded StreamJsonRpc
- Add comprehensive tests to validate ALC-specific proxy generation behavior
Reviewed Changes
Copilot reviewed 12 out of 12 changed files in this pull request and generated 3 comments.
Show a summary per file
| File | Description |
|---|---|
| test/UnreachableAssembly/UnreachableAssembly.csproj | Creates test assembly for ALC isolation testing |
| test/UnreachableAssembly/SomeUnreachableClass.cs | Simple test class in the unreachable assembly |
| test/UnreachableAssembly.targets | MSBuild configuration to make assembly unreachable from default ALC |
| test/StreamJsonRpc.Tests/UnreachableAssemblyTools.cs | Utility methods for ALC testing scenarios |
| test/StreamJsonRpc.Tests/StreamJsonRpc.Tests.csproj | Imports targets for unreachable assembly testing |
| test/StreamJsonRpc.Tests/JsonRpcProxyGenerationTests.cs | Adds tests for dynamic assembly reuse and ALC-specific proxy generation |
| test/StreamJsonRpc.Tests.NoInterceptors/StreamJsonRpc.Tests.NoInterceptors.csproj | Includes unreachable assembly tools for no-interceptors testing |
| src/StreamJsonRpc/SkipClrVisibilityChecks.cs | Fixes assembly name equality comparison and extracts comparer to separate class |
| src/StreamJsonRpc/ProxyGeneration.cs | Implements ALC-aware proxy generation and fixes dynamic assembly reuse |
| src/StreamJsonRpc/AssemblyNameEqualityComparer.cs | New structural equality comparer for AssemblyName instances |
| docfx/docs/proxies.md | Documents ALC considerations and usage patterns for dynamic proxies |
| StreamJsonRpc.slnx | Adds UnreachableAssembly project to solution |
Tip: Customize your code reviews with copilot-instructions.md. Create the file or learn how to get started.
You can also share your feedback on Copilot code review for a chance to win a $100 gift card. Take the survey.
ekoppel
approved these changes
Aug 14, 2025
This was referenced Jan 22, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
See also #1247, which merges these changes into v2.22 (for Dev17.14)
This pull request introduces significant improvements to how dynamic proxies are generated and loaded in .NET environments with multiple AssemblyLoadContexts (ALCs). The main focus is to ensure that StreamJsonRpc generates proxies in the correct ALC, preventing type resolution issues when interfaces are loaded from different contexts. The changes also add new tests and documentation to validate and explain this behavior.
This change fixes two bugs:
Create dynamic proxies in fewer dynamic assemblies
This corrects a bug that deviated from the original design. The idea was for all proxies to be generated into as few dynamic assemblies as possible. We have to allow for distinct dynamic assemblies for growing sets of skip visibility check attributes, but other than that we should reuse dynamic assemblies.
The bug here was that we were not supplying a structural
AssemblyNameequality comparer to ourImmutableHashSet. SinceAssemblyName.Equalsis a reference equality check and the CLR does not de-dupe assembly names, we must supply our own equality comparison in order to get the intended behavior.Generating proxies into the right ALC
This fixes the problem that we were always creating in the 'contextual' ALC, but not filing them as associated with that ALC. As a result, multiple ALCs in a process might share a DynamicAssembly, leading to type load failures or type equivalency failures.
To solve this, we are careful to never share dynamic assemblies across ALCs, and we document how callers can intentionally direct which ALC a dynamic proxy should be emitted into.
We now default to creating dynamic assemblies in the ALC that loaded the first interface rather than into the ALC that loaded StreamJsonRpc.