Как передать аргумент по ссылке, используя Reflection

Как вызвать метод, который принимает параметр по ссылке (используя ключевое слово ref ), используя отражение? JsonConverter<T> определяет следующий метод:

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

И я держу производный тип, но у общего параметра T есть только объект Type . Это не компилируется:

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

осветление

Utf8JsonReader является структурой. Мой вопрос был не о том, как заставить метод вызываться, а о том, как это сделать, не передавая параметр по значению (и не вызывая копирование структуры).

Всего 2 ответа


Если бы у вас был T , все было бы очень просто:

// 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);

Источник: второй результат поиска от Google

Но так как у вас нет, дела идут намного веселее. Вот мое решение (не так чисто, как хотелось бы, но оно работает). Сначала вам нужны дополнительные занятия:

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);
}

И теперь вы можете использовать их как это:

// 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);

И на всякий случай, вы не знаете, как получить 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;
}

Если ничего не помогает, вы можете сделать это с помощью скомпилированных выражений:

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();
}

Убедитесь, что вы кэшируете lambda - это относительно дорого создавать, но очень дешево вызывать.

По сути, это создает метод во время выполнения, который выглядит примерно так:

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;
}

Тем не менее, вам, вероятно, лучше написать общий метод, а затем вызывать его с отражением, а не использовать отражение для непосредственного вызова метода Read :

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());
    }
}

По общему признанию это только перемещает проблему, но это могло бы работать для Вас.