web-dev-qa-db-ja.com

.net Func <T>を.net Expression <Func <T >>に変換する

メソッド呼び出しを使用すると、ラムダから式に簡単に移動できます...

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>>'。明示的なキャストでは状況は解決しません。私が見落としているこれを行う機能はありますか?

111
Dave Cameron

ああ、それはまったく簡単ではありません。 Func<T>は、式ではなく、汎用のdelegateを表します。可能な方法がある場合(コンパイラーによる最適化やその他の処理により、一部のデータが破棄される可能性があるため、元の式を取り戻すことができない場合があります)、その場でILを逆アセンブルします式を推測します(決して簡単ではありません)。ラムダ式をデータとして扱う(Expression<Func<T>>)はcompiler(基本的にコンパイラはILにコンパイルする代わりにコードで式ツリーを構築します)によって行われる魔法です。

関連する事実

これが、ラムダを極端にプッシュする言語(LISPなど)がinterpretersとして実装するのが簡単な理由です。これらの言語では、コードとデータは本質的に同じものです(run timeでも)、チップはその形式のコードを理解できないため、その上にそれを理解するインタプリタを構築することによってそのようなマシンをエミュレートする(言語のようなLISPによって行われた選択)か、ある程度(C#によって行われた選択) 。 C#では、コンパイラは、ラムダをcodeFunc<T>)およびdataExpression<Func<T>>)atコンパイル時間

98
Mehrdad Afshari
    private static Expression<Func<T, bool>> FuncToExpression<T>(Func<T, bool> f)  
    {  
        return x => f(x);  
    } 
27
Override

おそらくあなたがすべきことは、方法を変えることです。 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());
}

明らかに、このことのパフォーマンスへの影響を考慮し、それが本当に必要なことかどうかを判断する必要があります。

21
David Wengier

ただし、.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());
}
7
Steve Willcock

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)
    } 
}
5
Sagi

式が必要な場合やデリゲートが必要な場合は、2つのオプションがあります。

  • 異なる方法があります(それぞれに1つ)
  • 常に_Expression<...>_バージョンを受け入れ、デリゲートが必要な場合は.Compile().Invoke(...)だけを受け入れます。これには明らかにコストがかかります。
4
Marc Gravell
 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);
        }
4
Dmitry Dzygin

Cecil MonoチームのJB Evainは、これを可能にするためにいくつかの進歩を遂げています

http://evain.net/blog/articles/2009/04/22/converting-delegates-to-expression-trees

3
aaguiar