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


326

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

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

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

Answers:


498

只需更改您的代码以使用接受BindingFlags 的重载版本GetMethod

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

这是BindingFlags枚举文档


248
我要为此陷入很多麻烦。
Frank Schwieterman,2009年

1
BindingFlags.NonPublic不返回private方法.. :(
Moumit 2014年

4
@MoumitMondal您的方法是静态的吗?您必须指定BindingFlags.Instance,以及BindingFlags.NonPublic用于非静态方法。
BrianS 2014年

没有@BrianS ..该方法是non-staticand private,并且该类是从System.Web.UI.Page.. 继承的。无论如何我还是傻瓜..我没有找到原因.. :(
Moumit 2014年

3
添加BindingFlags.FlattenHierarchy将使您能够将方法从父类获取到实例。
Dragonthoughts

67

BindingFlags.NonPublic本身不会返回任何结果。事实证明,将其与之结合就BindingFlags.Instance可以了。

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

相同的逻辑也适用于internal功能
supertopi 2015年

我有一个类似的问题。如果“这”是一个子类,而您尝试调用父类的私有方法怎么办?
persianLife

是否可以使用它来调用base.base类的受保护方法?
Shiv

51

并且,如果您真的想惹上麻烦,请编写扩展方法使其更容易执行:

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

14
有危险吗 是。但是当包裹在我的单元测试名称空间中时,它是一个很好的帮助扩展。谢谢你
罗伯特·瓦勒

5
如果您关心从调用的方法引发的实际异常,则最好将其包装到try catch块中,然后在捕获TargetInvokationException时重新抛出内部异常。我在单元测试助手扩展中做到了。
Slobodan Savkovic

2
反射危险吗?嗯... C#,Java,Python ...实际上一切都是危险的,甚至整个世界也是如此:D您只需要保管如何安全地执行操作...
传奇

16

Microsoft最近修改了反射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);
}

注意:

  • 如果所需的方法在泛型的超类中,objT必须将其显式设置为超类的类型。

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


它至少不适用于UWP .net版本,因为它仅适用于公共方法:“ 返回一个集合,该集合包含在当前类型上声明的所有与指定名称匹配的公共方法 ”。
Dmytro Bondarenko

1
@DmytroBondarenko我针对私有方法对其进行了测试,并且它可以正常工作。我确实看到了。不知道为什么它的行为与文档不同,但至少它能起作用。
欧文·詹姆斯

是的,如果文档中的GetDeclareMethod()内容仅用于检索公共方法,我不会将所有其他答案都称为过时的。
Mass Dot Net

10

您是否绝对确定无法通过继承完成此操作?反射是解决问题时您应该考虑的最后一件事,它会使重构,理解您的代码以及任何自动化分析变得更加困难。

看起来您应该只有一个DrawItem1,DrawItem2等类,该类覆盖了dynMethod。


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

8

尤其是对私人成员的反思是错误的

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

私有成员反射破坏了封装原理,因此将您的代码暴露给以下对象:

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

如果我仍然必须这样做怎么办?

在某些情况下,当您依赖第三方或需要一些未公开的api时,必须进行一些反思。有些人还使用它来测试自己拥有的某些类,但是他们不想更改接口以仅出于测试目的而访问内部成员。

如果你这样做,那就正确

  • 缓解容易中断:

为了减轻容易中断的问题,最好的办法是通过在连续集成构建等中运行的单元测试中的测试来检测任何潜在的中断。当然,这意味着您始终使用相同的程序集(其中包含私有成员)。如果使用动态负载和反射,则喜欢玩火,但始终可以捕获该调用可能产生的异常。

  • 缓解反射的速度:

在最新版本的.Net Framework中,CreateDelegate将MethodInfo调用的系数提高了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通话将围绕50倍的速度比MethodInfo.Invoke 使用draw作为标准Func像:

var res = draw(methodParams);

查看我的这篇文章以查看不同方法调用的基准


1
尽管我认为依赖注入应该是单元测试的首选方式,但我想谨慎地使用反射来访问原本无法进行测试的单元并不完全是邪恶的。我个人认为,与普通的[public] [protected] [private]修饰符一样,我们还应该具有[Test] [Composition]修饰符,以便可以在这些阶段使某些内容可见,而不必强制将所有内容完全公开(因此必须完全记录这些方法)
安德鲁颈部

1
感谢Fab列出了对私有成员进行反思的问题,这使我重新审视了使用它的感受并得出了一个结论...使用反思对私有成员进行单元测试是错误的,但是未对代码路径进行测试是真的很错。
安德鲁·帕特

2
但是,对于通常无法进行单元测试的遗留代码进行单元测试时,这是一种绝妙的方法
TS

2

您不仅可以为每种要绘制的类型使用不同的Draw方法吗?然后调用重载的Draw方法,传入要绘制的itemType类型的对象。

您的问题不清楚,itemType是否真正引用了不同类型的对象。



1

尽管对象实例具有保护级别,但仍调用任何方法。请享用!

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

0

阅读此(补充)答案(有时是答案),以了解问题的发展方向以及为什么该线程中的某些人抱怨“它仍然无法正常工作”

我编写的代码与此处的答案之一完全相同。但是我仍然有一个问题。我把断点放在

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

它执行但 mi == null

并继续这样的行为,直到我对所有涉及的项目进行了“重建”。当反射方法位于第三装配中时,我正在对一个装配进行单元测试。这完全令人困惑,但是我使用即时窗口来发现方法,并且发现尝试进行单元测试的私有方法具有旧名称(我将其重命名)。这告诉我,即使建立了单元测试项目,旧的程序集或PDB仍在那儿-由于某种原因,它没有测试过的项目。“重建”的工作


By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.