Skip to content

[RuntimeAsync] Tweak some reflection scenarios around async methods. #118045

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 23 commits into from
Aug 1, 2025

Conversation

VSadov
Copy link
Member

@VSadov VSadov commented Jul 24, 2025

Closes: #115099
Closes: #115101

This is a follow up that addresses a few concerns around reflection behavior with Async/Task-returning methods and adds tests.

The general theme for reflection is that async variant methods "do not exist". This is the least confusing approach, since these methods are not present in the IL. It also avoids issues with representation of the variants and their invokability.

There is one caveat around infrastructure helpers like Await/AwaitAwaiter. These are visible in reflection, since they are present in the IL, but they are async methods.
Ideally they should not be invokable form non-async methods and this change makes reflection invoke to throw for them.

NOTE: It is still possible to construct dynamic IL that calls the helpers from non-async caller. This is a fairly advanced scenario that is unlikely to happen by accident. It may be ok to leave this for v11.

In this change:

  • getting an async variant by name: not possible.
    Query by name skips async variants, returns actual methods in IL. This was the case even before this change.
  • getting "real" infrastructure methods like AsyncHelpers.Await via reflection: possible.
  • invoking infrastructure methods like AsyncHelpers.Await via MethodInfo Invoke: throws
    The message says: "Async infrastructure methods are not supported in reflection invocation."
  • Getting an async variant by slot index: not possible now.
    Getting methods by a slot number is an internal API. There is observable impact on the GetInterfaceMap, which before this change could return duplicate mappings for Task-returning methods due to presence of other variants.
  • dynamic IL as in regular Reflection.Emit case (not DynamicMethod case):
  • Reflection.Emit Task-returning methods can be called/awaited in async methods
  • Reflection.Emit callers can call Task-returning methods and are not confused by variants.
  • Reflection.Emit Task-returning methods decorated as MethodImpl.Async - can await via Await pattern, can be called and can be awaited.
  • dynamic IL as in DynamicMethod case:
  • Blocked. Cannot specify MethodImpl flags to these methods through current APIs.

Copy link
Contributor

Tagging subscribers to this area: @mangod9
See info in area-owners.md if you want to be subscribed.

@VSadov VSadov requested a review from jkotas July 25, 2025 18:20
@VSadov VSadov marked this pull request as ready for review July 25, 2025 18:20
@Copilot Copilot AI review requested due to automatic review settings July 25, 2025 18:20
Copy link
Contributor

@Copilot 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 tweaks reflection behavior around async methods by ensuring async variant methods are not visible through reflection APIs. The changes implement a consistent approach where async variants "do not exist" in reflection since they're not present in IL, while making infrastructure helpers like AsyncHelpers.Await visible but non-invokable through reflection.

Key changes:

  • Async variant methods are filtered out from reflection queries and interface mappings
  • Infrastructure async methods throw when invoked via reflection
  • Tests are added to verify the new reflection behavior with async methods

Reviewed Changes

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

Show a summary per file
File Description
src/tests/async/reflection/reflection-simple.csproj Enables the test project by adding process isolation and priority settings
src/tests/async/reflection/reflection-simple.cs Adds comprehensive tests for async method reflection behavior including dynamic invocation, interface mapping, and dynamic IL scenarios
src/tests/async/Directory.Build.targets Uncomments the DisableProjectBuild property to enable async test execution
src/libraries/System.Private.CoreLib/src/Resources/Strings.resx Adds error message for unsupported async infrastructure method invocation
src/coreclr/vm/runtimehandles.cpp Filters out async variant methods from RuntimeTypeHandle_GetMethodAt
src/coreclr/vm/reflectioninvocation.cpp Adds check to throw NotSupportedException when invoking async methods via reflection
src/coreclr/inc/clrconfigvalues.h Enables RuntimeAsync feature by default (changes default from 0 to 1)
src/coreclr/System.Private.CoreLib/src/System/RuntimeType.CoreCLR.cs Updates GetInterfaceMap to handle null method handles and resize arrays when async variants are filtered out

@VSadov VSadov enabled auto-merge (squash) August 1, 2025 17:53
@VSadov VSadov merged commit 1752b96 into dotnet:main Aug 1, 2025
146 checks passed
@VSadov VSadov deleted the asDyn branch August 1, 2025 21:58
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.

[RuntimeAsync] Dynamic methods (System.Reflection.Emit). [RuntimeAsync] Reflection/introspection support
2 participants