Skip to content

Create dynamic proxies in the same ALC as the interface they implement#1248

Merged
AArnott merged 7 commits intomainfrom
dev/andarno/fixDynamicAssemblies_main
Aug 14, 2025
Merged

Create dynamic proxies in the same ALC as the interface they implement#1248
AArnott merged 7 commits intomainfrom
dev/andarno/fixDynamicAssemblies_main

Conversation

@AArnott
Copy link
Member

@AArnott AArnott commented Aug 14, 2025

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 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.

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.

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.
Copy link

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 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.

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.

3 participants