2

How do I call a method that accepts a parameter by reference (using the ref keyword) using reflection? JsonConverter<T> defines the following method:

public abstract T Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options);

And I hold a derived type but have the generic parameter T as a Type object only. This doesn't compile:

converter.GetType().GetMethod("Read").Invoke(ref reader, type, options);

Clarification

Utf8JsonReader is a struct. My question wasn't about getting the method to be called, but rather how to do it without passing the parameter by value (and causing the struct to be copied).

Community
  • 1
  • 1
Gur Galler
  • 820
  • 9
  • 21
  • @JeroenvanLangen No, because the answer provided there shows how to pass the reader **by value**, causing the struct to be copeid to the array. – Gur Galler Mar 24 '20 at 11:35
  • 2
    And in particular, that wouldn't even compile as you can't box ref structs. It would really help if you could update the question to emphasize that this is a ref struct - emphasize why this question is *different* from other questions that have the same title. – Jon Skeet Mar 24 '20 at 11:38

2 Answers2

4

If you had T, everything would be very simple:

// declare a delegate
private delegate T ReadDelegate(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options);

// create and invoke a delegate
var readDelegate = Delegate.CreateDelegate(typeof(ReadDelegate), converter, "Read") as ReadDelegate;
var result = readDelegate.Invoke(ref reader, type, options);

Source: second search result from Google

But since you haven't, things go much more fun. Here is my solution (not as clean as I would like, but it works). First you need a supplementary classes:

internal abstract class ReadHelper
{
    public abstract object Read(ref Utf8JsonReader reader, Type type, JsonSerializerOptions options);
}

internal class ReadHelper<T> : ReadHelper
{
    private readonly ReadDelegate _readDelegate;

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

    public Reader(object converter)
    {
        _readDelegate = Delegate.CreateDelegate(typeof(ReadDelegate), converter, "Read") as ReadDelegate;
    }

    public override object Read(ref Utf8JsonReader reader, Type type, JsonSerializerOptions options)
        => _readDelegate.Invoke(ref reader, type, options);
}

And now you can use them as this:

// I assume you know how to get this:
// var converter = ...
// var typeOfT = ...
// var reader = ...
// var type = ...
// var options = ...

var readHelperType = typeof(ReadHelper<>).MakeGenericType(typeOfT);
var readHelper = Activator.CreateInstance(readerType, converter) as Reader;

// and finally:
var result = readHelper.Read(ref reader, type, options);

And just in case, you don't know how to get typeOfT:

private Type FindTypeOfT(object converter)
{
    var type = converter.GetType();
    while (type != null)
        if (type.IsGenericType && type.GetGenericTypeDefinition() == typeof(JsonConverter<>))
            return type.GetGenericArguments()[0];
        else
            type = type.BaseType;

    return null;
}
vasily.sib
  • 3,871
  • 2
  • 23
  • 26
0

If all else fails, you can do this with compiled expressions:

class Program
{
    private delegate object ReadDelegate(JsonConverter converter, ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options);

    public static void Main()
    {
        var converter = new FooConverter();
        var converterType = converter.GetType();
        var typeOfT = typeof(int);

        var converterParameter = Expression.Parameter(typeof(JsonConverter));
        var readerParameter = Expression.Parameter(typeof(Utf8JsonReader).MakeByRefType());
        var typeToConvertParameter = Expression.Parameter(typeof(Type));
        var optionsParameter = Expression.Parameter(typeof(JsonSerializerOptions));
        var readMethodInfo = converterType.GetMethod("Read");
        var castConverter = Expression.Convert(converterParameter, converterType);
        var call = Expression.Call(
            castConverter,
            readMethodInfo,
            readerParameter,
            typeToConvertParameter,
            optionsParameter);
        var castResult = Expression.Convert(call, typeof(object));
        var lambda = Expression.Lambda<ReadDelegate>(
            castResult,
            converterParameter,
            readerParameter,
            typeToConvertParameter,
            optionsParameter).Compile();

        var reader = new Utf8JsonReader();
        var result = lambda(converter, ref reader, typeof(int), new JsonSerializerOptions());
    }
}

public class FooConverter : JsonConverter<int>
{
    public override int Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) => 3;

    public override void Write(Utf8JsonWriter writer, int value, JsonSerializerOptions options) => throw new NotImplementedException();
}

Make sure you cache lambda -- it's relatively expensive to create, but very cheap to invoke.

This essentially builds a method at runtime, which looks a bit like this:

public object Lambda(
    JsonConverter converterParameter,
    ref Utf8JsonReader readerParameter,
    Type typeToConvertParameter,
    JsonSerializerOptions optionsParameter)
{
    var castConverter = (FooConverter)converterParameter;
    var call = castConverter.Read(ref readerParameter, typeToConvertParameter, optionsParameter);
    return (object)call;
}

That said, you would probably be better off writing a generic method, and then invoking it with reflection, rather than using reflection to invoke the Read method directly:

class Program
{
    public static void Main()
    {
        var converter = new FooConverter();
        var typeOfT = typeof(int);

        var methodInfo = typeof(Program).GetMethod("Foo").MakeGenericMethod(typeOfT);
        var result = methodInfo.Invoke(null, new[] { converter });
    }

    public static T Foo<T>(JsonConverter<T> converter)
    {
        var reader = new Utf8JsonReader();
        return converter.Read(ref reader, typeof(int), new JsonSerializerOptions());
    }
}

Admittedly this does just move the problem around, but it might work for you.

canton7
  • 37,633
  • 3
  • 64
  • 77
  • The first solution seems to work, but in the second one, I'll have to pass the reader by reference to the `Foo` method, and then we have the exact same problem as before. – Gur Galler Mar 24 '20 at 22:18
  • @GurGaller Right, with the second one the idea is to move the generics up until they reach the level where you create the reader (if you can). Obviously you can't pass in the reader using reflection, but you move the reflection boundary up until you don't need to pass the reader using reflection any more. Of course this might not be possible depending on your wider circumstances, which is why I posted the first solution first – canton7 Mar 24 '20 at 22:21