Search code examples
c#structruntimeinvokeref

How to invoke internal method with ref struct parameter and unknown return type


I need to invoke Read(ref Utf8JsonReader, Type, JsonSerializerOptions) method from System.Text.Json.Serialization.JsonConverter<T> which T is not known.

Attempt 1

object converter = //Somehow get it;

Utf8JsonReader reader = //Somehow get it;
Type type = //Somehow get it;
JsonSerializerOptions options = //Somehow get them;

var result = converter.GetType()
                      .GetMethod("Read", BindingFlags.Public | BindingFlags.Instance)
                      .Invoke(converter, new object[] { reader, type, options });
//                                                      ͠ ͠ ͠ ^͠ ͠ ͠ 
//                                      CS0029: Cannot implicitly convert type
//                                  'System.Text.Json.Utf8JsonReader' to 'object'

Due to ref structs can't be boxed we can't do this.

Attempt 2

delegate object ReadDelegate(ref Utf8JsonReader reader, Type type, JsonSerializerOptions options);

object converter = //Somehow get it;

Utf8JsonReader reader = //Somehow get it;
Type type = //Somehow get it;
JsonSerializerOptions options = //Somehow get them;

var result = converter.GetType()
                      .GetMethod("Read", BindingFlags.Public | BindingFlags.Instance)
                      .CreateDelegate<ReadDelegate>(converter)(ref reader, type, options);

It works if Read(ref Utf8JsonReader, Type, JsonSerializerOptions) returns reference types (I have tested int[], string), but value types (I have tested int, struct) produce System.ArgumentException: 'Cannot bind to the target method because its signature is not compatible with that of the delegate type.'


Solution

  • delegate object ReadDelegate(ref Utf8JsonReader reader, Type type, JsonSerializerOptions options);
    
    object converter = //Somehow get it;
    
    Utf8JsonReader reader = //Somehow get it;
    Type type = //Somehow get it;
    JsonSerializerOptions options = //Somehow get them;
    
    var instance = Expression.Constant(converter);
    var method = converter.GetType().GetMethod("Read", BindingFlags.Public | BindingFlags.Instance);
    var parameters = method.GetParameters().Select(p => Expression.Parameter(p.ParameterType, p.Name)).ToArray();
    
    var call = Expression.Call(instance, method, parameters);
    var cast = Expression.TypeAs(call, typeof(object));
    
    var @delegate = Expression.Lambda<ReadDelegate>(cast, parameters);
        
    var result = @delegate.Compile()(ref reader, type, options);