리플렉션을 사용하여 개인 메서드를 호출하려면 어떻게합니까?


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
나는 이것으로 너무 많은 어려움에 처하게 될 것입니다.
Frank Schwieterman

1
BindingFlags.NonPublicnot return privatemethod .. :(
Moumit

4
@MoumitMondal 메소드는 정적입니까? 비 정적 방법 BindingFlags.Instance뿐만 아니라 지정해야 BindingFlags.NonPublic합니다.
BrianS

어떤 @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"가 자식 클래스이고 부모의 private 메서드를 호출하려고하면 어떻게됩니까?
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
위험한? 예. 그러나 단위 테스트 네임 스페이스에 싸여 있으면 큰 도움이됩니다. 고마워
Robert Wahler 2014 년

5
호출 된 메소드에서 발생하는 실제 예외에 관심이있는 경우 TargetInvokationException이 잡힐 때 catch 블록으로 감싸고 내부 예외를 다시 발생시키는 것이 좋습니다. 나는 단위 테스트 도우미 확장에서 그렇게합니다.
Slobodan Savkovic

2
반사 위험? 흠 ... C #, Java, Python ... 실제로 모든 것이 위험합니다. 심지어 세상조차도 : D 당신은 그것을 안전하게하는 방법에주의해야합니다 ...
Legends

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

노트 :

  • 원하는 메소드가 제네릭 의 수퍼 클래스에 obj있는 경우 수퍼 클래스의 T유형으로 명시 적으로 설정해야합니다.

  • 메소드가 비동기 인 경우을 사용할 수 있습니다 await (Task) obj.InvokeMethod(…).


최소한 공개 메소드 에서만 작동하기 때문에 UWP .net 버전에서는 작동하지 않습니다 . " 지정된 이름과 일치하는 현재 유형에 선언 된 모든 공개 메소드 를 포함하는 콜렉션을 리턴합니다 ."
Dmytro Bondarenko

1
@DmytroBondarenko 개인 방법에 대해 테스트했으며 효과가있었습니다. 그러나 나는 그것을 보았다. 왜 문서와 다르게 작동하는지 확실하지 않지만 적어도 작동합니다.
Owen James

예, 문서 GetDeclareMethod()가 공용 메소드 만 검색하는 데 사용 된다고 말하면 다른 모든 대답은 쓸모가 없습니다 .
Mass Dot Net

10

상속을 통해 이것이 불가능하다는 것을 절대적으로 확신하십니까? 리플렉션은 문제를 해결할 때 가장 마지막으로 고려해야 할 사항이며 리팩토링, 코드 이해 및 자동화 된 분석을 더욱 어렵게 만듭니다.

dynMethod를 재정의하는 DrawItem1, DrawItem2 등의 클래스가 있어야합니다.


1
@Bill K : 다른 상황에서 우리는 상속을 사용하지 않기로 결정했습니다. 따라서 반영을 사용합니다. 대부분의 경우, 우리는 그렇게 할 것입니다.
Jeromy Irvine

8

특히 개인 회원에 대한 반성이 잘못되었습니다

  • 반사는 타입 안전을 깨뜨립니다. 존재하지 않는 (더 이상), 잘못된 매개 변수, 너무 많은 매개 변수, 충분하지 않은 또는 불완전한 순서 (이것은 내가 가장 좋아하는 방법)를 호출하려고 시도 할 수 있습니다. 그런데 반환 유형도 변경 될 수 있습니다.
  • 반사가 느립니다.

개인 멤버 리플렉션은 캡슐화 원칙을 위반 하여 코드를 다음에 노출시킵니다.

  • 클래스의 내부 동작을 처리해야하므로 코드의 복잡성증가 합니다. 숨겨진 것은 숨겨져 있어야합니다.
  • 코드 가 컴파일 되기는 쉽지만 메소드가 이름을 변경하면 실행되지 않습니다.
  • 개인 코드는 개인 코드 인 경우 그러한 방식으로 호출되지 않기 때문에 쉽게 깨뜨릴 수 있습니다. 아마도 private 메소드는 호출되기 전에 내부 상태를 기대할 수 있습니다.

어쨌든해야합니까?

타사에 의존하거나 노출되지 않은 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하는 것보다 약 50 배 빠릅니다 Func.

var res = draw(methodParams);

다른 메소드 호출에 대한 벤치 마크를 보려면 내 게시물을 확인하십시오.


1
의존성 주입이 선호되는 단위 테스트 방법이어야하지만,주의 깊게 사용하면 테스트에 액세스 할 수없는 유닛에 액세스하기 위해 리플렉션을 사용하는 것이 완전히 악한 것은 아닙니다. 개인적으로 저는 일반적인 [public] [protected] [private] 수정 자뿐만 아니라 [Test] [Composition] 수정 자도 가져야하므로 모든 단계를 공개하지 않고도 이러한 단계에서 특정 항목을 볼 수 있습니다 ( 따라서) 완전히 그 방법을 문서화해야
앤드류 머리

1
비공개 멤버에 반영하는 문제를 나열 해 주셔서 감사합니다 .Fab을 사용하여 느낌을 검토하고 결론을 내 렸습니다 ... 반복을 사용하여 비공개 멤버를 테스트하는 것은 잘못되었지만 코드 경로를 테스트하지 않은 것은 정말 정말 잘못되었습니다.
앤드류 페이트

2
그러나 일반적으로 단위 테스트가 불가능한 레거시 코드의 단위 테스트와 관련하여 훌륭한 방법입니다.
TS

2

그리려는 각 유형마다 다른 Draw 방법을 사용할 수 있습니까? 그런 다음 그려 질 itemType 유형의 객체를 전달하는 오버로드 된 Draw 메소드를 호출하십시오.

귀하의 질문에 itemType이 실제로 다른 유형의 객체를 참조하는지 여부가 명확하지 않습니다.


1

나는 당신이 그것을 전달할 수 있다고 생각 BindingFlags.NonPublic 는 IS GetMethod방법.


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

그리고 관련된 모든 프로젝트에서 "재 빌드"할 때까지 이와 같은 동작이 계속되었습니다. 반사 방법이 세 번째 어셈블리에 앉아있는 동안 하나의 어셈블리를 단위 테스트했습니다. 완전히 혼란 스럽지만 Immediate Window를 사용하여 메소드를 발견했으며 단위 테스트를 시도한 개인 메소드의 이름이 이전이라는 것을 알았습니다. 이것은 단위 테스트 프로젝트가 빌드 된 경우에도 이전 어셈블리 또는 PDB가 여전히 존재한다고 말했습니다. 어떤 이유로 테스트하지 않은 프로젝트가 있습니다. "재건축"효과


당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.