Обрабатывать нулевые параметры при вызове метода с помощью Reflection - PullRequest
8 голосов
/ 11 мая 2011

Я пытаюсь написать код, который будет выводить типы из списка параметров, а затем вызывать метод, соответствующий этим параметрам. Это работает очень хорошо, за исключением случаев, когда в списке параметров есть значение null.

Мне интересно, как я могу заставить вызов Type.GetMethod соответствовать функции / перегрузке, даже с параметром null в списке параметров.

object CallMethodReflection(object o, string nameMethod, params object[] args)
{
    try
    {
        var types = TypesFromObjects(args);
        var theMethod = o.GetType().GetMethod(nameMethod, types);
        return (theMethod == null) ? null : theMethod.Invoke(o, args);
    }
    catch (Exception ex)
    {
        return null;
    }
}
Type[] TypesFromObjects(params object[] pParams)
{
    var types = new List<Type>();
    foreach (var param in pParams)
    {
        types.Add((param == null) ? null : param.GetType());
    }
    return types.ToArray();
}

Основной строкой проблемы является types.Add((param == null) ? null : param.GetType());, что приведет к сбою вызова GetMethod со значением null в массиве типов.

void Function1(string arg1){ }
void Function1(string arg1, string arg2){ }
void Function1(string arg1, string arg2, string arg3){ }
void Function2(string arg1){ }
void Function2(string arg1, int arg2){ }
void Function2(string arg1, string arg2){ }

/*1*/ CallMethodReflection(obj, "Function1", "String", "String"); // This works
/*2*/ CallMethodReflection(obj, "Function1", "String", null); // This doesn't work, but still only matches one overload
/*3*/ CallMethodReflection(obj, "Function2", "String", "String"); // This works
/*4*/ CallMethodReflection(obj, "Function2", "String", null); // This doesn't work, and I can see why this would cause problems

В основном я пытаюсь определить, как изменить свой код, чтобы строка /*2*/ также работала.

Ответы [ 8 ]

6 голосов
/ 12 мая 2011

Существуют переопределения для вызова GetMethod, которые принимают объект, полученный из класса Binder.Это позволяет вам переопределить привязку метода по умолчанию и вернуть метод, который вы хотите использовать, основываясь на фактических переданных параметрах.По сути, это то, что делают два других ответа.Вот пример кода:

http://msdn.microsoft.com/en-us/library/system.reflection.binder.aspx

5 голосов
/ 20 мая 2011

Опция, которая не была упомянута, заключается в использовании Fasterflect , библиотеки, предназначенной для упрощения и ускорения задач отражения (посредством генерации IL).

Для вызова метода из данного словаряименованных параметров (или объекта со свойствами, которые следует использовать в качестве параметров), вы можете вызвать наилучшее совпадение следующим образом:

obj.TryCallMethod( "SomeMethod", argsDictionary );
obj.TryCallMethod( "AnotherMethod", new { Foo = "Bar" } );

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

obj.TryCallMethodWithValues( "MyMethod", 42, "foo", "bar", null, 2.0 );

PS: вам понадобится получить последние биты из системы контроля версий, чтобы воспользоваться преимуществами расширения TryCallMethodWithValues.

Отказ от ответственности: Я участвую в проекте Fasterflect.

3 голосов
/ 12 мая 2011

Для любого параметра, который является нулевым, вы можете просто сопоставить любой ссылочный тип. Следующий очень простой / наивный код будет работать для ваших методов, как показано, но он не обрабатывает такие вещи, как исключения неоднозначностей или более сложные случаи, используя параметры ref / out или возможность передачи производного типа в метод или универсальные методы.

Если вы используете 4.0, тогда лучше использовать динамический.

object CallMethodReflection(object o, string nameMethod, params object[] args)
{
    try
    {
        var types = TypesFromObjects(args);
        var oType = o.GetType();
        MethodInfo theMethod = null;

        // If any types are null have to perform custom resolution logic
        if (types.Any(type => type == null)) 
        {
            foreach (var method in oType.GetMethods().Where(method => method.Name == nameMethod))
            {
                var parameters = method.GetParameters();

                if (parameters.Length != types.Length)
                    continue;

                //check to see if all the parameters match close enough to use
                bool methodMatches = true;
                for (int paramIndex = 0; paramIndex < parameters.Length; paramIndex++)
                {
                    //if arg is null, then match on any non value type
                    if (args[paramIndex] == null)
                    {
                        if (parameters[paramIndex].ParameterType.IsValueType)
                        {
                            methodMatches = false;
                            break;
                        }
                    }
                    else //otherwise match on exact type, !!! this wont handle things passing a type derived from the parameter type !!!
                    {
                        if (parameters[paramIndex].ParameterType != args[paramIndex].GetType())
                        {
                            methodMatches = false;
                            break;
                        }
                    }
                }

                if (methodMatches)
                {
                    theMethod = method;
                    break;
                }
            }
        }
        else
        {
            theMethod = oType.GetMethod(nameMethod, types);
        }

        Console.WriteLine("Calling {0}", theMethod);
        return theMethod.Invoke(o, args);
    }
    catch (Exception ex)
    {
        Console.WriteLine("Could not call method: {0}, error: {1}", nameMethod, ex.ToString());
        return null;
    }
}
2 голосов
/ 11 мая 2011

Я думаю, вам нужно сделать:

var methods = o.GetType().GetMethods().Where(m => m.Name == methodName);

Тогда сделайте собственное разрешение перегрузки. Вы можете сначала попробовать существующий метод, перехватить исключение, а затем попробовать описанное выше.

1 голос
/ 13 мая 2011

Благодаря MSDN-ссылке , а также некоторому дополнительному обсуждению SO и стороннему обсуждению на форуме с участием видного члена SO , я попытался реализовать свой собственное решение, которое работает для меня до сих пор.

Я создал класс, который унаследовал класс Binder, и поместил свою логику для обработки потенциально null аргументов / типов.

object CallMethodReflection(object o, string nameMethod, params object[] args)
{
    try
    {
        var types = TypesFromObjects(args);
        var theMethod = o.GetType().GetMethod(nameMethod, CustomBinder.Flags, new CustomBinder(), types, null);
        return (theMethod == null) ? null : theMethod.Invoke(o, args);
    }
    catch (Exception ex)
    {
        return null;
    }
}
Type[] TypesFromObjects(params object[] pParams)
{
    var types = new List<Type>();
    foreach (var param in pParams)
    {
        types.Add((param == null) ? typeof(void) : param.GetType()); // GetMethod above doesn't like a simply null value for the type
    }
    return types.ToArray();
}
private class CustomBinder : Binder
{
    public const BindingFlags Flags = BindingFlags.Public | BindingFlags.Instance;
    public override MethodBase SelectMethod(BindingFlags bindingAttr, MethodBase[] matches, Type[] types, ParameterModifier[] modifiers)
    {
        if (matches == null)
            throw new ArgumentNullException("matches");
        foreach (var match in matches)
        {
            if (MethodMatches(match.GetParameters(), types, modifiers))
                return match;
        }
        return Type.DefaultBinder.SelectMethod(bindingAttr, matches, types, modifiers); // No matches. Fall back to default
    }
    private static bool MethodMatches(ParameterInfo[] parameters, Type[] types, ParameterModifier[] modifiers)
    {
        if (types.Length != parameters.Length)
            return false;
        for (int i = types.Length - 1; i >= 0; i--)
        {
            if ((types[i] == null) || (types[i] == typeof(void)))
            {
                if (parameters[i].ParameterType.IsValueType)
                    return false; // We don't want to chance it with a wonky value
            }
            else if (!parameters[i].ParameterType.IsAssignableFrom(types[i]))
            {
                return false; // If any parameter doesn't match, then the method doesn't match
            }
        }
        return true;
    }
}

Поскольку класс Binder является абстрактным, вам нужно переопределить несколько других членов, чтобы фактически использовать этот код, но большинство моих переопределений просто перед объектом Type.DefaultBinder.

public override FieldInfo BindToField(BindingFlags bindingAttr, FieldInfo[] matches, object value, CultureInfo culture)
{
    return Type.DefaultBinder.BindToField(bindingAttr, matches, value, culture);
}
0 голосов
/ 24 мая 2011
  1. Если ни один из параметров не равен NULL, вы выполняете обычный вызов метода, если один из них равен нулю, однако
  2. В противном случае, если хотя бы один из них равен нулю, вы применяете другой подход:
  3. тип параметра сборкисписок из параметров: например, "int, char, null, int"
  4. получить перегрузки функций с одинаковым количеством параметров для имени вашей функции
  5. посмотреть, есть ли только одна подходящая функция, вызвать, если естьесли 2, вы не можете определить, какой из них вызывать (самая сложная часть, но я думаю, что это довольно просто)
  6. вызвать функцию, которую вы выяснили, с вашими параметрами и нулями
0 голосов
/ 24 мая 2011

Вы можете решить эту проблему, внедрив свой собственный метод GetMethod, который выполняет итерацию по всем методам в объекте и определяет, какой из них лучше всего подходит, надеюсь, это поможет.Я протестировал следующий метод с примером, который вы предоставили, и он работал

MethodInfo SmarterGetMethod(object o, string nameMethod, params object[] args)
{
var methods = o.GetType().GetMethods();
var min = args.Length;
var values = new int[methods.Length];
values.Initialize();
//Iterates through all methods in o
for (var i = 0; i < methods.Length; i += 1)
{
    if (methods[i].Name == nameMethod)
    {
        var parameters = methods[i].GetParameters();
        if (parameters.Length == min)
        {
            //Iterates through parameters
            for (var j = 0; j < min; j += 1)
            {
                if (args[j] == null)
                {
                    if (parameters[j].ParameterType.IsValueType)
                    {
                        values[i] = 0;
                        break;
                    }
                    else
                    {
                        values[i] += 1;
                    }
                }
                else
                {
                    if (parameters[j].ParameterType != args[j].GetType())
                    {
                        values[i] = 0;
                        break;
                    }
                    else
                    {
                        values[i] += 2;
                    }
                }
            }
            if (values[i] == min * 2) //Exact match                    
                return methods[i];
        }
    }
}

var best = values.Max();
if (best < min) //There is no match
    return null;
//Iterates through value until it finds first best match
for (var i = 0; i < values.Length; i += 1)
{
    if (values[i] == best)
        return methods[i];
}
return null; //Should never happen
}
0 голосов
/ 24 мая 2011

Я не проверял это, и я думаю, что другие ответы намного лучше, но мне интересно, почему это не сработает:

foreach (var param in pParams.Where(p => p != null)
{
    types.Add(param.GetType());
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...