메서드 호출을 사용하면 람다에서 식으로 쉽게 이동할 수 있습니다.
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>
일반을 나타냅니다 delegate
. 그렇게 할 수있는 방법이 있다면 (최적화 및 컴파일러가 수행 한 기타 작업으로 인해 일부 데이터가 버려 질 수 있으므로 원래 표현식을 다시 가져 오는 것이 불가능할 수 있음) 즉석에서 IL을 디스 어셈블하는 것입니다. 그리고 표현을 추론하는 것 (이것이 결코 쉬운 일은 아닙니다). 람다 표현식을 데이터 ( Expression<Func<T>>
)로 처리하는 것은 컴파일러가 수행하는 마법입니다 (기본적으로 컴파일러는 IL로 컴파일하는 대신 코드에서 표현식 트리를 빌드합니다).
관련 사실
이것이 람다를 극한까지 밀어 붙이는 언어 (예 : Lisp)가 인터프리터 로 구현하기가 더 쉬운 이유 입니다. 이러한 언어에서 코드와 데이터는 본질적으로 동일 하지만 ( 런타임 에서도 ) 우리 칩은 코드의 형태를 이해할 수 없기 때문에이를 이해하는 인터프리터를 구축하여 그러한 기계를 에뮬레이션해야합니다. 언어와 같은 Lisp에 의해 선택) 또는 어느 정도 (코드가 더 이상 데이터와 정확히 동일하지 않음) 전력을 희생 (C #에 의해 선택)됩니다. C #에서 컴파일러는 람다가 컴파일 타임에 코드 ( Func<T>
) 및 데이터 ( Expression<Func<T>>
) 로 해석 될 수 있도록하여 코드를 데이터로 취급하는 환상을 제공합니다 .
답변
private static Expression<Func<T, bool>> FuncToExpression<T>(Func<T, bool> f)
{
return x => f(x);
}
답변
아마도해야 할 일은 방법을 바꾸는 것입니다. 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());
}
분명히 이것의 성능에 미치는 영향을 고려하고 이것이 실제로 수행해야하는 작업인지 결정해야합니다.
답변
그러나 .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());
}
답변
때때로 표현식이 필요하고 때로는 델리게이트가 필요한 경우 다음 두 가지 옵션이 있습니다.
- 다른 방법 (각각 1 개)
- 항상
Expression<...>
버전을 수락하고.Compile().Invoke(...)
대리인이 필요한 경우 에만 적용 하십시오. 분명히 여기에는 비용이 있습니다.
답변
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)
}
}
답변
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);
}