Skip to content

Conversation

@stakx
Copy link
Member

@stakx stakx commented Dec 19, 2025

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 → preview it here
  • update ref/ contract files
  • update 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
@stakx stakx marked this pull request as draft December 19, 2025 01:30
@stakx stakx force-pushed the byref-like-arguments branch 2 times, most recently from 75d36f1 to 34dde0e Compare December 19, 2025 10:42
@stakx stakx mentioned this pull request Jan 1, 2026
@stakx stakx force-pushed the byref-like-arguments branch from 34dde0e to 9abd5df Compare January 25, 2026 23:47
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 2 times, most recently from 0980f3f to fc07c42 Compare January 27, 2026 20:23
@stakx stakx force-pushed the byref-like-arguments branch from fc07c42 to e839375 Compare January 27, 2026 20:24
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.

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

1 participant