Как передать аргумент по ссылке, используя Reflection - PullRequest
2 голосов
/ 24 марта 2020

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

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

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

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

Пояснение

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

Ответы [ 2 ]

2 голосов
/ 24 марта 2020

Если бы у вас было 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

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

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;
}
0 голосов
/ 24 марта 2020

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

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

Тем не менее, вам, вероятно, лучше написать обобщенный метод c, а затем вызвать его с отражением, а не с помощью отражения для непосредственного вызова метода 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());
    }
}

По общему признанию, это просто перемещает проблему, но это может работать для вас.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...