การแปลง. net Func <T> เป็น. net Expression <Func <T>>


118

การเปลี่ยนจาก lambda เป็น Expression ทำได้ง่ายโดยใช้วิธีการเรียก ...

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>>'สายที่ไม่ได้ทำงานให้ฉันรวบรวมข้อผิดพลาดเวลา การแคสต์ที่ชัดเจนไม่สามารถแก้ไขสถานการณ์ได้ มีสิ่งอำนวยความสะดวกในการทำสิ่งนี้ที่ฉันมองเห็นหรือไม่?


ฉันไม่ค่อยเห็นการใช้ประโยชน์มากนักสำหรับตัวอย่าง 'rare case' ผู้โทรกำลังส่งผ่านใน Func <T> ไม่จำเป็นต้องย้อนกลับไปหาผู้โทรว่า Func <T> คืออะไร (ผ่านข้อยกเว้น)
Adam Ralph

2
ไม่มีการจัดการข้อยกเว้นในผู้โทร และเนื่องจากมีไซต์การโทรหลายไซต์ที่ส่งผ่าน Func <T> s ที่แตกต่างกันการจับข้อยกเว้นในตัวเรียกจึงสร้างการทำซ้ำ
Dave Cameron

1
การติดตามสแต็กข้อยกเว้นถูกออกแบบมาเพื่อแสดงข้อมูลนี้ หากข้อยกเว้นถูกทิ้งภายในการเรียกใช้ Func <T> สิ่งนี้จะแสดงในการติดตามสแต็ก อนึ่งถ้าคุณเลือกที่จะไปทางอื่นเช่นยอมรับนิพจน์และรวบรวมสำหรับการเรียกใช้คุณจะสูญเสียสิ่งนี้เนื่องจากการติดตามสแต็กจะแสดงสิ่งที่ต้องการat lambda_method(Closure )สำหรับการเรียกใช้ตัวแทนที่คอมไพล์
Adam Ralph

ฉันเดาว่าคุณควรดูคำตอบใน [ลิงค์] [1] [1]: stackoverflow.com/questions/9377635/create-expression-from-func/…
อิบราฮิมไคส์อิบราฮิม

คำตอบ:


104

โอมันไม่ง่ายเลย Func<T>แทนdelegateนิพจน์ทั่วไปไม่ใช่นิพจน์ หากมีวิธีใดที่คุณสามารถทำได้ (เนื่องจากการปรับให้เหมาะสมและสิ่งอื่น ๆ ที่ทำโดยคอมไพเลอร์ข้อมูลบางอย่างอาจถูกโยนทิ้งไปดังนั้นจึงเป็นไปไม่ได้ที่จะดึงนิพจน์ดั้งเดิมกลับมา) มันจะเป็นการแยกส่วน IL ออกทันที และอนุมานนิพจน์ (ซึ่งไม่ง่ายเลย) การจัดการนิพจน์แลมบ์ดาเป็น data ( Expression<Func<T>>) เป็นเวทมนตร์ที่ทำโดยคอมไพเลอร์ (โดยทั่วไปคอมไพเลอร์จะสร้างแผนภูมินิพจน์ในโค้ดแทนที่จะคอมไพล์เป็น IL)

ข้อเท็จจริงที่เกี่ยวข้อง

นี่คือเหตุผลที่ภาษาที่จะผลักดัน lambdas ไปมาก (เช่นเสียงกระเพื่อม) มักจะง่ายต่อการใช้เป็นล่าม ในภาษาเหล่านั้นรหัสและข้อมูลเป็นสิ่งเดียวกัน (แม้ในขณะรันไทม์ ) แต่ชิปของเราไม่สามารถเข้าใจรูปแบบของรหัสนั้นได้ดังนั้นเราจึงต้องเลียนแบบเครื่องดังกล่าวโดยการสร้างล่ามที่ด้านบนซึ่งเข้าใจได้ ( ตัวเลือกที่ทำโดย Lisp เช่นภาษา) หรือการเสียสละอำนาจ (รหัสจะไม่เท่ากับข้อมูลอีกต่อไป) ในระดับหนึ่ง (ตัวเลือกที่ทำโดย C #) ใน C # คอมไพเลอร์ให้ภาพลวงตาของการรักษารหัสเป็นข้อมูลโดยการอนุญาตให้ lambdas ที่จะตีความว่าเป็นรหัส ( Func<T>) และข้อมูล ( Expression<Func<T>>) ที่รวบรวมเวลา


3
เสียงกระเพื่อมไม่ต้องตีความก็สามารถเรียบเรียงได้อย่างง่ายดาย มาโครจะต้องขยายในเวลาคอมไพล์และหากคุณต้องการสนับสนุนevalคุณจะต้องเริ่มต้นคอมไพเลอร์ แต่นอกเหนือจากนั้นก็ไม่มีปัญหาในการทำเช่นนั้น
กำหนดค่า

2
"นิพจน์ <Func <T>> DangerousExpression = () = >angerCall ();" ไม่ง่าย?
mheyman

10
@mheyman นั่นจะสร้างใหม่Expressionเกี่ยวกับการกระทำของ Wrapper ของคุณ แต่จะไม่มีข้อมูลโครงสร้างนิพจน์เกี่ยวกับภายในของdangerousCallผู้รับมอบสิทธิ์
เณรแอด

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

1
ฉันต้องการสำรวจโครงสร้างไวยากรณ์ของนิพจน์ที่ส่งคืน วิธีนี้จะช่วยให้ฉันทำเช่นนั้นได้หรือไม่?
Dave Cameron

6
@DaveCameron - ไม่ดูคำตอบด้านบน - คอมไพล์แล้วFuncจะซ่อนอยู่ในนิพจน์ใหม่ นี่เป็นการเพิ่มข้อมูลหนึ่งชั้นบนโค้ด คุณสามารถสำรวจเลเยอร์เดียวเพื่อค้นหาพารามิเตอร์ของคุณfโดยไม่มีรายละเอียดเพิ่มเติมดังนั้นคุณจึงมาถูกที่แล้ว
Jonno

21

สิ่งที่คุณควรทำคือเปลี่ยนวิธีการ ใช้ Expression> และรวบรวมและเรียกใช้ หากล้มเหลวแสดงว่าคุณมี 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

1
FWIW นี่อาจไม่ใช่สิ่งที่ตั๋วหลักเกี่ยวกับ แต่มันคือสิ่งที่ฉันต้องการ มันเป็นcall.Targetส่วนหนึ่งที่กำลังฆ่าฉัน ทำงานมาหลายปีแล้วก็หยุดทำงานทันทีและเริ่มบ่นเกี่ยวกับ blah blah แบบคงที่ / ไม่คงที่ ยังไงก็ขอบคุณ!
Eli Gassert


-1

เปลี่ยนแปลง

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

ถึง

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

Servy เป็นวิธีที่ถูกกฎหมายในการแสดงออก ไวยากรณ์น้ำตาลเพื่อสร้างแม้ว่า expression.lambda และ expression.call ทำไมคุณถึงคิดว่ามันน่าจะล้มเหลวในขณะรันไทม์?
Roman Pokrovskij
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.