Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Add support for by-ref-like (ref struct) parameter types such as Span<T> and ReadOnlySpan<T> #712

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

Draft
stakx wants to merge 5 commits into castleproject:master
base: master
Choose a base branch
Loading
from stakx:byref-like-arguments

Conversation

@stakx
Copy link
Member

@stakx stakx commented Dec 19, 2025
edited
Loading

Unlike my earlier draft #664, this PR will not require DynamicProxy user code to set up any value converters for byref-like parameters.

Instead, any by-ref-like argument will automatically get substituted in IInvocation with a "reference" type:

  • (削除) SpanArgument<T> (削除ここまで) (削除) SpanProxy<T> (削除ここまで) SpanReference<T> for Span<T> values
  • (削除) ReadOnlySpanArgument<T> (削除ここまで) (削除) ReadOnlySpanProxy<T> (削除ここまで) ReadOnlySpanReference<T> for ReadOnlySpan<T> values
  • (削除) ByRefLikeProxy (削除ここまで) ByRefLikeReference on .NET 8 for any non-span by-ref-like values
  • (削除) ByRefLikeArgument<TByRefLike> (削除ここまで) (削除) ByRefLikeProxy<TByRefLike> (削除ここまで) ByRefLikeReference<TByRefLike> on .NET 9+ for any non-span by-ref-like values of type TByRefLike

Each of these (except the non-generic ByRefLikeReference) has a ref-returning Value property for accessing the actual value.

These class types are essentially references to the actual by-ref-like parameters. They use unmanaged pointers (void*) under the hood. I've added a big comment in the ByRefLikeReference.cs code file explaining why I think this is safe.

public interface IFoo
{
 void A(Span<char> characters);
 void B(ref Span<char> characters);
}
public void FooAInterceptor : IInterceptor
{
 public void Intercept(IInvocation invocation);
 {
 var charactersRef = invocation.Arguments[0] as SpanReference<char>;
 Span<char> characters = charactersRef.Value;
 }
}
public void FooBInterceptor : IInterceptor
{
 public void Intercept(IInvocation invocation);
 {
 var charactersRef = invocation.Arguments[0] as SpanReference<char>;
 Span<char> characters = charactersRef.Value;
 charactersRef.Value = characters[0..1]);
 }
}

I'm not quite done yet:

  • add support for reading/writing by-ref-like argument values and propagating them through the interception pipeline
  • add support for by-ref-like return values (as noted in a TODO in MethodWithInvocationGenerator.cs)
  • update / replace unit tests in ByRefLikeTestCase (which still expect by-ref-like parameters to default / get nullified)
  • write a documentation article on how to properly interact with by-ref-like arguments in IInvocation
  • update ref/ contract files and the changelog

Will fix #651 and close #663 once completed and merged.

@stakx stakx added this to the v6.0.0 milestone Dec 19, 2025
@stakx stakx self-assigned this Dec 19, 2025
Comment on lines 110 to 111
// TODO: If the return type is by-ref-like, we should prepare a local variable and a `ByRefLikeProxy` for it
// and place it in `IInvocation.ReturnValue`, so that the interception pipeline has a means to return something.
Copy link
Member Author

Choose a reason for hiding this comment

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

This TODO needs to be implemented so we can return by-ref-like values, too.

{
if (checkType != type)
{
throw new AccessViolationException();
Copy link
Member Author

Choose a reason for hiding this comment

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

Since we'll probably be adding a documentation page regarding by-ref-like values during interception, perhaps we could add exception messages with a link to the documentation page for more info...?

Comment on lines 279 to 280
// TODO: perhaps we should cache these `ConstructorInfo`s?
ConstructorInfo proxyCtor = typeof(ByRefLikeProxy<>).MakeGenericType(dereferencedArgumentType).GetConstructors().Single();
Copy link
Member Author

Choose a reason for hiding this comment

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

Should implement ConstructorInfo caching here, as the "todo" comment suggest.

Since Span<T> and ReadOnlySpan<T> are commonly encountered types in the FCL, perhaps constructed generic types deriving from these should also be in the cache.

Comment on lines +331 to +334
new MethodInvocationExpression(
ThisExpression.Instance,
InvocationMethods.GetArgumentValue,
new LiteralIntExpression(i)),
Copy link
Member Author

Choose a reason for hiding this comment

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

An alternative to calling IInvocation.GetArgumentValue would be to query IInvocation.Arguments once, and then access the returned arguments array directly for every by-ref-like parameter? That might be more efficient for methods having more than one by-ref-like-typed parameter.

Comment on lines 356 to 357
// TODO: For by-ref-like return values, we will need to read `IInvocation.ReturnValue`
// and set the return value via pointer indirection (`ByRefLikeProxy.GetPtr`).
Copy link
Member Author

Choose a reason for hiding this comment

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

Implement this "todo" for by-ref-like-typed return parameter support. And don't forget to also Invalidate the wrapper object for the return type.

stakx added 5 commits January 26, 2026 22:39
 * Calling `ByRefLikeProxy` et al. "proxies" could be misleading, since
 DynamicProxy proxies typically have the exact same public surface as
 the proxied types. This is not the case here, `ByRefLikeProxy` types
 come with their own distinct API.
 I am choosing "reference" because that's exactly what the types are.
 Alternatives considered were "value accessor" and "argument". The
 former would lead to long type names (`ByRefLikeValueAccessor`), and
 the latter would be inaccurate once we start using these types for
 `IInvocation.ReturnValue`, too.
 * There seems to be little benefit to having a parallel interface type
 hierarchy. On the contrary: users observing (say) a `SpanProxy` inst-
 ance in the debugger and then being told in an XML documentation
 comment to access it through the `ISpanProxy` interface doesn't seem
 particularly user-friendly. Let's go with the simplest solution: keep
 only the classes.
@stakx stakx force-pushed the byref-like-arguments branch from 9abd5df to 095ab6c Compare January 26, 2026 23:25
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Reviewers

No reviews

Labels

None yet

Projects

None yet

Milestone

v6.0.0

Development

Successfully merging this pull request may close these issues.

Support by-ref-like (ref struct) parameter types such as Span<T> and ReadOnlySpan<T> InvalidProgramException when proxying MemoryStream with .NET 7

1 participant

AltStyle によって変換されたページ (->オリジナル) /