【问题标题】:How to pass an argument by reference using Reflection如何使用反射通过引用传递参数
【发布时间】:2020-03-25 13:05:34
【问题描述】:

如何使用反射调用通过引用(使用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 是一个结构体。 我的问题不是要调用方法,而是如何在不按值传递参数(并导致结构被复制)的情况下做到这一点。

【问题讨论】:

  • @JeroenvanLangen 不,因为那里提供的答案显示了如何按值传递阅读器,从而导致结构被复制到数组。
  • 特别是,它甚至无法编译,因为你不能装箱 ref 结构。如果您可以更新问题以强调这是一个 ref 结构,这将非常有帮助 - 强调为什么这个问题与具有相同标题的其他问题不同

标签: c# json reflection


【解决方案1】:

如果你有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);

来源:second search result from 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;
}

【讨论】:

  • 编译时不知道T怎么用?如果我愿意,我就不需要反思了:)
  • @GurGaller 如果你不知道 T - 你可以声明一个通用的 Delegate。查看我的更新。
  • 但是readDelegate的类型是Delegate,那么我该如何调用呢?
  • @GurGaller 这很难。还有一种方法,请看我的修改
【解决方案2】:

如果一切都失败了,你可以用编译的表达式来做到这一点:

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

诚然,这确实只是解决了问题,但它可能对你有用。

【讨论】:

  • 第一个解决方案似乎可行,但在第二个解决方案中,我必须通过参考Foo 方法通过读者,然后我们遇到与之前完全相同的问题。
  • @GurGaller 对,第二个想法是将泛型向上移动,直到它们达到您创建阅读器的级别(如果可以的话)。显然你不能使用反射传递阅读器,但是你将反射边界向上移动,直到你不再需要使用反射传递阅读器。当然,根据您的更广泛的情况,这可能是不可能的,这就是我首先发布第一个解决方案的原因
猜你喜欢
  • 2017-05-23
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-02-24
  • 1970-01-01
  • 2012-04-21
相关资源
最近更新 更多