Skip to content

Commit 1dbf409

Browse files
committed
Add new documentation about byref-like parameters
1 parent b35a3cc commit 1dbf409

2 files changed

Lines changed: 107 additions & 0 deletions

File tree

Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
# Support for byref-like (`ref struct`) parameters and return types
2+
3+
Starting with version 6.0.0 of the library, and when targeting .NET 8 or later, DynamicProxy has comprehensive support for byref-like types.
4+
5+
6+
## What are byref-like types?
7+
8+
Byref-like types – also known as `ref struct` types in C# – are a special category of types that by definition live exclusively on the evaluation stack. Therefore, they can never be found on the heap, or be parts of heap-allocated objects. This implies that unlike other value types, they cannot be boxed (converted to `object`).
9+
10+
11+
## How does DynamicProxy place `ref struct` values into an `IInvocation`?
12+
13+
The impossibility of converting byref-like values to `object` poses a fundamental problem for DynamicProxy, which represents all of an intercepted method's arguments as well as the intended return value as `object`s in an `IInvocation` instance: arguments are placed in the `object[]`-typed `IInvocation.Arguments` property, and the intended return value can be set via the `object`-typed `IInvocation.ReturnValue` property. So how can `ref struct`-typed argument values possibly appear in `IInvocation`?
14+
15+
The answer is that they cannot. DynamicProxy substitutes `ref struct` argument values with values of different, boxable types:
16+
17+
* `System.Span<T>` values are replaced with instances of `Castle.DynamicProxy.SpanReference<T>`.
18+
* `System.ReadOnlySpan<T>` values are replaced with instances of `Castle.DynamicProxy.ReadOnlySpanReference<T>`.
19+
* Values of any other `ref struct` type `TByRefLike` are replaced with instances of `Castle.DynamicProxy.ByRefLikeReference<TByRefLike>` (for .NET 9+) or with `Castle.DynamicProxy.ByRefLikeReference` (for .NET 8).
20+
21+
Here is a diagram showing these types' hierarchy:
22+
```
23+
(not for direct use) (only available on .NET 9+)
24+
+----------------------+ +----------------------------------+ +--------------------------------+
25+
| ByRefLikeReference | <----- | ByRefLikeReference<TByRefLike> | <------- | ReadOnlySpanReference<T> |
26+
+----------------------+ +==================================+ \ +================================+
27+
| +Value: ref TByRefLike | \ | +Value: ref ReadOnlySpan<T> |
28+
+----------------------------------+ \ +-------------------------------++
29+
\
30+
\ +------------------------+
31+
\ | SpanReference<T> |
32+
---- +========================+
33+
| +Value: ref Span<T> |
34+
+------------------------+
35+
```
36+
37+
These types do not contain the actual byref-like values, but (like their names imply) they hold a "reference" that they can resolve to the values. With the exception of the non-generic `ByRefLikeReference` substitute type (which is meant for use by the DynamicProxy runtime, not by user code!), all of these expose a `ref`-returning `Value` property which can be used to:
38+
39+
* read the actual byref-like argument or return value; or,
40+
* for `ref` and `out` parameters, update the parameter.
41+
42+
43+
## Basic usage example
44+
45+
46+
### Reading (and updating) a byref-like parameter
47+
48+
```csharp
49+
public interface TypeToBeProxied
50+
{
51+
void Method(ref Span<int> arg);
52+
}
53+
54+
var proxy = proxyGenerator.CreateInterfaceProxyWithoutTarget<TypeToBeProxied>(new MethodInterceptor());
55+
56+
class MethodInterceptor : IInterceptor
57+
{
58+
public void Intercept(IInvocation invocation)
59+
{
60+
var argRef = (SpanReference<int>)invocation.Arguments[0];
61+
62+
Span<int> arg = argRef.Value; // read the argument value
63+
argRef.Value = arg[0..^1]; // update the parameter (only makes sense for `ref` and `out` parameters)
64+
}
65+
}
66+
```
67+
68+
69+
### Returning a byref-like value
70+
71+
```csharp
72+
public interface TypeToBeProxied
73+
{
74+
ReadOnlySpan<char> Method();
75+
}
76+
77+
var proxy = proxyGenerator.CreateInterfaceProxyWithoutTarget<TypeToBeProxied>(new MethodInterceptor());
78+
79+
class MethodInterceptor : IInterceptor
80+
{
81+
public void Intercept(IInvocation invocation)
82+
{
83+
var returnValueRef = (ReadOnlySpanReference<int>)invocation.ReturnValue;
84+
85+
var returnValue = "Hello there!".AsSpan();
86+
returnValueRef.Value = returnValue;
87+
}
88+
}
89+
```
90+
91+
92+
## Rules for safe & correct usage
93+
94+
1. ✅ The only permissible interaction with values of the above-mentioned substitute types &ndash; `SpanReference<T>`, `ByRefLikeReference<TByRefLike>`, etc. &ndash; is reading and writing their `Value` property.
95+
96+
2. 🚫 The substitutes may only be accessed while the intercepted method is running. Once it returns, the substitute values become invalid, and any further attempts at using them in any way will cause immediate `AccessViolationException`s. (The reason for this is that the substitutes reference storage locations in the intercepted method's stack frame. Once the method stops executing, its stack frame is popped off the evaluation stack, which effectively ends the method's arguments' lifetime.)
97+
98+
- DynamicProxy tries to prevent you from making this mistake by actively erasing the substitute values from the `IInvocation` instance once the intercepted method returns to the caller.
99+
100+
- Note also that the substitute values cannot survive an async `await` or `yield return` boundary for the same reason. (This is not unlike the C# language rule which forbids the use of byref-like parameters in `async` methods.)
101+
102+
- A final consequence of this is that `ref struct` arguments will be unavailable in an interception pipeline restarted by `IInvocationProceedInfo` / `invocation.CaptureProceedInfo`.
103+
104+
3. 🚫 It is very strongly recommended that values of the substitute types not be copied anywhere else. DynamicProxy places them in specific spots inside an `IInvocation` instance, and that is precisely where they should stay. Don't copy them into variables for later use, or from one `IInvocation.Arguments` array position to another, etc.
105+
106+
- DynamicProxy performs some checks against this practice, and if any such attempt is detected, an `AccessViolationException` gets thrown.

docs/dynamicproxy.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ If you're new to DynamicProxy you can read a [quick introduction](dynamicproxy-i
1818
* [Make your supporting classes serializable](dynamicproxy-serializable-types.md)
1919
* [Use proxy generation hooks and interceptor selectors for fine grained control](dynamicproxy-fine-grained-control.md)
2020
* [SRP applies to interceptors](dynamicproxy-srp-applies-to-interceptors.md)
21+
* [Support for byref-like (`ref struct`) parameters and return types](dynamicproxy-byref-like-parameters.md)
2122
* [Behavior of by-reference parameters during interception](dynamicproxy-by-ref-parameters.md)
2223
* [Optional parameter value limitations](dynamicproxy-optional-parameter-value-limitations.md)
2324
* [Asynchronous interception](dynamicproxy-async-interception.md)

0 commit comments

Comments
 (0)