使用System.Type调用泛型方法

5
我正在使用C#/.NET 4.0和Protocol Buffers库(protobuf-net),该库提供以下功能。
public static class Serializer {
    public static void Serialize<T>(Stream destination, T instance);
    public static void Serialize<T>(SerializationInfo info, T instance);
    public static void Serialize<T>(XmlWriter writer, T instance);
    public static void Serialize<T>(SerializationInfo info, StreamingContext context, T instance);
    public static T Deserialize<T>(Stream source);
}

我需要用非泛型等价物包装这两个调用。 具体而言,我想要

void SerializeReflection(Stream destination, object instance);
object DeserializeReflection(Stream source, Type type);

只需在运行时调用Serializer的相应通用成员即可。 我已使用以下代码使DeserializeReflection方法运作:

public static object DeserializeReflection(Stream stream, Type type)
{
    return typeof(Serializer)
        .GetMethod("Deserialize")
        .MakeGenericMethod(type)
        .Invoke(null, new object[] { stream });
}

SerializeReflection方法是让我感到困扰的原因。起初,我尝试了以下代码:

public static void SerializeReflection(Stream stream, object instance)
{
    typeof(Serializer)
        .GetMethod("Serialize")
        .MakeGenericMethod(instance.GetType())
        .Invoke(null, new object[] { stream, instance });
}

问题在于typeof(Serializer).Invoke(...)之间的部分无法正常工作。调用GetMethod("Serialize")会出现AmbiguousMatchException,因为有四个方法名为“Serialize”。
然后我尝试使用带有System.Type数组参数的GetMethod重载来解决绑定问题:
GetMethod("Serialize", new[] { typeof(Stream), instance.GetType() })

但这样只会使得GetMethod的结果变成null

我应该如何使用反射来获取void Serializer.Serialize<T>(Stream, T)方法的MethodInfo,其中Tinstance.GetType()


1
考虑这个线程 https://dev59.com/W2865IYBdhLWcg3wA5uc - Ilya Ivanov
可能是使用反射选择正确的泛型方法的重复问题。 - nawfal
可能是如何使用反射调用泛型方法?的重复问题。 - usr
2个回答

4
尝试使用以下代码段,看看它是否符合您的需求。它创建了一个密闭类型的方法实例public static void Serialize<T>(Stream destination, T instance)。在这种情况下,它选择了第一个带有Stream参数的方法,但您可以更改这个谓词 method.GetParameters().Any(par => par.ParameterType == typeof(Stream)) 以满足您的需要。
public static object DeserializeReflection(Stream stream, object instance)
{
   return typeof(Serializer)
        .GetMethods()
        .First(method => method.Name == "Serialize" && method.GetParameters().Any(par => par.ParameterType == typeof(Stream)))
        .MakeGenericMethod(instance.GetType())
        .Invoke(null, new object[] { stream, instance });
}

虽然这对于只有一个使用“Stream”参数的“Serialize”重载的特定类型可以工作,但请注意,如果您打算将此方法推广到在类型上定位适当的通用方法定义,则应更加严格地检查参数。 - mlorbetske
@mlorbetske 当然可以。这就是为什么我写下了要指定符合他标准的谓词。无论如何,感谢您的评论。您的回答也很好。 - Ilya Ivanov

2

对于这种情况,我经常使用像这样的帮助方法

public static MethodInfo MakeGenericMethod<TSourceType>(Type genericArgument, string methodName, Type[] parameterTypes, params int[] indexesWhereParameterIsTheGenericArgument)
{
    //Get the type of the thing we're looking for the method on
    var sourceType = typeof (TSourceType);
    //Get all the methods that match the default binding flags
    var allMethods = sourceType.GetMethods();
    //Get all the methods with the same names
    var candidates = allMethods.Where(x => x.Name == methodName);

    //Find the appropriate method from the set of candidates
    foreach (var candidate in candidates)
    {
        //Look for methods with the same number of parameters and same types 
        //   of parameters (excepting for ones that have been marked as 
        //   replaceable by the generic parameter)
        var parameters = candidate.GetParameters();
        var successfulMatch = parameters.Length == parameterTypes.Length;

        if (successfulMatch)
        {
            for (var i = 0; i < parameters.Length; ++i)
            {
                successfulMatch &= parameterTypes[i] == parameters[i].ParameterType || indexesWhereParameterIsTheGenericArgument.Contains(i);
            }
        }

        //If all the parameters were validated, make the generic method and return it
        if (successfulMatch)
        {
            return candidate.MakeGenericMethod(genericArgument);
        }
    }

    //We couldn't find a suitable candidate, return null
    return null;
}

要使用它,你需要执行以下操作:
var serializeMethod = MakeGenericMethod<Serializer>(instance.GetType(), "Serialize", new[]{typeof(stream), typeof(object)}, 1);

这对我正在做的事情也适用,但是Ivanov先生的答案非常直接。 - Timothy Shields

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接