.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>を渡す呼び出しサイトが複数あるため、呼び出し元で例外をキャッチすると重複が発生します。
デイブキャメロン

1
例外スタックトレースは、この情報を表示するように設計されています。Func <T>の呼び出し内で例外がスローされた場合、これはスタックトレースに表示されます。ちなみに、別の方法、つまり式を受け入れてそれを呼び出し用にコンパイルすることを選択した場合、スタックトレースはat lambda_method(Closure )コンパイルされたデリゲートの呼び出しのようなものを表示するため、これは失われます。
アダムラルフ

この[リンク] [1] [1]で答えを確認する必要があると思います:stackoverflow.com/questions/9377635/create-expression-from-func/…–
Ibrahim Kais Ibrahim

回答:


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 2014

10
@mheymanこれはExpressionラッパーアクションについて新しく作成しますが、dangerousCallデリゲートの内部に関する式ツリー情報はありません。
Nenad 2014

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

1
返された式の構文ツリーをトラバースしたいと思いました。このアプローチは私にそれを可能にしますか?
Dave Cameron

6
@DaveCameron-いいえ。上記の回答を参照してください-すでにコンパイルFuncされているものは新しい式で非表示になります。これは単にコード上に1つのデータ層を追加するだけです。f詳細を確認せずにパラメーターを見つけるために1つのレイヤーをトラバースすることができるので、最初からやり直すことができます。
ジョンノ

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

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

  • 異なる方法があります(それぞれに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私を殺していた部分でした。それは何年にもわたって機能し、その後突然動作を停止し、静的/非静的な何とか何とか何とか不平を言い始めました。まあありがとう!
Eli Gassert 2016年


-1

変化する

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

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

サーヴィ、それは表現を得るための絶対的に合法な方法として。expression.lambdaとexpression.callでビルドするための構文シュガー。実行時に失敗するのはなぜだと思いますか?
Roman Pokrovskij 2017年
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.