ChatGPT解决这个技术问题 Extra ChatGPT

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

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

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

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

BindingFlags.NonPublic

g
gunr2171

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

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

这是BindingFlags enumeration documentation


我会给自己惹上这么多麻烦。
BindingFlags.NonPublic 不返回 private 方法.. :(
@MoumitMondal 你的方法是静态的吗?您必须为非静态方法指定 BindingFlags.InstanceBindingFlags.NonPublic
添加 BindingFlags.FlattenHierarchy 将使您能够从父类获取方法到您的实例。
第一条评论的点赞量让我感到不安。然而,我即将使用它。我不得不查找它的原因是因为我通常会尽量避免这种情况。这通常很糟糕,但在某些情况下它可以工作。这就是它在那里的原因,也是我在这里的原因。请不要滥用这个。
J
Jeromy Irvine

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

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

同样的逻辑也适用于 internal 函数
我有一个类似的问题。如果“this”是一个子类并且您尝试调用父类的私有方法怎么办?
c
cod3monk3y

如果你真的想让自己陷入困境,可以通过编写一个扩展方法让它更容易执行:

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

危险的?是的。但是当包装在我的单元测试命名空间中时,它是一个很好的辅助扩展。谢谢你。
如果您关心从调用的方法抛出的真正异常,最好将其包装到 try catch 块中,并在 TargetInvokationException 捕获时重新抛出内部异常。我在我的单元测试助手扩展中这样做。
反射危险?嗯... C#、Java、Python...实际上一切都是危险的,即使是世界 :D 您只需要注意如何安全地进行...
O
Owen James

Microsoft 最近modified the reflection 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 版本,因为它仅适用于公共方法:“返回一个集合,其中包含在当前类型上声明的与指定名称匹配的所有公共方法”。
@DmytroBondarenko 我针对私有方法对其进行了测试,并且有效。不过,我确实看到了。不知道为什么它的行为与文档不同,但至少它有效。
是的,如果文档说 GetDeclareMethod() 旨在仅用于检索公共方法,我不会称所有其他答案都已过时。
C
Community

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

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

反射很慢。

私有成员反射违反了 encapsulation 原则,从而将您的代码暴露给以下内容:

增加代码的复杂性,因为它必须处理类的内部行为。隐藏的东西应该保持隐藏。

使您的代码易于破解,因为它可以编译,但如果方法更改其名称则不会运行。

使私有代码易于破解,因为如果它是私有的,则不打算以这种方式调用它。也许私有方法在被调用之前需要一些内部状态。

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

有这样的情况,当你依赖第三方或者你需要一些不暴露的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 调用将比 MethodInfo.Invoke 使用 draw 作为标准 Func 快大约 50 倍,如下所示:

var res = draw(methodParams);

检查此 post of mine 以查看不同方法调用的基准


尽管我认为依赖注入应该是单元测试的首选方式,但我认为谨慎使用反射来访问原本无法进行测试的单元并不是完全邪恶的。就我个人而言,我认为除了普通的 [public][protected][private] 修饰符之外,我们还应该有 [Test][Composition] 修饰符,因此可以在这些阶段使某些事物可见,而不必被迫将所有内容完全公开(因此必须完整记录这些方法)
感谢 Fab 列出了反射私有成员的问题,它让我回顾了我对使用它的感觉并得出了一个结论......使用反射对你的私有成员进行单元测试是错误的,但是未经测试的代码路径是真的真的错了。
但是当涉及到通常不可单元测试的遗留代码的单元测试时,这是一种绝妙的方法
B
Bill K

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

看起来你应该只有一个 DrawItem1、DrawItem2 等覆盖你的 dynMethod 的类。


@Bill K:考虑到其他情况,我们决定不为此使用继承,因此使用反射。在大多数情况下,我们会这样做。
P
Peter Hession

你能不能对你想要绘制的每种类型都有不同的 Draw 方法?然后调用重载的 Draw 方法传入要绘制的 itemType 类型的对象。

您的问题并不清楚 itemType 是否真正指代不同类型的对象。


M
Maksim Shamihulau

调用任何方法,尽管其对对象实例的保护级别。享受!

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

A
Armin Ronacher

我认为您可以传递它BindingFlags.NonPublic,其中 itGetMethod 方法。


u
urglas

应该注意的是,从派生类调用可能会出现问题。

容易出错:

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

正确的:

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

T
T.S.

阅读此(补充)答案(有时是答案)以了解这是怎么回事以及为什么该线程中的某些人抱怨“它仍然无法正常工作”

I wrote exactly same code as one of the answers here。但我仍然有一个问题。我把断点放在

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

它执行但 mi == null

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