ฉันเข้าใจ lambdas Func
และAction
ผู้ได้รับมอบหมาย แต่การแสดงออกตอฉัน
คุณจะใช้Expression<Func<T>>
แบบเก่ามากกว่าแบบธรรมดาในสถานการณ์Func<T>
ใด
ฉันเข้าใจ lambdas Func
และAction
ผู้ได้รับมอบหมาย แต่การแสดงออกตอฉัน
คุณจะใช้Expression<Func<T>>
แบบเก่ามากกว่าแบบธรรมดาในสถานการณ์Func<T>
ใด
คำตอบ:
เมื่อคุณต้องการรักษาแลมบ์ดานิพจน์เป็นต้นไม้นิพจน์และดูข้างในพวกเขาแทนการดำเนินการเหล่านั้น ตัวอย่างเช่น LINQ ไปยัง SQL รับนิพจน์และแปลงเป็นคำสั่ง SQL ที่เทียบเท่าและส่งไปยังเซิร์ฟเวอร์ (แทนที่จะเรียกใช้แลมบ์ดา)
แนวคิดExpression<Func<T>>
คือสมบูรณ์แตกต่างFunc<T>
จาก Func<T>
หมายถึง a delegate
ซึ่งเป็นตัวชี้ไปยังเมธอดและExpression<Func<T>>
แสดงโครงสร้างข้อมูลทรีสำหรับนิพจน์แลมบ์ดา โครงสร้างต้นไม้นี้อธิบายถึงสิ่งที่แลมบ์ดาแสดงออกแทนที่จะทำสิ่งที่เกิดขึ้นจริง มันเก็บข้อมูลเกี่ยวกับองค์ประกอบของการแสดงออกตัวแปรการเรียกใช้เมธอด ... (ตัวอย่างเช่นมันเก็บข้อมูลเช่นแลมบ์ดานี้มีค่าคงที่ + พารามิเตอร์บางตัว) คุณสามารถใช้คำอธิบายนี้เพื่อแปลงเป็นวิธีการจริง (พร้อมExpression.Compile
) หรือทำสิ่งอื่น ๆ (เช่นตัวอย่าง LINQ เป็น SQL) ด้วย การกระทำของการรักษาลูกแกะเป็นวิธีการไม่ระบุชื่อและต้นไม้แสดงออกเป็นเพียงเวลารวบรวม
Func<int> myFunc = () => 10; // similar to: int myAnonMethod() { return 10; }
จะรวบรวมอย่างมีประสิทธิภาพกับวิธีการ IL ที่ไม่ได้อะไรเลยและส่งคืน 10
Expression<Func<int>> myExpression = () => 10;
จะถูกแปลงเป็นโครงสร้างข้อมูลที่อธิบายนิพจน์ที่ไม่ได้รับพารามิเตอร์และส่งคืนค่า 10:
ในขณะที่พวกเขาทั้งสองมีลักษณะเดียวกันที่รวบรวมเวลาสิ่งที่คอมไพเลอร์สร้างเป็นที่แตกต่างกันโดยสิ้นเชิง
Expression
มีข้อมูลเมตาเกี่ยวกับผู้รับมอบสิทธิ์ที่แน่นอน
Expression<Func<...>>
Func<...>
(isAnExample) => { if(isAnExample) ok(); else expandAnswer(); }
นิพจน์ดังกล่าวเป็น ExpressionTree สาขาถูกสร้างขึ้นสำหรับคำสั่ง If
ฉันกำลังเพิ่มคำตอบสำหรับ noobs เพราะคำตอบเหล่านี้ปรากฏบนหัวของฉันจนกว่าฉันจะรู้ว่ามันง่ายแค่ไหน บางครั้งมันเป็นความคาดหวังของคุณว่ามีความซับซ้อนที่ทำให้คุณไม่สามารถ 'ห่อหัวของคุณไปรอบ ๆ มัน'
ฉันไม่จำเป็นต้องเข้าใจความแตกต่างจนกว่าฉันจะเข้าสู่ 'ข้อผิดพลาด' ที่น่ารำคาญจริงๆที่พยายามใช้ LINQ-to-SQL โดยทั่วไป:
public IEnumerable<T> Get(Func<T, bool> conditionLambda){
using(var db = new DbContext()){
return db.Set<T>.Where(conditionLambda);
}
}
สิ่งนี้ทำงานได้ดีจนกระทั่งฉันเริ่มรับ OutofMemoryExceptions บนชุดข้อมูลขนาดใหญ่ การตั้งค่าเบรกพอยต์ภายในแลมบ์ดาทำให้ฉันรู้ว่ามันซ้ำแล้วซ้ำอีกในแต่ละแถวในตารางของฉันทีละคนมองหาแมตช์กับสภาพแลมบ์ดาของฉัน สิ่งนี้ทำให้ฉันงงอยู่ครู่หนึ่งเพราะเหตุใด heck จึงปฏิบัติกับตารางข้อมูลของฉันในฐานะ IEnumerable ขนาดยักษ์แทนที่จะใช้ LINQ-to-SQL อย่างที่ควรจะเป็น มันก็ทำสิ่งเดียวกันใน LINQ-to-MongoDb ของฉันด้วย
การแก้ไขเป็นเพียงการเปลี่ยนFunc<T, bool>
เป็นExpression<Func<T, bool>>
ดังนั้นฉันจึง googled ทำไมมันต้องการExpression
แทนที่จะFunc
จบลงที่นี่
การแสดงออกเพียงเปลี่ยนผู้แทนเป็นข้อมูลเกี่ยวกับตัวเอง ดังนั้นa => a + 1
จะกลายเป็นสิ่งที่ชอบ "ที่ด้านซ้ายมีการint a
. ทางด้านขวาคุณเพิ่ม 1 ถึงมัน." แค่นั้นแหละ. คุณสามารถกลับบ้านได้แล้ว เห็นได้ชัดว่ามีโครงสร้างมากกว่านั้น แต่ที่จริงแล้วต้นไม้นิพจน์เป็นจริง - ไม่มีอะไรห่อหัวของคุณ
เมื่อเข้าใจว่าจะเห็นได้ชัดว่าทำไม LINQ-to-SQL จึงต้องมีExpression
และFunc
ไม่เพียงพอ Func
ไม่ได้มีวิธีในการเข้าไปในตัวเองเพื่อดู nitty-gritty ของวิธีการแปลเป็น SQL / MongoDb / แบบสอบถามอื่น ๆ คุณไม่สามารถดูได้ว่ามันกำลังทำการเพิ่มหรือการคูณหรือการลบ สิ่งที่คุณทำได้ก็คือเรียกใช้ Expression
ในทางกลับกันอนุญาตให้คุณมองเข้าไปข้างในผู้รับมอบสิทธิ์และดูทุกสิ่งที่ต้องการ สิ่งนี้ช่วยให้คุณสามารถแปลผู้รับมอบสิทธิ์เป็นสิ่งที่คุณต้องการเช่นแบบสอบถาม SQLFunc
ไม่ทำงานเพราะ DbContext ของฉันตาบอดกับเนื้อหาของการแสดงออกแลมบ์ดา ด้วยเหตุนี้จึงไม่สามารถแปลงแลมบ์ดาให้เป็น SQL ได้ อย่างไรก็ตามมันทำสิ่งที่ดีที่สุดถัดไปและทำซ้ำเงื่อนไขนั้นในแต่ละแถวในตารางของฉัน
แก้ไข: อธิบายประโยคสุดท้ายของฉันตามคำร้องขอของ John Peter:
IQueryable ขยาย IEnumerable ดังนั้นวิธีการ IEnumerable เหมือนได้รับการยอมรับว่าเกินWhere()
Expression
เมื่อคุณส่งผ่านExpression
ไปยังที่คุณเก็บ IQueryable เป็นผล แต่เมื่อคุณผ่านFunc
คุณจะล้มลงบนฐาน IEnumerable และคุณจะได้รับ IEnumerable เป็นผล กล่าวอีกนัยหนึ่งโดยไม่สังเกตว่าคุณเปลี่ยนชุดข้อมูลของคุณไปเป็นรายการให้ทำซ้ำซึ่งตรงข้ามกับบางสิ่งที่จะสืบค้น มันยากที่จะสังเกตเห็นความแตกต่างจนกว่าคุณจะเห็นภายใต้ประทุนที่ลายเซ็น
การพิจารณาที่สำคัญอย่างยิ่งในการเลือก Expression vs Func คือผู้ให้บริการ IQueryable เช่น LINQ to Entities สามารถ 'ย่อย' สิ่งที่คุณผ่านใน Expression แต่จะเพิกเฉยต่อสิ่งที่คุณส่งผ่าน Func ฉันมีบทความในบล็อกสองเรื่อง:
เพิ่มเติมเกี่ยวกับ Expression vs Func พร้อม Entity Frameworkและ หลงรัก LINQ - ตอนที่ 7: การแสดงออกและ Funcs (ส่วนสุดท้าย)
ฉันต้องการเพิ่มบันทึกเกี่ยวกับความแตกต่างระหว่างFunc<T>
และExpression<Func<T>>
:
Func<T>
เป็นเพียง MulticastDelegate โรงเรียนเก่า;Expression<Func<T>>
เป็นตัวแทนของแลมบ์ดาแสดงออกในรูปแบบของต้นไม้แสดงออก;Func<T>
;ExpressionVisitor
;Func<T>
;Expression<Func<T>>
ทำงานด้วยมีบทความที่อธิบายรายละเอียดพร้อมตัวอย่างรหัส:
LINQ: Func <T> กับนิพจน์ <Func <T>>เทียบกับการแสดงออก
หวังว่ามันจะเป็นประโยชน์
มีคำอธิบายเพิ่มเติมเกี่ยวกับปรัชญาจากหนังสือของ Krzysztof Cwalina ( แนวทางการออกแบบกรอบการทำงาน: อนุสัญญาสำนวนและรูปแบบสำหรับไลบรารี. NET ที่ใช้ซ้ำได้ );
แก้ไขสำหรับเวอร์ชั่นที่ไม่ใช่รูปภาพ:
เวลาส่วนใหญ่คุณจะต้องการFuncหรือActionหากสิ่งที่ต้องเกิดขึ้นคือการเรียกใช้โค้ด คุณต้องการนิพจน์เมื่อต้องวิเคราะห์วิเคราะห์ลำดับหรือปรับให้เหมาะสมก่อนที่จะรัน นิพจน์มีไว้สำหรับคิดเกี่ยวกับโค้ดFunc / Actionใช้สำหรับเรียกใช้
database.data.Where(i => i.Id > 0)
SELECT FROM [data] WHERE [id] > 0
หากคุณเพียงแค่ผ่านใน Func คุณได้ใส่ blinders กับไดรเวอร์ของคุณและทั้งหมดก็สามารถทำได้คือSELECT *
แล้วเมื่อมีการโหลดข้อมูลทั้งหมดที่เป็นหน่วยความจำย้ำผ่านแต่ละคนและกรองออกมาทุกอย่างที่มี ID> 0 ห่อของคุณFunc
ในการExpression
ให้อำนาจ โปรแกรมควบคุมเพื่อวิเคราะห์Func
และเปลี่ยนเป็นคิวรี Sql / MongoDb / อื่น ๆ
Expression
แต่เมื่อฉันอยู่ในช่วงวันหยุดมันจะเป็นFunc/Action
;)
LINQ เป็นตัวอย่างที่ยอมรับได้ (ตัวอย่างเช่นการพูดคุยกับฐานข้อมูล) แต่ตามความเป็นจริงทุกครั้งที่คุณสนใจเพิ่มเติมเกี่ยวกับการแสดงสิ่งที่ต้องทำแทนที่จะทำตามจริง ตัวอย่างเช่นฉันใช้วิธีนี้ในกอง RPC ของ protobuf-net (เพื่อหลีกเลี่ยงการสร้างรหัส ฯลฯ ) - ดังนั้นคุณจึงเรียกวิธีด้วย:
string result = client.Invoke(svc => svc.SomeMethod(arg1, arg2, ...));
สิ่งนี้แยกโครงสร้างต้นไม้นิพจน์เพื่อแก้ไขSomeMethod
(และค่าของแต่ละอาร์กิวเมนต์) ทำการเรียก RPC อัพเดตใด ๆref
/ out
args และส่งคืนผลลัพธ์จากการเรียกระยะไกล สิ่งนี้สามารถทำได้ผ่านทรีนิพจน์ ฉันครอบคลุมสิ่งนี้เพิ่มเติมได้ที่นี่ที่นี่
อีกตัวอย่างหนึ่งคือเมื่อคุณสร้างต้นไม้นิพจน์ด้วยตนเองเพื่อจุดประสงค์ในการรวบรวมแลมบ์ดาดังที่ทำโดยรหัสตัวดำเนินการทั่วไป
คุณจะใช้นิพจน์เมื่อคุณต้องการใช้ฟังก์ชันเป็นข้อมูลไม่ใช่รหัส คุณสามารถทำได้ถ้าคุณต้องการจัดการรหัส (เป็นข้อมูล) เวลาส่วนใหญ่ถ้าคุณไม่เห็นความต้องการนิพจน์คุณอาจไม่จำเป็นต้องใช้นิพจน์
เหตุผลหลักคือเมื่อคุณไม่ต้องการรันโค้ดโดยตรง แต่ต้องการตรวจสอบมัน สิ่งนี้สามารถทำได้ด้วยเหตุผลหลายประการ:
Expression
สามารถเป็นไปไม่ได้ที่จะทำให้เป็นอนุกรมในฐานะผู้รับมอบสิทธิ์เนื่องจากนิพจน์ใด ๆ สามารถมีการร้องขอของการอ้างอิงผู้แทน / วิธีการโดยพลการ "ง่าย" เป็นญาติแน่นอน
ฉันยังไม่เห็นคำตอบใด ๆ ที่กล่าวถึงประสิทธิภาพ ผ่านFunc<>
s เข้าไปWhere()
หรือCount()
ไม่ดี ไม่ดีจริง ถ้าคุณใช้ a Func<>
ก็จะเรียกIEnumerable
สิ่ง LINQ แทนIQueryable
ซึ่งหมายความว่าดึงตารางทั้งหมดแล้วกรอง Expression<Func<>>
เร็วกว่ามากโดยเฉพาะถ้าคุณทำการสืบค้นฐานข้อมูลที่ใช้งานเซิร์ฟเวอร์อื่น