mengonversi .net Func <T> menjadi .net Expression <Func <T>>

118

Beralih dari lambda ke Expression mudah menggunakan metode panggilan ...

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

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

Tapi saya ingin mengubah Func menjadi ekspresi, hanya dalam kasus yang jarang terjadi ...

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());
}

Garis yang tidak berfungsi memberi saya kesalahan waktu kompilasi Cannot implicitly convert type 'System.Func<T>' to 'System.Linq.Expressions.Expression<System.Func<T>>'. Pemeran eksplisit tidak menyelesaikan situasi. Apakah ada fasilitas untuk melakukan ini yang saya abaikan?

Dave Cameron
sumber
Saya tidak terlalu melihat banyak kegunaan dari contoh 'kasus langka'. Pemanggil melewati Func <T>. Tidak perlu mengulang kembali ke pemanggil seperti apa Func <T> itu (melalui pengecualian).
Adam Ralph
2
Pengecualian tidak ditangani di pemanggil. Dan, karena ada beberapa situs panggilan yang meneruskan <T> Func yang berbeda, menangkap pengecualian di pemanggil akan membuat duplikasi.
Dave Cameron
1
Pelacakan tumpukan pengecualian dirancang untuk menampilkan informasi ini. Jika pengecualian dilempar dalam pemanggilan Func <T>, ini akan ditampilkan di pelacakan tumpukan. Kebetulan, jika Anda memilih untuk pergi ke arah lain, yaitu menerima ekspresi dan mengkompilasinya untuk pemanggilan, Anda akan kehilangan ini karena pelacakan tumpukan akan menunjukkan sesuatu seperti at lambda_method(Closure )untuk pemanggilan delegasi yang dikompilasi.
Adam Ralph
Saya kira Anda harus melihat jawabannya di [tautan] [1] [1] ini: stackoverflow.com/questions/9377635/create-expression-from-func/…
Ibrahim Kais Ibrahim

Jawaban:

104

Ooh, itu tidak mudah sama sekali. Func<T>mewakili delegateekspresi generik dan bukan. Jika ada cara untuk melakukannya (karena pengoptimalan dan hal lain yang dilakukan oleh compiler, beberapa data mungkin dibuang, jadi mungkin tidak mungkin untuk mendapatkan kembali ekspresi aslinya), itu akan membongkar IL dengan cepat dan menyimpulkan ungkapan (yang sama sekali tidak mudah). Memperlakukan ekspresi lambda sebagai data ( Expression<Func<T>>) adalah keajaiban yang dilakukan oleh kompilator (pada dasarnya kompilator membangun pohon ekspresi dalam kode alih-alih mengkompilasinya ke IL).

Fakta terkait

Inilah sebabnya mengapa bahasa yang mendorong lambda hingga ekstrem (seperti Lisp) seringkali lebih mudah diimplementasikan sebagai penerjemah . Dalam bahasa tersebut, kode dan data pada dasarnya adalah hal yang sama (bahkan saat dijalankan ), tetapi chip kami tidak dapat memahami bentuk kode tersebut, jadi kami harus meniru mesin seperti itu dengan membangun interpreter di atasnya yang memahaminya ( pilihan yang dibuat oleh Lisp seperti bahasa) atau mengorbankan kekuatan (kode tidak akan lagi persis sama dengan data) sampai batas tertentu (pilihan dibuat oleh C #). Dalam C #, kompilator memberikan ilusi memperlakukan kode sebagai data dengan mengizinkan lambda diinterpretasikan sebagai kode ( Func<T>) dan data ( Expression<Func<T>>) pada waktu kompilasi .

Mehrdad Afshari
sumber
3
Lisp tidak harus diinterpretasikan, ia dapat dengan mudah dikompilasi. Makro harus diperluas pada waktu kompilasi, dan jika Anda ingin mendukung, evalAnda perlu memulai kompilator, tetapi selain itu, tidak ada masalah sama sekali untuk melakukan itu.
konfigurator
2
"Ekspresi <Func <T>> DangerousExpression = () => hazardCall ();" tidak mudah?
mheyman
10
@mheyman Itu akan membuat baru Expressiontentang tindakan pembungkus Anda, tetapi itu tidak akan memiliki info pohon ekspresi tentang internal dangerousCalldelegasi.
Nenad
34
    private static Expression<Func<T, bool>> FuncToExpression<T>(Func<T, bool> f)  
    {  
        return x => f(x);  
    } 
Mengesampingkan
sumber
1
Saya ingin melintasi pohon sintaks dari ekspresi yang dikembalikan. Apakah pendekatan ini memungkinkan saya melakukan itu?
Dave Cameron
6
@DaveCameron - Tidak. Lihat jawaban di atas - yang sudah dikompilasi Funcakan disembunyikan di Expression baru. Ini hanya menambahkan satu lapisan data di atas kode; Anda dapat melintasi satu lapisan hanya untuk menemukan parameter Anda ftanpa detail lebih lanjut, jadi Anda tepat di tempat Anda memulai.
Jonno
21

Apa yang mungkin harus Anda lakukan adalah membalikkan metode. Ambil Expression>, lalu kompilasi dan jalankan. Jika gagal, Anda sudah memiliki Expression untuk diperiksa.

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());
}

Jelas Anda perlu mempertimbangkan implikasi kinerja dari ini, dan menentukan apakah itu sesuatu yang benar-benar perlu Anda lakukan.

David Wengier
sumber
7

Anda bisa pergi ke arah lain melalui metode .Compile () - tidak yakin apakah ini berguna untuk Anda:

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());
}
Steve Willcock
sumber
6

Jika terkadang Anda membutuhkan ekspresi dan terkadang membutuhkan delegasi, Anda memiliki 2 opsi:

  • memiliki metode yang berbeda (masing-masing 1)
  • selalu terima Expression<...>versinya, dan hanya .Compile().Invoke(...)jika Anda ingin delegasi. Jelas ini membutuhkan biaya.
Marc Gravell
sumber
6

NJection.LambdaConverter adalah pustaka yang mengubah delegasi menjadi ekspresi

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)
    } 
}
Sagi
sumber
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
sumber
Bisakah Anda menjelaskan bagian "ini tidak akan berhasil"? Sudahkah Anda benar-benar mencoba menyusun dan menjalankannya? Atau tidak berfungsi secara khusus dalam aplikasi Anda?
Dmitry Dzygin
1
FWIW, ini mungkin bukan tentang tiket utamanya, tapi itu yang saya butuhkan. Itu adalah call.Targetbagian yang membunuh saya. Ini bekerja selama bertahun-tahun, dan kemudian tiba-tiba berhenti bekerja dan mulai mengeluh tentang bla bla statis / non-statis. Terima kasih!
Eli Gassert
-1

Perubahan

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

Untuk

// This next line works!
Expression<Func<T>> DangerousExpression = () => dangerousCall();
mheyman
sumber
Pelayanan, itu cara yang benar-benar legal untuk berekspresi. sintaks gula untuk membangunnya melalui ekspresi.lambda dan ekspresi.call. Menurut Anda mengapa harus gagal saat runtime?
Roman Pokrovskij