关于c#:如何使用反射来调用私有方法?

How do I use reflection to invoke a private method?

我的类中有一组私有方法,我需要根据输入值动态调用一个方法。调用代码和目标方法都在同一个实例中。代码如下:

1
2
MethodInfo dynMethod = this.GetType().GetMethod("Draw_" + itemType);
dynMethod.Invoke(this, new object[] { methodParams });

在这种情况下,GetMethod()不会返回私有方法。我需要向GetMethod()提供什么,以便它可以定位私有方法?


只需更改代码以使用接受绑定标志的重载版本GetMethod

1
2
3
MethodInfo dynMethod = this.GetType().GetMethod("Draw_" + itemType,
    BindingFlags.NonPublic | BindingFlags.Instance);
dynMethod.Invoke(this, new object[] { methodParams });

这是bindingFlags枚举文档。


BindingFlags.NonPublic本身不会返回任何结果。事实证明,把它和BindingFlags.Instance结合起来就可以做到这一点。

1
2
MethodInfo dynMethod = this.GetType().GetMethod("Draw_" + itemType,
    BindingFlags.NonPublic | BindingFlags.Instance);


如果您真的想让自己陷入困境,那么通过编写一个扩展方法来简化执行过程:

1
2
3
4
5
6
7
8
9
10
11
static class AccessExtensions
{
    public static object call(this object o, string methodName, params object[] args)
    {
        var mi = o.GetType ().GetMethod (methodName, System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance );
        if (mi != null) {
            return mi.Invoke (o, args);
        }
        return null;
    }
}

用途:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
    class Counter
    {
        public int count { get; private set; }
        void incr(int value) { count += value; }
    }

    [Test]
    public void making_questionable_life_choices()
    {
        Counter c = new Counter ();
        c.call ("incr", 2);             //"incr" is private !
        c.call ("incr", 3);
        Assert.AreEqual (5, c.count);
    }


微软最近修改了反射API,使得这些答案大部分都过时了。以下内容应适用于现代平台(包括xamarin.forms和uwp):

1
obj.GetType().GetTypeInfo().GetDeclaredMethod("MethodName").Invoke(obj, yourArgsHere);

或作为扩展方法:

1
2
3
4
5
6
public static object InvokeMethod<T>(this T obj, string methodName, params object[] args)
{
    var type = typeof(T);
    var method = type.GetTypeInfo().GetDeclaredMethod(methodName);
    return method.Invoke(obj, args);
}

注:

  • 如果所需方法位于obj的超类中,则必须将T泛型显式设置为超类的类型。

  • 如果方法是异步的,可以使用await (Task) obj.InvokeMethod(…)


你确定这不能通过继承来实现吗?反射是解决问题时最不应该考虑的事情,它使重构、理解代码和任何自动化分析变得更加困难。

看起来您应该只有一个drawitem1、drawitem2等类来重写dynmethod。


特别是对私人成员的反思是错误的

  • 反射破坏类型安全。您可以尝试调用一个不再存在的方法(不再存在),或者使用错误的参数,或者使用过多的参数,或者使用不足的参数…或者是顺序不对(这是我最喜欢的一个)。顺便说一下,返回类型也可以改变。
  • 反射很慢。

私有成员反射破坏了封装原则,从而使代码暴露于以下内容:

  • 增加代码的复杂性,因为它必须处理类的内部行为。隐藏的应该保持隐藏。
  • 使您的代码易于中断,因为它将编译,但不会运行,如果方法更改了名称。
  • 使私有代码容易被破坏,因为如果它是私有的,就不打算以这种方式调用它。可能私有方法在被调用之前需要一些内部状态。

如果我无论如何都必须这么做呢?

有这样的情况,当你依赖第三方或者你需要一些不公开的API时,你必须做一些反思。有些人还使用它来测试他们拥有的一些类,但他们不想更改接口,只为测试而授予对内部成员的访问权。

如果你做了,就做对了

  • 缓解易破性:

为了缓解容易中断的问题,最好是在单元测试中检测任何可能的中断,这些测试将在持续集成构建中运行。当然,这意味着您总是使用相同的程序集(其中包含私有成员)。如果您使用动态加载和反射,您喜欢玩火,但是您总是可以捕获调用可能产生的异常。

  • 缓解反射的缓慢性:

在.NET Framework的最新版本中,CreateDelegate以系数50击败了MethodInfo调用:

1
2
3
4
5
6
7
8
// The following should be done once since this does some reflection
var method = this.GetType().GetMethod("Draw_" + itemType,
  BindingFlags.NonPublic | BindingFlags.Instance);

// Here we create a Func that targets the instance of type which has the
// Draw_ItemType method
var draw = (Func<TInput, Output[]>)_method.CreateDelegate(
                 typeof(Func<TInput, TOutput[]>), this);

draw呼叫比MethodInfo.Invoke快50倍左右。使用draw作为标准Func如下:

1
var res = draw(methodParams);

查看我的这个职位,查看不同方法调用的基准


对于要绘制的每种类型,您不能只使用不同的绘制方法吗?然后调用重载的draw方法,传入要绘制的itemtype类型的对象。

您的问题并不清楚itemtype是否真正引用不同类型的对象。


调用任何方法,尽管它在对象实例上具有保护级别。享受!

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public static object InvokeMethod(object obj, string methodName, params object[] methodParams)
{
    var methodParamTypes = methodParams?.Select(p => p.GetType()).ToArray() ?? new Type[] { };
    var bindingFlags = BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance | BindingFlags.Static;
    MethodInfo method = null;
    var type = obj.GetType();
    while (method == null && type != null)
    {
        method = type.GetMethod(methodName, bindingFlags, Type.DefaultBinder, methodParamTypes, null);
        type = type.BaseType;
    }

    return method?.Invoke(obj, methodParams);
}

我想你可以把它传递给BindingFlags.NonPublic,这里是GetMethod方法。


阅读这个(补充的)答案(有时是答案),了解这是怎么回事,以及为什么这个话题中的一些人抱怨"它仍然不起作用"。

我写的代码和这里的答案完全一样。但我还是有问题。我把断点放在

1
var mi = o.GetType().GetMethod(methodName, BindingFlags.NonPublic | BindingFlags.Instance );

它执行了,但mi == null

它继续这样的行为,直到我对所有涉及的项目进行了"重新构建"。我正在单元测试一个组件,而反射方法则在第三个组件中。这完全令人困惑,但我使用即时窗口发现了方法,我发现我尝试单元测试的私有方法有旧名称(我将其重命名)。这告诉我,即使单元测试项目构建了,旧的汇编或PDB仍然存在——出于某种原因,项目IT测试没有构建。"重建"工作"


BindingFlags.NonPublic