5

I have a MethodInfo object that represents a method, and that method takes a ref struct as an argument, and that struct has a ReadOnlySpan<byte> field. How do I invoke the method via the MethodInfo object? I can't use MethodInfo.Invoke because Invoke requires the parameters as an object?[]? array and a struct with a ReadOnlySpan<byte> field can't be cast to an object.

So how can I invoke a method via reflection and pass it a ref struct value where that struct has a field of type ReadOnlySpan?

cdjc
  • 1,039
  • 12
  • 24
  • 2
    Structs _can_ be casted to `object`. It's just that there will be boxing. Do you not like boxing? – Sweeper Jul 28 '20 at 03:27
  • Sorry. Edited to include the ReadOnlySpan field – cdjc Jul 28 '20 at 03:36
  • I am guessing it is an ReadOnly Ref Struct which can only live on the stack. – TheGeneral Jul 28 '20 at 03:58
  • @TheGeneral Doh! Yes, it is a ref struct! How did I not notice that... I'll edit the question. Easy answer now for somebody. – cdjc Jul 28 '20 at 04:03
  • Possibly this is helpful: https://stackoverflow.com/questions/55878525/unit-testing-ref-structs-with-private-fields-via-reflection – osexpert May 10 '23 at 20:20

2 Answers2

5

This can be achieved very simply with MethodInfo.CreateDelegate().

Here is an example which uses one of the generic overloads, available as of .NET 5.0:

public ref struct MyRefStruct
{
    private ReadOnlySpan<byte> myReadOnlySpan;

    public MyRefStruct(ReadOnlySpan<byte> myReadOnlySpan) =>
        this.myReadOnlySpan = myReadOnlySpan;

    public void Print()
    {
        foreach (byte b in myReadOnlySpan)
        {
            Console.WriteLine(b);
        }
    }
}

public class MyClass
{
    private void PrintMyRefStruct(MyRefStruct myRefStruct) => myRefStruct.Print();
}

delegate void MyDelegate(MyRefStruct myRefStruct);

var myClass = new MyClass();
var myRefStruct = new MyRefStruct(new ReadOnlySpan<byte>(new byte[] { 0, 1 }));

var methodInfo = typeof(MyClass).GetMethod(
    "PrintMyRefStruct",
    BindingFlags.Instance | BindingFlags.NonPublic
);

methodInfo.CreateDelegate<MyDelegate>(myClass)(myRefStruct);
James T
  • 795
  • 9
  • 11
3

Since your struct would naturally be a ref struct which will live on the stack (and can't escape to the managed heap), you face several compiler limitations and have a few hurdles to overcome.

  • A ref struct can't be the element type of an array.
  • A ref struct can't be a declared type of a field of a class or a non-ref struct.
  • A ref struct can't implement interfaces.
  • A ref struct can't be boxed to System.ValueType or System.Object.
  • A ref struct can't be a type argument. vA ref struct variable can't be captured by a lambda expression or a local function.
  • A ref struct variable can't be used in an async method. However, you can use ref struct variables in synchronous methods, for example, in those that return Task or Task.
  • A ref struct variable can't be used in iterators.

As you can see, ref struct can't be boxed on the heap. This means you will not be able to cast to an object and use Invoke. However you can use an expression tree or relection.emit

When using an expression tree you will need to use a delegate with Expression.Lambda as ref struct can't be used as type parameters in a Func.

Given

public readonly ref struct Bob
{
   public Bob(ReadOnlySpan<byte> myProperty)
      => MyProperty = myProperty;    

   public ReadOnlySpan<byte> MyProperty { get; }
}

...

public static byte DoSomething(Bob bob)
   => bob.MyProperty[1]; // return something from the span ¯\_(ツ)_/¯

delegate byte myDelegate(Bob asd);

...

var bytes = new byte[] {1, 2, 3};
var span = bytes.AsSpan();
var bob = new Bob(span);

Usage

var method = typeof(SomeType).GetMethod("DoSomething");
var parameter = Expression.Parameter(typeof(Bob), "b");
var call = Expression.Call(method, parameter);
var expression = Expression.Lambda<myDelegate>(call, parameter);
var func = expression.Compile();
var result = func(bob);

Console.WriteLine(result);

Results

2

Or with the in parameter modifier you'll need to make use of MakeByRefType

Returns a Type object that represents the current type when passed as a ref parameter

public static byte DoSomething(in Bob bob)
  => bob.MyProperty[1]; // return something from the span ¯\_(ツ)_/¯

delegate byte myDelegate(in Bob asd);

...

var method = typeof(SomeType).GetMethod("DoSomething", new[] {typeof(Bob).MakeByRefType()});
var parameter = Expression.Parameter(typeof(Bob).MakeByRefType(), "b");
var call = Expression.Call(method, parameter);
var expression = Expression.Lambda<myDelegate>(call, parameter);
var func = expression.Compile();
var result = func(bob);

Note : This code is not the bastion perfect expression'ing, however it should work. Also this is built for a static method, you will need to pass in an instance if it's not

halfer
  • 19,824
  • 17
  • 99
  • 186
TheGeneral
  • 79,002
  • 9
  • 103
  • 141
  • This is a very good answer. Thank you. For non-static methods, something like `var instance = Expression.Constant(...)` and changing the call to `Expression.Call(instance, method, parameter)` works. – cdjc Jul 28 '20 at 20:25