5

I am using C#/.NET 4.0 and a Protocol Buffers library (protobuf-net) which provides the following functionality.

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

I need to wrap two of these calls with non-generic equivalents. Specifically, I want

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

that simply call the respective generic members of Serializer at runtime. I have gotten the DeserializeReflection method to work with the following code:

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

The SerializeReflection method is what is causing me trouble. I at first tried the following code:

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

The problem is that the part between typeof(Serializer) and .Invoke(...) is not working. The call to GetMethod("Serialize") gets me an AmbiguousMatchException, because there are four methods named "Serialize."

I then tried using the overload of GetMethod that takes an array of System.Type to resolve the binding:

GetMethod("Serialize", new[] { typeof(Stream), instance.GetType() })

But this just made the result of GetMethod null.

How can I use reflection to get the MethodInfo for void Serializer.Serialize<T>(Stream, T), where T is instance.GetType()?

leppie
  • 115,091
  • 17
  • 196
  • 297
Timothy Shields
  • 75,459
  • 18
  • 120
  • 173
  • 1
    Consider this thread http://stackoverflow.com/questions/4035719/getmethod-for-generic-method – Ilya Ivanov Jan 08 '13 at 19:23
  • possible duplicate of [Select Right Generic Method with Reflection](http://stackoverflow.com/questions/3631547/select-right-generic-method-with-reflection) – nawfal Jan 18 '14 at 05:55
  • possible duplicate of [How to use reflection to call generic Method?](http://stackoverflow.com/questions/232535/how-to-use-reflection-to-call-generic-method) – usr May 30 '14 at 17:05

2 Answers2

4

Try to use next code snippet to see if it meets your need. It creates a close typed instance of method public static void Serialize<T>(Stream destination, T instance). In this case it select the first method with Stream as parameter, but you can change this predicate method.GetParameters().Any(par => par.ParameterType == typeof(Stream)) to whatever you want

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 });
}
Ilya Ivanov
  • 23,148
  • 4
  • 64
  • 90
  • While this works for this specific type where there's exactly one overload of `Serialize` that takes a `Stream`, keep in mind that more rigorous checking of the parameters should be done if you intend to generalize this method of locating the appropriate generic method definitions on a type. – mlorbetske Jan 08 '13 at 19:36
  • @mlorbetske yes, sure. That's why I wrote to specify predicate, that meets his cretiria. Anyway - thank for comment. Your answer is also quite good. – Ilya Ivanov Jan 08 '13 at 19:37
2

For this sort of thing I often user helper methods like this

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

To use it, you'd do

var serializeMethod = MakeGenericMethod<Serializer>(instance.GetType(), "Serialize", new[]{typeof(stream), typeof(object)}, 1);
mlorbetske
  • 5,529
  • 2
  • 28
  • 40