.net Func <T>를 .net Expression <Func <T >>로 변환


118

메서드 호출을 사용하면 람다에서 식으로 쉽게 이동할 수 있습니다.

public void GimmeExpression(Expression<Func<T>> expression)
{
    ((MemberExpression)expression.Body).Member.Name; // "DoStuff"
}

public void SomewhereElse()
{
    GimmeExpression(() => thing.DoStuff());
}

하지만 드물게 만 Func를 표현으로 바꾸고 싶습니다.

public void ContainTheDanger(Func<T> dangerousCall)
{
    try 
    {
        dangerousCall();
    }
    catch (Exception e)
    {
        // This next line does not work...
        Expression<Func<T>> DangerousExpression = dangerousCall;
        var nameOfDanger = 
            ((MemberExpression)dangerousCall.Body).Member.Name;
        throw new DangerContainer(
            "Danger manifested while " + nameOfDanger, e);
    }
}

public void SomewhereElse()
{
    ContainTheDanger(() => thing.CrossTheStreams());
}

작동하지 않는 줄은 나에게 컴파일 타임 오류를 제공 Cannot implicitly convert type 'System.Func<T>' to 'System.Linq.Expressions.Expression<System.Func<T>>'합니다. 명시 적 캐스트는 상황을 해결하지 않습니다. 내가 간과하고있는 이것을 할 수있는 시설이 있는가?


나는 '희귀 한 경우'의 예를 많이 사용하지 않는다. 호출자가 Func <T>를 전달합니다. Func <T>가 무엇인지 (예외를 통해) 호출자에게 다시 반복 할 필요가 없습니다.
Adam Ralph

2
예외는 호출자에서 처리되지 않습니다. 그리고 다른 Func <T>를 전달하는 여러 호출 사이트가 있기 때문에 호출자에서 예외를 포착하면 중복이 생성됩니다.
Dave Cameron

1
예외 스택 추적은이 정보를 표시하도록 설계되었습니다. Func <T> 호출 내에서 예외가 throw되면 스택 추적에 표시됩니다. 덧붙여 말하면, 표현식을 받아들이고 호출을 위해 컴파일하는 것과 같이 다른 방식으로 선택하는 경우 스택 추적이 at lambda_method(Closure )컴파일 된 델리게이트의 호출 과 같은 것을 보여주기 때문에 이것을 잃게됩니다 .
Adam Ralph

답변:


104

오, 전혀 쉬운 일이 아닙니다. 표현식이 아닌 Func<T>일반을 나타냅니다 delegate. 그렇게 할 수있는 방법이 있다면 (최적화 및 컴파일러가 수행 한 기타 작업으로 인해 일부 데이터가 버려 질 수 있으므로 원래 표현식을 다시 가져 오는 것이 불가능할 수 있음) 즉석에서 IL을 디스 어셈블하는 것입니다. 그리고 표현을 추론하는 것 (이것이 결코 쉬운 일은 아닙니다). 람다 표현식을 데이터 ( Expression<Func<T>>)로 처리하는 것은 컴파일러가 수행하는 마법입니다 (기본적으로 컴파일러는 IL로 컴파일하는 대신 코드에서 표현식 트리를 빌드합니다).

관련 사실

이것이 람다를 극한까지 밀어 붙이는 언어 (예 : Lisp)가 인터프리터 로 구현하기가 더 쉬운 이유 입니다. 이러한 언어에서 코드와 데이터는 본질적으로 동일 하지만 ( 런타임 에서도 ) 우리 칩은 코드의 형태를 이해할 수 없기 때문에이를 이해하는 인터프리터를 구축하여 그러한 기계를 에뮬레이션해야합니다. 언어와 같은 Lisp에 의해 선택) 또는 어느 정도 (코드가 더 이상 데이터와 정확히 동일하지 않음) 전력을 희생 (C #에 의해 선택)됩니다. C #에서 컴파일러는 람다가 컴파일 타임에 코드 ( Func<T>) 및 데이터 ( Expression<Func<T>>) 로 해석 될 수 있도록하여 코드를 데이터로 취급하는 환상을 제공합니다 .


3
Lisp는 해석 할 필요가 없으며 쉽게 컴파일 할 수 있습니다. 매크로는 컴파일 타임에 확장되어야하며 지원을 원한다면 eval컴파일러를 시작해야하지만 그 외에는 전혀 문제가 없습니다.
구성자

2
"Expression <Func <T >> DangerousExpression = () => dangerousCall ();" 쉽지 않다?
mheyman

10
@mheyman Expression래퍼 작업에 대해 새로 만들지 만 dangerousCall델리게이트의 내부에 대한 표현식 트리 정보가 없습니다 .
Nenad

34
    private static Expression<Func<T, bool>> FuncToExpression<T>(Func<T, bool> f)  
    {  
        return x => f(x);  
    } 

1
반환 된 식의 구문 트리를 탐색하고 싶었습니다. 이 접근 방식으로 그렇게 할 수 있습니까?
Dave Cameron

6
@DaveCameron-아니요. 위 답변 참조-이미 컴파일 된 파일 Func은 새 Expression에서 숨겨집니다. 이것은 단순히 코드 위에 데이터의 한 레이어를 추가합니다. f추가 세부 사항없이 매개 변수를 찾기 위해 한 레이어를 순회 할 수 있으므로 시작한 위치에 바로 있습니다.
Jonno

21

아마도해야 할 일은 방법을 바꾸는 것입니다. Expression>을 받아 컴파일하고 실행합니다. 실패하면 이미 살펴볼 표현식이 있습니다.

public void ContainTheDanger(Expression<Func<T>> dangerousCall)
{
    try 
    {
        dangerousCall().Compile().Invoke();;
    }
    catch (Exception e)
    {
        // This next line does not work...
        var nameOfDanger = 
            ((MemberExpression)dangerousCall.Body).Member.Name;
        throw new DangerContainer(
            "Danger manifested while " + nameOfDanger, e);
    }
}

public void SomewhereElse()
{
    ContainTheDanger(() => thing.CrossTheStreams());
}

분명히 이것의 성능에 미치는 영향을 고려하고 이것이 실제로 수행해야하는 작업인지 결정해야합니다.


7

그러나 .Compile () 메서드를 통해 다른 방법으로 이동할 수 있습니다. 이것이 유용한 지 확실하지 않습니다.

public void ContainTheDanger<T>(Expression<Func<T>> dangerousCall)
{
    try
    {
        var expr = dangerousCall.Compile();
        expr.Invoke();
    }
    catch (Exception e)
    {
        Expression<Func<T>> DangerousExpression = dangerousCall;
        var nameOfDanger = ((MethodCallExpression)dangerousCall.Body).Method.Name;
        throw new DangerContainer("Danger manifested while " + nameOfDanger, e);
    }
}

public void SomewhereElse()
{
    var thing = new Thing();
    ContainTheDanger(() => thing.CrossTheStreams());
}

6

때때로 표현식이 필요하고 때로는 델리게이트가 필요한 경우 다음 두 가지 옵션이 있습니다.

  • 다른 방법 (각각 1 개)
  • 항상 Expression<...>버전을 수락하고 .Compile().Invoke(...)대리인이 필요한 경우 에만 적용 하십시오. 분명히 여기에는 비용이 있습니다.

6

NJection.LambdaConverter 는 대리자를 식으로 변환하는 라이브러리입니다.

public class Program
{
    private static void Main(string[] args) {
       var lambda = Lambda.TransformMethodTo<Func<string, int>>()
                          .From(() => Parse)
                          .ToLambda();            
    }   

    public static int Parse(string value) {
       return int.Parse(value)
    } 
}

4
 Expression<Func<T>> ToExpression<T>(Func<T> call)
        {
            MethodCallExpression methodCall = call.Target == null
                ? Expression.Call(call.Method)
                : Expression.Call(Expression.Constant(call.Target), call.Method);

            return Expression.Lambda<Func<T>>(methodCall);
        }

"작동하지 않을 것입니다"부분을 자세히 설명해 주시겠습니까? 실제로 컴파일하고 실행 해 보셨습니까? 아니면 귀하의 응용 프로그램에서 특히 작동하지 않습니까?
Dmitry Dzygin 2015 년

1
FWIW, 이것은 주요 티켓이 아닌 것일 수도 있지만 내가 필요한 것입니다. 그것은이었다 call.Target나를 죽이는 부분. 수년 동안 작동했지만 갑자기 작동을 멈추고 정적 / 비 정적 blah blah에 대해 불평하기 시작했습니다. 어쨌든 감사합니다!
Eli Gassert 2016


-1

변화

// This next line does not work...
Expression<Func<T>> DangerousExpression = dangerousCall;

// This next line works!
Expression<Func<T>> DangerousExpression = () => dangerousCall();

Servy, 그것은 표현을 얻는 절대적으로 합법적 인 방법입니다. expression.lambda 및 expression.call을 통해 구문 설탕을 작성합니다. 런타임에 실패해야하는 이유는 무엇입니까?
Roman Pokrovskij
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.