使用反射调用方法时处理空参数

发布于 2024-11-06 18:25:42 字数 1651 浏览 1 评论 0原文

我正在尝试编写从参数列表推断类型的代码,然后调用与这些参数匹配的方法。这非常有效,除非参数列表中有 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 调用失败并显示types 数组中的 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*/ 也能正常工作。

I'm trying to write code that will infer types from a parameter list and then call the method that matches those parameters. This works very well, except when the parameter list has a null value in it.

I am wondering how I might cause the Type.GetMethod call to match a function/overload, even with a null parameter in the parameters list.

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

The main problem line is the types.Add((param == null) ? null : param.GetType());, which will cause the GetMethod call to fail with a null value in the types array.

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

Mainly, I'm trying to determine how to change my code so that line /*2*/ works as well.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(8

帅哥哥的热头脑 2024-11-13 18:25:42

GetMethod 调用有一些重写,它采用从 Binder 类派生的对象。这允许您覆盖默认方法绑定并根据传递的实际参数返回您想要使用的方法。这基本上也是其他两个答案正在做的事情。这里有一些示例代码:

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

There are overrides to the GetMethod call which take an object derived from the Binder class. This allows you to override the default method binding and return the method you want to use, based on the actual parameters passed. This is essentially what the two other answers are doing as well. There is some sample code here:

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

不知在何时 2024-11-13 18:25:42

没有提到的一个选项是使用 Fasterflect,这是一个旨在使反射任务更轻松、更快的库(通过 IL一代)。

要调用给定命名参数字典(或具有应用作参数的属性的对象)的方法,您可以像这样调用最佳匹配:

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

如果您拥有的只是参数值及其顺序,则可以使用另一个重载:

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

PS: 您需要从源代码管理获取最新位才能利用 TryCallMethodWithValues 扩展。

免责声明:我是 Fasterflect 项目的贡献者。

An option that has not been mentioned is to use Fasterflect, a library designed to make reflection tasks easier and faster (through IL generation).

To invoke a method given a dictionary of named parameters (or an object with properties that should be used as parameters), you can invoke the best match like this:

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

If all you have are the parameter values and their ordering, you can use another overload:

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

PS: You'll need to obtain the latest bits from source control to take advantage of the TryCallMethodWithValues extension.

Disclaimer: I am a contributor to the Fasterflect project.

我三岁 2024-11-13 18:25:42

对于任何为 null 的参数,您可以只匹配任何引用类型。以下非常简单/简单的代码将适用于您的方法,如图所示,但它不能处理歧义异常或使用 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;
    }
}

For any parameter that is null you could just match to any reference type. The following very simple/naive code will work for your methods as shown, but it doesn't handle things like exceptions on ambiguities or more complex cases using ref/out parameters or being able to pass a derived type to the method or generic methods.

If you are using 4.0 then simply using dynamic might be a better choice.

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;
    }
}
电影里的梦 2024-11-13 18:25:42

我认为你必须这样做:

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

然后基本上做你自己的重载决议。您可以先尝试现有的方法,捕获异常,然后尝试上面的方法。

I think you would have to do:

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

Then essentially do your own overload resolution. You could try your existing method first, catch the exception and then try the above.

肩上的翅膀 2024-11-13 18:25:42

感谢MSDN 链接以及一些<一个href="https://stackoverflow.com/questions/292437/define-if-a-reflected-type-can-be-cast-to-another-reflected-type/1809539#1809539">额外的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);
}

Thanks to the MSDN link as well as some additional SO discussion and an outside forum discussion involving a prominent SO member, I have tried to implement my own solution, which is working for me so far.

I created a class which inherited the Binder class and put my logic to handle the potentially null arguments/types in there.

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

Since the Binder class is an abstract class, you have to override a few other members to actually use this code, but most of my overrides just front the Type.DefaultBinder object.

public override FieldInfo BindToField(BindingFlags bindingAttr, FieldInfo[] matches, object value, CultureInfo culture)
{
    return Type.DefaultBinder.BindToField(bindingAttr, matches, value, culture);
}
叹沉浮 2024-11-13 18:25:42

我没有测试它,我认为其他答案要好得多,但我想知道为什么这不起作用:

foreach (var param in pParams.Where(p => p != null)
{
    types.Add(param.GetType());
}

I didn't test it and i think the other answers are much better, but i'm wondering why this wouldn't work:

foreach (var param in pParams.Where(p => p != null)
{
    types.Add(param.GetType());
}
独享拥抱 2024-11-13 18:25:42

您可以通过实现自己的 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
}

You could approach the problem by implementing your own GetMethod that iterates through all the method in the object and determine which one is the best match, I hope this helps.
I tested the following method with the example you provided and it worked

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
}
葮薆情 2024-11-13 18:25:42
  1. 如果没有一个参数为 NULL,则执行通常的方法调用,如果有一个为 null,但是
  2. 如果至少有一个为 null,则采取不同的方法:
  3. 从参数构建参数类型列表:如“int、char、null、int”
  4. get 函数重载为函数名称的参数数量相同
  5. ,看看是否只有一个匹配的函数,因为如果有 2 个,您无法确定要调用哪一个(最难的部分,但我认为相当简单)
  6. 调用您使用参数和空值计算出的函数
  1. If none of parameters is NULL you perform usual method call, if one is null however
  2. else if at least one is null you take different approach:
  3. build parameter type list from parameters : like "int, char, null, int"
  4. get functions overloads with same number of parameters for your function name
  5. see whether there is just one matching function, cause if there are 2 you cannot determine which to call (hardest part but fairly straightforward I think)
  6. call the function you figured out with your parameters and nulls
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文