[C#] 리플렉션을 사용하여 일반 메서드를 호출하려면 어떻게합니까?

컴파일 할 때 type 매개 변수를 모르는 대신 런타임에 동적으로 얻을 때 제네릭 메서드를 호출하는 가장 좋은 방법은 무엇입니까?

다음 샘플 코드를 고려하십시오- Example()메소드 내에서 변수에 저장된 변수를 GenericMethod<T>()사용하여 호출하는 가장 간결한 방법은 무엇입니까?TypemyType

public class Sample
{
    public void Example(string typeName)
    {
        Type myType = FindType(typeName);

        // What goes here to call GenericMethod<T>()?
        GenericMethod<myType>(); // This doesn't work

        // What changes to call StaticMethod<T>()?
        Sample.StaticMethod<myType>(); // This also doesn't work
    }

    public void GenericMethod<T>()
    {
        // ...
    }

    public static void StaticMethod<T>()
    {
        //...
    }
}



답변

리플렉션을 사용하여 메서드를 시작한 다음 MakeGenericMethod 와 함께 형식 인수를 제공하여 메서드를 “구축”해야합니다 .

MethodInfo method = typeof(Sample).GetMethod(nameof(Sample.GenericMethod));
MethodInfo generic = method.MakeGenericMethod(myType);
generic.Invoke(this, null);

정적 메서드의 null경우 첫 번째 인수로을 전달 하십시오 Invoke. 그것은 일반적인 방법과는 아무런 관련이 없습니다. 단지 정상적인 반영 일뿐입니다.

언급했듯이, C # 4에서 사용하는 것이 훨씬 간단 dynamic합니다. 물론 형식 유추를 사용할 수 있다면. 문제의 정확한 예와 같이 형식 유추를 사용할 수없는 경우에는 도움이되지 않습니다.


답변

원래 답변에 추가 된 것입니다. 이것이 작동하는 동안 :

MethodInfo method = typeof(Sample).GetMethod("GenericMethod");
MethodInfo generic = method.MakeGenericMethod(myType);
generic.Invoke(this, null);

에 대한 컴파일 타임 검사를 잃어 버리는 것도 약간 위험합니다 GenericMethod. 나중에 리팩토링 및 rename을 수행 GenericMethod하면이 코드는 인식되지 않으며 런타임에 실패합니다. 또한 어셈블리의 사후 처리 (예 : 사용하지 않는 메소드 / 클래스를 난독 처리 또는 제거)하는 경우이 코드도 손상 될 수 있습니다.

따라서 컴파일 타임에 링크하는 방법을 알고 있는데 수백만 번 호출되지 않으므로 오버 헤드는 중요하지 않습니다.이 코드를 다음과 같이 변경합니다.

Action<> GenMethod = GenericMethod<int>;  //change int by any base type 
                                          //accepted by GenericMethod
MethodInfo method = this.GetType().GetMethod(GenMethod.Method.Name);
MethodInfo generic = method.MakeGenericMethod(myType);
generic.Invoke(this, null);

아주 예쁘지는 않지만 GenericMethod여기에 컴파일 시간 참조가 있으며 리팩토링, 삭제 또는 작업을 수행 GenericMethod하면이 코드는 계속 작동하거나 적어도 컴파일 시간에 중단됩니다 (예 : 제거하는 경우 GenericMethod).

다른 방법으로 새 ​​래퍼 클래스를 만들고를 통해 만들 수 Activator있습니다. 더 좋은 방법이 있는지 모르겠습니다.


답변

dynamic리플렉션 API 대신 형식 을 사용하면 런타임에만 알려진 형식 매개 변수를 사용하여 일반 메서드를 호출하는 것을 크게 단순화 할 수 있습니다 .

이 기술을 사용하려면 Type클래스 의 인스턴스뿐만 아니라 실제 객체에서 유형을 알아야합니다 . 그렇지 않으면 해당 유형의 오브젝트를 작성하거나 표준 리플렉션 API 솔루션을 사용해야합니다 . Activator.CreateInstance 메서드 를 사용하여 개체를 만들 수 있습니다 .

일반 메소드를 호출하려면 “정상적인”사용법에서 해당 유형이 유추 된 것이므로 알 수없는 유형의 오브젝트를로 캐스팅하는 것입니다 dynamic. 예를 들면 다음과 같습니다.

class Alpha { }
class Beta { }
class Service
{
    public void Process<T>(T item)
    {
        Console.WriteLine("item.GetType(): " + item.GetType()
                          + "\ttypeof(T): " + typeof(T));
    }
}

class Program
{
    static void Main(string[] args)
    {
        var a = new Alpha();
        var b = new Beta();

        var service = new Service();
        service.Process(a); // Same as "service.Process<Alpha>(a)"
        service.Process(b); // Same as "service.Process<Beta>(b)"

        var objects = new object[] { a, b };
        foreach (var o in objects)
        {
            service.Process(o); // Same as "service.Process<object>(o)"
        }
        foreach (var o in objects)
        {
            dynamic dynObj = o;
            service.Process(dynObj); // Or write "service.Process((dynamic)o)"
        }
    }
}

이 프로그램의 결과는 다음과 같습니다.

item.GetType(): Alpha    typeof(T): Alpha
item.GetType(): Beta     typeof(T): Beta
item.GetType(): Alpha    typeof(T): System.Object
item.GetType(): Beta     typeof(T): System.Object
item.GetType(): Alpha    typeof(T): Alpha
item.GetType(): Beta     typeof(T): Beta

Process전달 된 인수의 실제 유형 ( GetType()메서드 사용)과 일반 매개 변수 유형 ( typeof연산자 사용 ) 을 작성하는 일반 인스턴스 메소드입니다 .

객체 인자를 dynamic타입으로 캐스트함으로써 런타임까지 타입 파라미터를 제공하는 것을 연기했습니다. 때 Process메소드가 불려 dynamic인수 후 컴파일러는이 인수의 유형에 대해 상관하지 않는다. 컴파일러는 런타임에 리플렉션을 사용하여 전달 된 인수의 실제 유형을 확인하고 호출 할 최상의 메소드를 선택하는 코드를 생성합니다. 여기에는 하나의 일반 메소드 만 있으므로 적절한 유형 매개 변수를 사용하여 호출됩니다.

이 예제에서 출력은 다음과 같이 작성합니다.

foreach (var o in objects)
{
    MethodInfo method = typeof(Service).GetMethod("Process");
    MethodInfo generic = method.MakeGenericMethod(o.GetType());
    generic.Invoke(service, new object[] { o });
}

동적 유형의 버전은 확실히 더 짧고 작성하기 쉽습니다. 또한이 함수를 여러 번 호출하는 성능에 대해 걱정하지 않아도됩니다. DLR 의 캐싱 메커니즘 덕분에 동일한 유형의 인수를 사용하는 다음 호출이 더 빨라 집니다. 물론 호출 된 대리자를 캐시하는 코드를 작성할 수 있지만 dynamic형식을 사용하면이 동작을 무료로 얻을 수 있습니다.

호출하려는 일반 메소드에 매개 변수화 된 유형의 인수가없는 경우 (유형 매개 변수를 유추 할 수 없음) 다음 예제와 같이 헬퍼 메소드에서 일반 메소드의 호출을 랩핑 할 수 있습니다.

class Program
{
    static void Main(string[] args)
    {
        object obj = new Alpha();

        Helper((dynamic)obj);
    }

    public static void Helper<T>(T obj)
    {
        GenericMethod<T>();
    }

    public static void GenericMethod<T>()
    {
        Console.WriteLine("GenericMethod<" + typeof(T) + ">");
    }
}

유형 안전성 향상

dynamic리플렉션 API를 대신 하여 객체를 사용하는 것에 대한 가장 큰 장점은 런타임까지 알 수없는이 특정 유형의 컴파일 시간 검사 만 손실한다는 것입니다. 다른 인수와 메소드의 이름은 평소와 같이 컴파일러에 의해 정적으로 분석됩니다. 인수를 제거하거나 더 추가하거나, 유형을 변경하거나 메소드 이름을 바꾸면 컴파일 타임 오류가 발생합니다. 메소드 이름을 문자열로 Type.GetMethod, 인수를 객체 배열로 인수로 제공하면 이런 일이 발생하지 않습니다 MethodInfo.Invoke.

다음은 컴파일시 (코멘트 된 코드) 및 런타임시 일부 오류가 발생하는 방법을 보여주는 간단한 예입니다. 또한 DLR이 호출 할 메소드를 분석하는 방법을 보여줍니다.

interface IItem { }
class FooItem : IItem { }
class BarItem : IItem { }
class Alpha { }

class Program
{
    static void Main(string[] args)
    {
        var objects = new object[] { new FooItem(), new BarItem(), new Alpha() };
        for (int i = 0; i < objects.Length; i++)
        {
            ProcessItem((dynamic)objects[i], "test" + i, i);

            //ProcesItm((dynamic)objects[i], "test" + i, i);
            //compiler error: The name 'ProcesItm' does not
            //exist in the current context

            //ProcessItem((dynamic)objects[i], "test" + i);
            //error: No overload for method 'ProcessItem' takes 2 arguments
        }
    }

    static string ProcessItem<T>(T item, string text, int number)
        where T : IItem
    {
        Console.WriteLine("Generic ProcessItem<{0}>, text {1}, number:{2}",
                          typeof(T), text, number);
        return "OK";
    }
    static void ProcessItem(BarItem item, string text, int number)
    {
        Console.WriteLine("ProcessItem with Bar, " + text + ", " + number);
    }
}

여기서 우리는 인자를 dynamic타입 으로 캐스팅하여 몇 가지 메소드를 다시 실행 합니다. 첫 번째 인수 유형의 확인 만 런타임에 연기됩니다. 호출하는 메소드의 이름이 존재하지 않거나 다른 인수가 유효하지 않은 경우 (잘못된 인수 또는 유형이 잘못된 경우) 컴파일러 오류가 발생합니다.

dynamic인수를 메소드에 전달하면 이 호출은 최근에 바인드 됩니다. 메소드 과부하 해결은 런타임에 발생하며 최상의 과부하를 선택하려고합니다. 따라서 유형 ProcessItem의 객체로 메소드 를 호출하면 BarItem이 유형과 더 잘 일치하기 때문에 실제로는 제네릭이 아닌 메소드를 호출합니다. 그러나이 Alpha객체를 처리 할 수있는 메서드가 없기 때문에 형식 의 인수를 전달하면 런타임 오류가 발생 합니다 (일반 메서드에는 제약 조건이 where T : IItem있고 Alpha클래스는이 인터페이스를 구현하지 않습니다). 그러나 그것은 요점입니다. 컴파일러에이 호출이 유효하다는 정보가 없습니다. 프로그래머는 이것을 알고 있으며이 코드가 오류없이 실행되도록해야합니다.

리턴 타입

동적 유형의 매개 변수 void 이외의 메소드를 호출 할 때, 그것의 반환 형식은 아마 것입니다 dynamic . 따라서 이전 예제를이 코드로 변경하려면 다음을 수행하십시오.

var result = ProcessItem((dynamic)testObjects[i], "test" + i, i);

결과 객체의 유형은 dynamic입니다. 컴파일러는 어떤 메소드가 호출 될지 항상 알지 못하기 때문입니다. 함수 호출의 리턴 유형을 알고 있으면 나머지 코드가 정적으로 유형이되도록 암시 적으로 이를 필수 유형으로 변환 해야합니다.

string result = ProcessItem((dynamic)testObjects[i], "test" + i, i);

유형이 일치하지 않으면 런타임 오류가 발생합니다.

실제로 이전 예제에서 결과 값을 얻으려고하면 두 번째 루프 반복에서 런타임 오류가 발생합니다. void 함수의 반환 값을 저장하려고했기 때문입니다.


답변

C # 4.0에서는 DLR이 런타임 유형을 사용하여 호출 할 수 있으므로 리플렉션이 필요하지 않습니다. DLR 라이브러리를 사용하면 C # 컴파일러 생성 코드 대신 동적으로 어려움을 겪기 때문에 오픈 소스 프레임 워크 Dynamitey (.net 표준 1.5)를 사용하면 컴파일러가 생성하는 동일한 호출에 쉽게 캐시 된 런타임 액세스를 제공합니다 당신을 위해.

var name = InvokeMemberName.Create;
Dynamic.InvokeMemberAction(this, name("GenericMethod", new[]{myType}));


var staticContext = InvokeContext.CreateStatic;
Dynamic.InvokeMemberAction(staticContext(typeof(Sample)), name("StaticMethod", new[]{myType}));


답변

Adrian Gallero의 답변 에 추가 :

타입 정보에서 일반 메소드를 호출하려면 세 단계가 필요합니다.

TLDR : 타입 객체로 알려진 제네릭 메서드를 호출하는 것은 다음과 같은 방법으로 수행 할 수 있습니다.

((Action)GenericMethod<object>)
    .Method
    .GetGenericMethodDefinition()
    .MakeGenericMethod(typeof(string))
    .Invoke(this, null);

여기서 GenericMethod<object>호출 할 메소드 이름과 일반 제한 조건을 만족시키는 모든 유형이 있습니다.

(Action)은 호출 할 메소드의 서명과 일치합니다. 즉 ( Func<string,string,int>또는 Action<bool>)

1 단계는 일반 메소드 정의에 대한 MethodInfo를 가져옵니다.

방법 1 : 적절한 유형 또는 바인딩 플래그와 함께 GetMethod () 또는 GetMethods ()를 사용하십시오.

MethodInfo method = typeof(Sample).GetMethod("GenericMethod");

방법 2 : 대리자를 만들고 MethodInfo 개체를 가져온 다음 GetGenericMethodDefinition을 호출합니다.

메소드가 포함 된 클래스 내부에서 :

MethodInfo method = ((Action)GenericMethod<object>)
    .Method
    .GetGenericMethodDefinition();

MethodInfo method = ((Action)StaticMethod<object>)
    .Method
    .GetGenericMethodDefinition();

메소드가 포함 된 클래스 외부에서 :

MethodInfo method = ((Action)(new Sample())
    .GenericMethod<object>)
    .Method
    .GetGenericMethodDefinition();

MethodInfo method = ((Action)Sample.StaticMethod<object>)
    .Method
    .GetGenericMethodDefinition();

C #에서 메서드 이름 (예 : “ToString”또는 “GenericMethod”)은 실제로 하나 이상의 메서드를 포함 할 수있는 메서드 그룹을 나타냅니다. 메소드 매개 변수의 유형을 제공 할 때까지는 어떤 메소드를 참조하는지 알 수 없습니다.

((Action)GenericMethod<object>)특정 방법에 대한 대리자를 나타냅니다. ((Func<string, int>)GenericMethod<object>)
GenericMethod의 다른 과부하를 나타냅니다.

방법 3 : 메서드 호출 식을 포함하는 람다 식 만들기, MethodInfo 개체 및 GetGenericMethodDefinition

MethodInfo method = ((MethodCallExpression)((Expression<Action<Sample>>)(
    (Sample v) => v.GenericMethod<object>()
    )).Body).Method.GetGenericMethodDefinition();

이것은 고장

본문이 원하는 메소드를 호출하는 람다 식을 만듭니다.

Expression<Action<Sample>> expr = (Sample v) => v.GenericMethod<object>();

본문을 추출하여 MethodCallExpression으로 캐스트

MethodCallExpression methodCallExpr = (MethodCallExpression)expr.Body;

메소드에서 일반 메소드 정의를 가져옵니다.

MethodInfo methodA = methodCallExpr.Method.GetGenericMethodDefinition();

2 단계는 MakeGenericMethod를 호출하여 적절한 유형의 일반 메소드를 작성합니다.

MethodInfo generic = method.MakeGenericMethod(myType);

3 단계는 적절한 인수로 메소드를 호출합니다.

generic.Invoke(this, null);


답변

아무도 ” 클래식 리플렉션 “솔루션을 제공하지 않았으므로 다음은 완전한 코드 예제입니다.

using System;
using System.Collections;
using System.Collections.Generic;

namespace DictionaryRuntime
{
    public class DynamicDictionaryFactory
    {
        /// <summary>
        /// Factory to create dynamically a generic Dictionary.
        /// </summary>
        public IDictionary CreateDynamicGenericInstance(Type keyType, Type valueType)
        {
            //Creating the Dictionary.
            Type typeDict = typeof(Dictionary<,>);

            //Creating KeyValue Type for Dictionary.
            Type[] typeArgs = { keyType, valueType };

            //Passing the Type and create Dictionary Type.
            Type genericType = typeDict.MakeGenericType(typeArgs);

            //Creating Instance for Dictionary<K,T>.
            IDictionary d = Activator.CreateInstance(genericType) as IDictionary;

            return d;

        }
    }
}

위의 DynamicDictionaryFactory클래스에는 메소드가 있습니다.

CreateDynamicGenericInstance(Type keyType, Type valueType)

호출 keyType과 키에 키와 값의 유형이 정확히 지정된 IDictionary 인스턴스를 만들고 반환합니다 valueType.

다음은 이 메소드를 호출하여 인스턴스를 작성하고 사용하는 방법에 대한 완전한 예입니다Dictionary<String, int> .

using System;
using System.Collections.Generic;

namespace DynamicDictionary
{
    class Test
    {
        static void Main(string[] args)
        {
            var factory = new DictionaryRuntime.DynamicDictionaryFactory();
            var dict = factory.CreateDynamicGenericInstance(typeof(String), typeof(int));

            var typedDict = dict as Dictionary<String, int>;

            if (typedDict != null)
            {
                Console.WriteLine("Dictionary<String, int>");

                typedDict.Add("One", 1);
                typedDict.Add("Two", 2);
                typedDict.Add("Three", 3);

                foreach(var kvp in typedDict)
                {
                    Console.WriteLine("\"" + kvp.Key + "\": " + kvp.Value);
                }
            }
            else
                Console.WriteLine("null");
        }
    }
}

위의 콘솔 응용 프로그램이 실행되면 올바른 예상 결과를 얻습니다.

Dictionary<String, int>
"One": 1
"Two": 2
"Three": 3


답변

이것은 Grax의 답변을 기반으로하는 2 센트 이지만 일반적인 방법에는 두 가지 매개 변수가 필요합니다.

메소드가 헬퍼 클래스에서 다음과 같이 정의되었다고 가정하십시오.

public class Helpers
{
    public static U ConvertCsvDataToCollection<U, T>(string csvData)
    where U : ObservableCollection<T>
    {
      //transform code here
    }
}

필자의 경우 U 유형은 항상 T 유형의 관찰 가능한 컬렉션 저장 객체입니다.

미리 정의 된 유형이 있으므로 먼저 관찰 가능한 컬렉션 (U)과 그 안에 저장된 개체 (T)를 나타내는 “더미”개체를 만들고 Make를 호출 할 때 해당 형식을 가져 오는 데 사용됩니다.

object myCollection = Activator.CreateInstance(collectionType);
object myoObject = Activator.CreateInstance(objectType);

그런 다음 GetMethod를 호출하여 Generic 함수를 찾으십시오.

MethodInfo method = typeof(Helpers).
GetMethod("ConvertCsvDataToCollection");

지금까지 위의 호출은 위에서 설명한 것과 거의 동일하지만 여러 매개 변수를 전달해야 할 때 약간의 차이가 있습니다.

위에서 생성 한 “더미”객체의 유형을 포함하는 MakeGenericMethod 함수에 Type [] 배열을 전달해야합니다.

MethodInfo generic = method.MakeGenericMethod(
new Type[] {
   myCollection.GetType(),
   myObject.GetType()
});

완료되면 위에서 언급 한대로 Invoke 메소드를 호출해야합니다.

generic.Invoke(null, new object[] { csvData });

그리고 당신은 끝났습니다. 매력을 발휘합니다!

최신 정보:

@Bevan이 강조했듯이 매개 변수 에서처럼 MakeGenericMethod 함수를 호출 할 때 배열을 만들 필요가 없으며 형식을이 함수에 직접 전달할 수 있으므로 형식을 얻기 위해 객체를 만들 필요가 없습니다. 필자의 경우 다른 클래스에서 미리 정의 된 유형이 있으므로 코드를 다음과 같이 변경했습니다.

object myCollection = null;

MethodInfo method = typeof(Helpers).
GetMethod("ConvertCsvDataToCollection");

MethodInfo generic = method.MakeGenericMethod(
   myClassInfo.CollectionType,
   myClassInfo.ObjectType
);

myCollection = generic.Invoke(null, new object[] { csvData });

myClassInfo에는 Type생성자에 전달 된 열거 형 값을 기반으로 런타임에 설정 한 2 가지 유형의 속성이 포함 되어 있으며 MakeGenericMethod에서 사용하는 관련 유형을 제공합니다.

@Bevan을 강조해 주셔서 다시 한 번 감사드립니다.