Использование System.Type для вызова универсального метода

Я использую 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".

Затем я попытался использовать перегрузку GetMethod, которая принимает массив System.Type для разрешения привязки:

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

Но это только что сделало результат GetMethod null.

Как я могу использовать отражение, чтобы получить MethodInfo вместо void Serializer.Serialize<T>(Stream, T), где T равно instance.GetType()?


person Timothy Shields    schedule 08.01.2013    source источник
comment
Рассмотрим этот поток stackoverflow.com/questions/4035719/   -  person Ilya Ivanov    schedule 08.01.2013
comment
возможный дубликат Выбрать правильный универсальный метод с отражением   -  person nawfal    schedule 18.01.2014
comment
возможный дубликат Как использовать отражение для вызова универсального метода?   -  person usr    schedule 30.05.2014


Ответы (2)


Попробуйте использовать следующий фрагмент кода, чтобы увидеть, соответствует ли он вашим потребностям. Он создает близко типизированный экземпляр метода 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 });
}
person Ilya Ivanov    schedule 08.01.2013
comment
Хотя это работает для этого конкретного типа, где есть только одна перегрузка Serialize, которая принимает Stream, имейте в виду, что следует выполнять более тщательную проверку параметров, если вы собираетесь обобщить этот метод поиска соответствующих определений универсального метода для типа. - person mlorbetske; 08.01.2013
comment
@mlorbetske да, конечно. Вот почему я написал, чтобы указать предикат, который соответствует его cretiria. В любом случае - спасибо за комментарий. Ваш ответ тоже весьма хорош. - person Ilya Ivanov; 08.01.2013

Для такого рода вещей я часто использую вспомогательные методы, подобные этому

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);
person mlorbetske    schedule 08.01.2013
comment
Это также сработало бы для того, чем я занимаюсь, но г-н Иванов ответил очень прямо. - person Timothy Shields; 08.01.2013