メソッド呼び出しを使用すると、ラムダから式に簡単に移動できます...
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>>
)はcompiler(基本的にコンパイラはILにコンパイルする代わりにコードで式ツリーを構築します)によって行われる魔法です。
これが、ラムダを極端にプッシュする言語(LISPなど)がinterpretersとして実装するのが簡単な理由です。これらの言語では、コードとデータは本質的に同じものです(run timeでも)、チップはその形式のコードを理解できないため、その上にそれを理解するインタプリタを構築することによってそのようなマシンをエミュレートする(言語のようなLISPによって行われた選択)か、ある程度(C#によって行われた選択) 。 C#では、コンパイラは、ラムダをcode(Func<T>
)およびdata(Expression<Func<T>>
)atコンパイル時間。
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());
}
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)
}
}
式が必要な場合やデリゲートが必要な場合は、2つのオプションがあります。
Expression<...>
_バージョンを受け入れ、デリゲートが必要な場合は.Compile().Invoke(...)
だけを受け入れます。これには明らかにコストがかかります。 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);
}
Cecil MonoチームのJB Evainは、これを可能にするためにいくつかの進歩を遂げています
http://evain.net/blog/articles/2009/04/22/converting-delegates-to-expression-trees