如何使用反射调用私有方法?

382

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

MethodInfo dynMethod = this.GetType().GetMethod("Draw_" + itemType);
dynMethod.Invoke(this, new object[] { methodParams });
在这种情况下,GetMethod()方法将无法返回私有方法。我需要提供哪些BindingFlagsGetMethod()方法,以便它可以定位私有方法?
在这种情况下,GetMethod()方法将无法返回私有方法。您需要向GetMethod()方法提供哪些BindingFlags参数,以便它能够定位私有方法?

BindingFlags.NonPublic - Khoth
12个回答

592

301
我要做的事情可能会给自己惹来很多麻烦。 - Frank Schwieterman
2
BindingFlags.NonPublic没有返回private方法.. :( - Moumit
6
@MoumitMondal 你的方法是静态的吗?对于非静态方法,你必须指定 BindingFlags.InstanceBindingFlags.NonPublic - BrianS
4
添加 BindingFlags.FlattenHierarchy 将使您能够在实例中获取来自父类的方法。 - Dragonthoughts
2
那个第一条评论的点赞数量让我感到不安。然而,我即将使用它。我必须查找它的原因是因为我通常尽量避免使用它。这通常是不好的,但在某些情况下它是有效的。这就是为什么它存在的原因,也是我在这里的原因。请不要滥用它。 - Jesse
显示剩余6条评论

75

BindingFlags.NonPublic本身不会返回任何结果。事实证明,与BindingFlags.Instance相结合可以达到效果。

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

相同的逻辑也适用于“internal”函数。 - supertopi
我有一个相似的问题。如果“this”是一个子类,你尝试调用父类的私有方法会怎样? - persianLife

56

如果你真的想自找麻烦,可以编写一个扩展方法来让它更容易执行:

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

并且使用:

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

15
危险吗?是的。但是当包含在我的单元测试命名空间中时,它是一个很好的帮助扩展。感谢这个。 - Robert Wahler
5
如果你在意从调用方法中抛出的真实异常,那么将其包装在try catch块中并在捕获TargetInvokationException时重新抛出内部异常是个好主意。我在我的单元测试辅助扩展中这样做。 - Slobodan Savkovic
3
反思是危险的吗?嗯... C#、Java、Python... 实际上所有的东西都有危险性,甚至包括这个世界 :D 你只需要注意如何安全地做就可以了... - Legends
@Legends 这是危险的,因为私有代码通常会经常变动。你无法保证这个方法在未来是否存在。而且你也不知道这个方法是如何运作和与其他方法协作的。当你调用一个明显不是为特定功能入口而设计的私有方法时,可能会导致意想不到的副作用。例如,这个方法可能只能在上下文被另一个内部调用的方法正确配置之后才能正常工作。 - user21970328
@传说中的人们称之为危险或愚蠢,去调用内部或私有代码。因为这样做可能是不应该的。至少如果你重视应用程序的稳健代码库的话。 - user21970328
@RobertWahler 单元测试私有方法... 对此已经说得够多了。测试公共API自然会测试为API服务的私有内部方法。 - user21970328

27

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

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

或者作为扩展方法:

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(…)


它至少不适用于UWP .net版本,因为它仅适用于公共方法:“_返回一个集合,其中包含当前类型上声明的所有公共方法,这些方法与指定的名称匹配_”。 - Dmytro Bondarenko
2
@DmytroBondarenko 我已经测试了私有方法并且它可以工作。我确实看到了这一点。不确定为什么它的行为与文档不同,但至少它可以工作。 - Owen James
1
是的,我不会称其他答案为过时的,如果文档说GetDeclareMethod()旨在用于检索公共方法。 - Mass Dot Net

14

反射对私有成员的使用是错误的

  • 反射会破坏类型安全。你可能试图调用一个不存在的方法,或者带有错误的参数、过多或不足的参数,甚至按错误的顺序(这是我最喜欢的一种情况 :))。另外,返回类型也可能发生变化。
  • 反射速度很慢。

私有成员的反射违反了封装原则,从而暴露了您的代码:

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

如果必须使用,要正确使用

在某些情况下,当您依赖第三方或需要一些未公开的 API 时,您必须使用反射。有些人也会将其用于测试自己拥有但不想更改接口以访问内部成员的类。

  • 缓解易于破坏问题:

为了缓解易于破坏的问题,最好通过单元测试检测任何潜在的破坏,在持续集成构建或类似的环境中运行。当然,这意味着您始终使用相同的程序集(其中包含私有成员)。如果您使用动态加载和反射,那么您就像是在玩火,但您始终可以捕获调用可能产生的异常。

  • 缓解反射的速度问题:

在最新版本的 .Net Framework 中,CreateDelegate 方法比 MethodInfo.Invoke 方法快50倍:

// 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 使用,如下:

var res = draw(methodParams);

查看我的这篇文章,了解不同方法调用的基准测试。


1
虽然我知道依赖注入应该是单元测试的首选方式,但我认为谨慎使用反射来访问本来无法进行测试的单元并不完全是一件坏事。就个人而言,我认为除了常规的 [public][protected][private] 修饰符之外,我们还应该有 [Test][Composition] 修饰符,这样在这些阶段可以使某些东西可见,而不必强制将所有内容都设为完全公开(因此必须完全记录这些方法)。 - andrew pate
1
感谢Fab列出了反思私有成员的问题,这促使我重新审视使用它的感受并得出结论...使用反射来单元测试您的私有成员是错误的,但是留下未经测试的代码路径真的非常错误。 - andrew pate
3
对于通常不适合进行单元测试的遗留代码,但是当涉及到单元测试时,这是一种绝佳的方式。 - T.S.

9

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

看起来你只需要创建DrawItem1、DrawItem2等类,覆盖dynMethod方法即可。


1
@Bill K:鉴于其他情况,我们决定不使用继承,因此采用了反射。对于大多数情况,我们会这样做。 - Jeromy Irvine

4
需要翻译的内容:

需要注意的是,从派生类中调用可能会有问题。

容易出错:

this.GetType().GetMethod("PrivateTestMethod", BindingFlags.Instance | BindingFlags.NonPublic)

正确:

typeof(CurrentClass).GetMethod("PrivateTestMethod", BindingFlags.Instance | BindingFlags.NonPublic)

3

在对象实例上调用任何方法,无论其保护级别如何。享受吧!

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

3

我认为你可以在GetMethod方法中传递BindingFlags.NonPublic参数。


2

您能否为您想绘制的每种类型编写不同的绘制方法?然后调用重载的Draw方法,将要绘制的itemType对象传递进去。

您的问题并没有明确说明itemType是否真正指的是不同类型的对象。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接