IT TIP

Func를 얻을 수 있습니까?

itqueen 2020. 12. 25. 10:38
반응형

Func를 얻을 수 있습니까? (또는 유사) MethodInfo 개체에서?


일반적으로 리플렉션을 사용하면 성능에 영향을 미친다는 것을 알고 있습니다. (저는 사실 성찰의 팬이 아닙니다. 이것은 순전히 학문적 인 질문입니다.)

다음과 같은 클래스가 있다고 가정합니다.

public class MyClass {
    public string GetName() {
        return "My Name";
    }
}

여기서 참아주세요. MyClasscalled 인스턴스가 있으면을 호출 x할 수 있습니다 x.GetName(). 또한 Func<string>변수를 x.GetName.

이제 여기 내 질문이 있습니다. 의 내가 생각한 하지 않습니다 위의 클래스라고 알고 MyClass; 나는 물건을 가지고 x있지만 그것이 무엇인지 모르겠다. 다음 GetName을 수행 하여 해당 객체에 메서드 가 있는지 확인할 수 있습니다 .

MethodInfo getName = x.GetType().GetMethod("GetName");

getNamenull이 아니라고 가정합니다 . 그 때 나는 또한 확인할 수없는 경우 getName.ReturnType == typeof(string)getName.GetParameters().Length == 0,이 시점에서, 나는 나의하여 방법을 표현하는 것이 매우 확신 할 수없는 것 getName오브젝트 수 확실히 A를 캐스팅 할 Func<string>어떻게 든?

나는이 있다는 것을 알고 있으며 MethodInfo.Invoke항상 다음 과 같이 만들 수 있다는 것도 알고 있습니다Func<string> .

Func<string> getNameFunc = () => getName.Invoke(x, null);

나는 내가 무엇을 요구하고있어 갈 수있는 방법이 있는지 추측 에서MethodInfo 객체 에 반사의 성능 비용을 들이지, 그것이 표현하는 실제 방법 프로세스가 있지만, , 예를 통해 (직접 메소드를 호출 할 수있는 그 시점 a Func<string>또는 유사한 것) 성능 저하 없이 .

내가 구상하는 것은 다음과 같이 보일 수 있습니다.

// obviously this would throw an exception if GetActualInstanceMethod returned
// something that couldn't be cast to a Func<string>
Func<string> getNameFunc = (Func<string>)getName.GetActualInstanceMethod(x);

(나는 존재하지 않는 실현, 아무것도 만약 거기에 궁금하네요 같은 .이)


이것은 약간 더 긴 경로이지만 빠른 메서드 호출을 제공하고 다른 답변과 달리 다른 인스턴스를 통과 할 수 있기 때문에 이전 답변을 대체합니다 (여러 인스턴스가 발생할 경우 같은 유형의). 원하지 않는 경우 하단의 업데이트를 확인하거나 Ben M의 답변을 확인하십시오.

다음은 원하는 작업을 수행하는 테스트 방법입니다.

public class TestType
{
  public string GetName() { return "hello world!"; }
}

[TestMethod]
public void TestMethod2()
{
  object o = new TestType();

  var input = Expression.Parameter(typeof(object), "input");
  var method = o.GetType().GetMethod("GetName", 
    System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.Public);
  //you should check for null *and* make sure the return type is string here.
  Assert.IsFalse(method == null && !method.ReturnType.Equals(typeof(string)));

  //now build a dynamic bit of code that does this:
  //(object o) => ((TestType)o).GetName();
  Func<object, string> result = Expression.Lambda<Func<object, string>>(
    Expression.Call(Expression.Convert(input, o.GetType()), method), input).Compile();

  string str = result(o);
  Assert.AreEqual("hello world!", str);
}

델리게이트를 한 번 빌드하면 사전에 캐시 할 수 있습니다.

Dictionary<Type, Func<object, string>> _methods;

그런 다음 들어오는 객체의 Type (GetType ()에서)을 키로 사용하여 사전에 추가하기 만하면됩니다. 앞으로는 먼저 사전에 준비된 델리게이트가 있는지 확인하고 (있는 경우 호출) 그렇지 않으면 먼저 빌드하고 추가 한 다음 호출합니다.

부수적으로 이것은 DLR이 동적 디스패치 메커니즘에 대해 수행하는 작업의 매우 단순화 된 버전입니다 (C # 용어에서 'dynamic'키워드를 사용하는 경우).

그리고 마지막으로

몇몇 사람들이 언급했듯이받은 객체에 직접 바인딩 된 Func를 굽고 싶다면 다음과 같이하십시오.

[TestMethod]
public void TestMethod3()
{
  object o = new TestType();

  var method = o.GetType().GetMethod("GetName", 
    System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.Public);

  Assert.IsFalse(method == null && !method.ReturnType.Equals(typeof(string)));

  //this time, we bake Expression.Constant(o) in.
  Func<string> result = Expression.Lambda<Func<string>>(
   Expression.Call(Expression.Constant(o), method)).Compile();

  string str = result(); //no parameter this time.
  Assert.AreEqual("hello world!", str);
}

하지만 표현식 트리가 삭제되면 해당 트리가 o범위 내에 있는지 확인해야합니다. 그렇지 않으면 불쾌한 결과를 얻을 수 있습니다. 가장 쉬운 방법은 델리게이트의 수명 동안 로컬 참조 (아마도 클래스 인스턴스에서)를 유지하는 것입니다. (Ben M의 댓글로 인해 삭제됨)


예, 가능합니다.

Func<string> func = (Func<string>)
                     Delegate.CreateDelegate(typeof(Func<string>), getName);

표현 트리를 구축하여 내 대답은 다음과 같습니다. 다른 답변과 달리 결과 ( getNameFunc)는 매개 변수로 전달할 필요없이 원래 인스턴스에 바인딩 된 함수입니다.

class Program
{
    static void Main(string[] args)
    {
        var p = new Program();
        var getNameFunc = GetStringReturningFunc(p, "GetName");
        var name = getNameFunc();
        Debug.Assert(name == p.GetName());
    }

    public string GetName()
    {
        return "Bob";
    }

    static Func<string> GetStringReturningFunc(object x, string methodName)
    {
        var methodInfo = x.GetType().GetMethod(methodName);

        if (methodInfo == null ||
            methodInfo.ReturnType != typeof(string) ||
            methodInfo.GetParameters().Length != 0)
        {
            throw new ArgumentException();
        }

        var xRef = Expression.Constant(x);
        var callRef = Expression.Call(xRef, methodInfo);
        var lambda = (Expression<Func<string>>)Expression.Lambda(callRef);

        return lambda.Compile();
    }
}

이를 수행하는 가장 쉬운 방법은 다음과 Delegate.CreateDelegate같습니다.

Func<string> getNameFunc = (Func<string>) Delegate.CreateDelegate(
                                           typeof(Func<string>), x, getName);

이 바인딩이 있음을 참고 getNameFunc하기 x때문에 각 x새 대리자 인스턴스를 작성해야합니다 것입니다. 이 옵션은 Expression기반 예제 보다 훨씬 덜 복잡합니다 . 그러나 식 기반 예제를 사용하면 Func<MyClass, string> getNameFuncForAny한 번만 만들 수 있으며 MyClass.

이러한 getNameFuncForAny를 만들려면 다음과 같은 메서드가 필요합니다.

public Func<MyClass, string> GetInstanceMethod(MethodInfo method)
{
    ParameterExpression x = Expression.Parameter(typeof(MyClass), "it");
    return Expression.Lambda<Func<MyClass, string>>(
        Expression.Call(x, method), x).Compile();
}

다음과 같이 사용할 수 있습니다.

Func<MyClass, string> getNameFuncForAny = GetInstanceMethod(getName);

MyClass x1 = new MyClass();
MyClass x2 = new MyClass();

string result1 = getNameFuncForAny(x1);
string result2 = getNameFuncForAny(x2);

에 묶이지 않으려면 Func<MyClass, string>다음을 정의 할 수 있습니다.

public TDelegate GetParameterlessInstanceMethod<TDelegate>(MethodInfo method)
{
    ParameterExpression x = Expression.Parameter(method.ReflectedType, "it");
    return Expression.Lambda<TDelegate>(
        Expression.Call(x, method), x).Compile();
}

You could build an Expression Tree representing a lambda calling this method and then Compile() it so that further calls will just be as fast as standard compiled calls.

Alternatively, I wrote this method a good while ago based on a great MSDN article, which generates a wrapper using IL to call any MethodInfo way faster than with MethodInfo.DynamicInvoke since once the code is generated, there are almost no overhead over a normal call.


One off the top of my head approach would be to use dynamic. You could then so something like this:

if( /* This method can be a Func<string> */)
{
    dynamic methodCall = myObject;
    string response = methodCall.GetName();
}

ReferenceURL : https://stackoverflow.com/questions/2933221/can-you-get-a-funct-or-similar-from-a-methodinfo-object

반응형