リフレクションを使用してプライベートメソッドを呼び出すにはどうすればよいですか?


326

私のクラスにはプライベートメソッドのグループがあり、入力値に基づいて動的に呼び出す必要があります。呼び出しコードとターゲットメソッドの両方が同じインスタンスにあります。コードは次のようになります。

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

この場合、GetMethod()プライベートメソッドは返されません。プライベートメソッドを見つけBindingFlagsられるGetMethod()ようにするには、何を提供する必要がありますか?

回答:


498

BindingFlagsを受け入れるオーバーロードバージョンGetMethodを使用するようにコードを変更するだけです。

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

これがBindingFlags列挙ドキュメントです。


248
私はこれで多くのトラブルに巻き込まれるつもりです。
フランク・シュヴィーターマン

1
BindingFlags.NonPublicprivateメソッドを返さない.. :(
Moumit 2014年

4
@MoumitMondalはメソッドが静的ですか?非静的メソッドBindingFlags.Instanceと同様に指定する必要がありますBindingFlags.NonPublic
BrianS 2014年

いいえ、@ BrianS ..メソッドはnon-staticprivateあり、クラスはSystem.Web.UI.Page.. から継承されます..とにかくばかになります..私は理由を見つけられませんでした。:(
Moumit

3
追加するBindingFlags.FlattenHierarchyと、親クラスからインスタンスにメソッドを取得できます。
Dragonthoughts、

67

BindingFlags.NonPublic単独では結果を返しません。結局のところ、それを組み合わせるとBindingFlags.Instanceトリックが実行されます。

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

同じ論理がに適用されinternal、同様の機能
supertopi

同様の問題があります。「this」が子クラスで、親のプライベートメソッドを呼び出そうとした場合はどうなりますか?
persianLife 2016年

これを使用してbase.baseクラスで保護されたメソッドを呼び出すことはできますか?
Shiv 2018年

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
危険な?はい。しかし、ユニットテストの名前空間にラップすると、優れたヘルパー拡張になります。これをありがとう。
Robert Wahler 2014

5
呼び出されたメソッドからスローされた実際の例外が気になる場合は、それをtry catchブロックにラップして、TargetInvokationExceptionがキャッチされたときに内部例外を再スローすることをお勧めします。私は単体テストヘルパー拡張機能でそれを行います。
Slobodan Savkovic

2
危険な反射?うーん... C#、Java、Python ...実際にはすべてが危険です。世界でも危険です。D安全にそれを行う方法に注意する必要があります...
Legends

16

マイクロソフトは最近、これらの回答のほとんどを廃止するリフレクション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ボンダレンコ2017

1
@DmytroBondarenko私はそれをプライベートメソッドに対してテストしましたが、うまくいきました。しかし、私はそれを見ました。なぜドキュメントと動作が異なるのかはわかりませんが、少なくとも動作します。
オーウェンジェームズ

ええ、ドキュメンテーションがGetDeclareMethod()パブリックメソッドのみを取得するために使用されることを意図している場合、私は他のすべての回答を廃止することはしません。
マスドットネット

10

これは継承によって実行できないと確信していますか?リフレクションは、問題を解決するときに最後に注目すべきものであり、リファクタリング、コードの理解、および自動分析をさらに困難にします。

dynMethodをオーバーライドするDrawItem1、DrawItem2などのクラスが必要なようです。


1
@Bill K:他の状況を考慮して、継承にこれを使用しないことを決定したため、リフレクションを使用しました。ほとんどの場合、そのようにします。
Jeromy Irvine

8

特に個人会員への反省は間違っている

  • 反射はタイプセーフを壊します。存在しない(もう)メソッド、またはパラメーターが間違っているメソッド、パラメーターが多すぎるメソッド、または不十分なメソッド、または間違った順序(これは私のお気に入り:)であるメソッドを呼び出そうとすることができます。ちなみに、戻り値の型も変わる可能性があります。
  • 反射が遅い。

プライベートメンバーのリフレクションはカプセル化の原則に違反するため、コードを次のように公開します。

  • クラスの内部動作を処理する必要があるため、コードの複雑さ増します。隠されているものは隠されたままにすべきです。
  • コードはコンパイルされますが、メソッドの名前が変更された場合は実行されないため、コードが壊れやすくなります。
  • プライベートコードは、プライベートコードの場合、そのように呼び出すことを目的としていないため、簡単に解読できます。おそらく、プライベートメソッドは、呼び出される前に内部状態を期待しています。

とにかくそれをしなければならない場合はどうなりますか?

そのため、サードパーティに依存している場合や、公開されていないAPIが必要な場合は、リフレクションを行う必要があります。所有しているいくつかのクラスをテストするためにそれを使用する人もいますが、テストのためだけに内部メンバーにアクセスできるようにインターフェースを変更する必要はありません。

あなたがそれをするなら、それを正しくしなさい

  • 壊れやすいものを緩和します。

壊れやすい問題を軽減するには、継続的インテグレーションビルドなどで実行される単体テストでテストすることで、潜在的な中断を検出するのが最善です。もちろん、常に同じアセンブリ(プライベートメンバーを含む)を使用することを意味します。動的なロードとリフレクションを使用する場合は、火で遊ぶのが好きですが、呼び出しが生成する可能性のある例外をいつでもキャッチできます。

  • 反射の遅さを緩和します。

.Net Frameworkの最近のバージョンでは、CreateDelegateが50倍にMethodInfoを呼び出してビートしました。

// 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倍速くなります。drawFunc

var res = draw(methodParams);

私のこの投稿をチェックして、さまざまなメソッド呼び出しのベンチマークを確認してください


1
依存性注入はユニットテストの望ましい方法であるはずですが、リフレクションを使用してテストにアクセスできないユニットにアクセスすることは、まったく危険ではありません。個人的には、通常の[public] [protected] [private]修飾子だけでなく、[Test] [Composition]修飾子も必要だと思うので、これらすべての段階で特定のものを完全に公開せずに見えるようにすることができます(したがって、これらのメソッドを完全に文書化する必要があります)
アンドリューペイト2017

1
プライベートメンバーへの反映の問題をリストしてくれたFabに感謝します。それにより、それを使用することに対する私の感想を見直し、結論に至りました...リフレクションを使用してプライベートメンバーをユニットテストすることは間違っていますが、コードパスをテストしないままにすることは本当に本当に間違っています。
アンドリューパテ2017

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

この(補足)回答(時には回答)を読んで、これがどこに向かっているのか、そしてこのスレッドの一部の人々が「まだ機能しない」と不平を言う理由を理解してください。

私はここで答えの1つとまったく同じコードを書きました。しかし、まだ問題がありました。ブレークポイントを置いた

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

実行しましたが mi == null

そして、関係するすべてのプロジェクトで「再構築」を行うまで、このような動作が続きました。反射法が3番目のアセンブリにある間に、1つのアセンブリのユニットテストを行っていました。完全に混乱しましたが、イミディエイトウィンドウを使用してメソッドを発見したところ、ユニットテストを試みたプライベートメソッドの名前が古い(名前を変更した)ことに気付きました。これは、ユニットテストプロジェクトがビルドされたとしても、古いアセンブリまたはPDBがまだ存在することを教えてくれました-何らかの理由で、プロジェクトはテストされませんでした。「再構築」が機能した


弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.