在我的类中有一组私有方法,我需要根据输入值动态调用其中一个方法。调用代码和目标方法都在同一个实例中。代码如下所示:
MethodInfo dynMethod = this.GetType().GetMethod("Draw_" + itemType);
dynMethod.Invoke(this, new object[] { methodParams });
在这种情况下,GetMethod()将不会返回私有方法。我需要为GetMethod()提供什么BindingFlags,以便它可以定位私有方法?
如果你真的想让自己陷入麻烦,通过写一个扩展方法来让它更容易执行:
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);
}
尤其是对私人成员的反思是错误的
反射破坏类型安全。您可以尝试调用一个不存在(不再存在)的方法,或者使用错误的参数,或者使用太多的参数,或者没有足够的参数……甚至顺序也不对(这是我最喜欢的:))。顺便说一下,返回类型也可以改变。
反思是缓慢的。
私有成员反射破坏了封装原则,从而将您的代码暴露于以下情况:
增加代码的复杂性,因为它必须处理类的内部行为。隐藏的东西应该继续隐藏。
使您的代码很容易破坏,因为它将编译,但不会运行,如果方法改变了它的名称。
使私有代码容易被破坏,因为如果它是私有的,就不打算这样调用它。也许私有方法在被调用之前需要一些内部状态。
如果我必须这么做呢?
有这样的情况,当你依赖于第三方或者你需要一些不公开的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快50倍左右。调用
使用draw作为标准Func,就像这样:
var res = draw(methodParams);
查看我的这篇文章,看看不同方法调用的基准测试
如果你真的想让自己陷入麻烦,通过写一个扩展方法来让它更容易执行:
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);
}