castleproject / Core

Castle Core, including Castle DynamicProxy, Logging Services and DictionaryAdapter
http://www.castleproject.org/
Other
2.2k stars 467 forks source link

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

Open stakx opened 1 year ago

stakx commented 1 year ago

This is a partially working proof of concept for #663, but it still needs quite a bit of work:


Usage example 1: defining conversions for by-ref-like argument values

This shows how one would set up a converter for by-ref-like argument values. In this case, one gets set up such that Span<byte> arguments will get converted to a byte[] copy during invocation:

// this will get invoked during interception:
public class CopyByteSpanConverter
{
    // a converter for by-ref-like argument values must be accessible to the dynamically generated assembly,
    // and it must have...

    // (1) for inbound conversions, a public `Box` method
    //     with a single `in` parameter of a by-ref-like type, and return type `object`:
    public object Box(in Span<byte> span)
    {
        return span.ToArray();
    }

    // (2) for outbound conversions, a public `Unbox` method
    //     with an `object` parameter and a `ref` parameter of a by-ref-like type:
    public void Unbox(object argument, ref Span<byte> span)
    {
        var array = (byte[])argument;
        array.CopyTo(span);
    }
}

// this will get invoked during proxy type generation:
class ConverterSelector : IByRefLikeConverterSelector
{
    public Type SelectConverterType(MethodInfo method, int parameterPosition, Type parameterType)
    {
        return parameterType == typeof(Span<byte>) ? typeof(CopyByteSpanConverter) : null;
    }
}

// this is what we want to proxy:
public interface IFoo
{
    void Method(in Span<byte> bytes);
}

var generator = new ProxyGenerator();
var options = new ProxyGenerationOptions { ByRefLikeConverterSelector = new ConverterSelector() };
var proxy = generator.CreateInterfaceProxyWithoutTarget<IFoo>(options, ...);
var span = new byte[] { ... }.AsSpan();
proxy.Method(in span);  // this argument will end up in the invocation's `Arguments` as a `byte[]` array!

Usage example 2: new default / fallback behavior

If no converter is set up, by-ref-like argument values no longer cause an exception during invocation, but now get converted to null references in the IInvocation.Arguments array.

// this is what we want to proxy:
public interface IFoo
{
    void Method(in Span<byte> bytes);
}

var generator = new ProxyGenerator();
var span = new byte[] { ... }.AsSpan();
proxy.Method(in span);  // this argument will end up in the interceptor's `Arguments` as a `null` reference