การตั้งค่าสไตล์ LINQ [ปิด]


21

ฉันมาใช้ LINQ ในการเขียนโปรแกรมทุกวันเป็นจำนวนมาก ในความเป็นจริงฉันไม่ค่อยได้ใช้ลูปที่ชัดเจน ฉันมี แต่พบว่าฉันไม่ได้ใช้ SQL เช่นไวยากรณ์อีกต่อไป ฉันใช้ฟังก์ชั่นเสริม ดังนั้นแทนที่จะพูดว่า:

from x in y select datatransform where filter 

ฉันใช้:

x.Where(c => filter).Select(c => datatransform)

คุณชอบสไตล์ของ LINQ แบบไหนและคนอื่นในทีมของคุณพอใจกับอะไรบ้าง?


5
อาจเป็นที่น่าสังเกตว่าท่าทางของ MS อย่างเป็นทางการคือไวยากรณ์ของคิวรี่นั้นดีกว่า
R0MANARMY

1
ในที่สุดมันก็ไม่สำคัญ สิ่งที่สำคัญคือรหัสนั้นเป็นที่เข้าใจได้ แบบฟอร์มหนึ่งอาจดีกว่าในกรณีหนึ่งกรณีอื่น ๆ ในอีกกรณีหนึ่ง ดังนั้นการใช้งานที่เคยเหมาะสมในเวลา
ChrisF

ฉันเชื่อว่าตัวอย่างที่สองของคุณเรียกว่า lambda syntax ซึ่งฉันใช้ 95% ของเวลา อีก 5% ที่ฉันใช้ไวยากรณ์แบบสอบถามซึ่งเมื่อฉันกำลังทำร่วมฉันพยายามที่จะเปลี่ยนไปใช้การรวมไวยากรณ์แลมบ์ดา แต่เหมือนคนอื่น ๆ ได้ชี้ให้เห็นว่ามันยุ่งเหยิง
มัฟฟินชาย

คำตอบ:


26

ฉันพบว่าโชคไม่ดีที่ท่าทางของ Microsoft ต่อเอกสารคู่มือ MSDN คือไวยากรณ์ของแบบสอบถามนั้นดีกว่าเพราะฉันไม่เคยใช้มัน แต่ฉันใช้ไวยากรณ์ของวิธี LINQ ตลอดเวลา ฉันชอบความสามารถในการดับการสืบค้นหนึ่งซับไปที่เนื้อหาหัวใจของฉัน เปรียบเทียบ:

var products = from p in Products
               where p.StockOnHand == 0
               select p;

ไปที่:

var products = Products.Where(p => p.StockOnHand == 0);

เส้นที่สั้นลงเร็วขึ้นและต่อดวงตาของฉันดูสะอาดตา ไวยากรณ์การสืบค้นไม่รองรับตัวดำเนินการ LINQ มาตรฐานทั้งหมด ตัวอย่างแบบสอบถามที่ฉันทำเมื่อเร็ว ๆ นี้มีลักษณะเช่นนี้:

var itemInfo = InventoryItems
    .Where(r => r.ItemInfo is GeneralMerchInfo)
    .Select(r => r.ItemInfo)
    .Cast<GeneralMerchInfo>()
    .FirstOrDefault(r => r.Xref == xref);

ความรู้ของฉันในการทำซ้ำแบบสอบถามนี้โดยใช้ไวยากรณ์แบบสอบถาม (เท่าที่เป็นไปได้) มันจะมีลักษณะเช่นนี้:

var itemInfo = (from r in InventoryItems
                where r.ItemInfo is GeneralMerchInfo
                select r.ItemInfo)
                .Cast<GeneralMerchInfo>()
                .FirstOrDefault(r => r.Xref == xref);

อย่ามองฉันมากขึ้นและคุณต้องรู้วิธีใช้ไวยากรณ์ของเมธอดอยู่ดี โดยส่วนตัวแล้วฉันหลงใหลในสไตล์การประกาศของ LINQ มากที่สุดและใช้มันในทุกสถานการณ์ที่เป็นไปได้ ตรงประเด็นด้วยไวยากรณ์ของวิธีการฉันสามารถทำสิ่งนี้:

// projects an InventoryItem collection with total stock on hand for each GSItem
inventoryItems = repository.GSItems
    .Select(gsItem => new InventoryItem() {
        GSItem = gsItem,
        StockOnHand = repository.InventoryItems
            .Where(inventoryItem => inventoryItem.GSItem.GSNumber == gsItem.GSNumber)
            .Sum(r => r.StockOnHand)
     });

ฉันคิดว่าโค้ดข้างต้นนั้นยากที่จะเข้าใจสำหรับคนที่เข้ามาในโครงการโดยไม่มีเอกสารประกอบที่ดีและหากพวกเขาไม่มีพื้นหลังที่มั่นคงใน LINQ พวกเขาอาจไม่เข้าใจมันอยู่ดี ยังคงไวยากรณ์ของวิธีการเปิดเผยความสามารถที่มีประสิทธิภาพบางอย่างที่รวดเร็ว (ในแง่ของบรรทัดของรหัส) โครงการแบบสอบถามเพื่อรับข้อมูลรวมเกี่ยวกับคอลเลกชันต่างๆที่จะใช้จำนวนมากที่น่าเบื่อ foreach ลูป ในกรณีเช่นนี้ไวยากรณ์ของเมธอดนั้นกะทัดรัดเป็นพิเศษสำหรับสิ่งที่คุณได้รับจากมัน ความพยายามในการทำเช่นนี้กับไวยากรณ์ของคิวรีอาจจะค่อนข้างยากลำบาก


นักแสดงที่คุณสามารถทำได้ภายในตัวเลือก แต่น่าเสียดายที่คุณไม่สามารถระบุให้ใช้ระเบียน X อันดับสูงสุดโดยไม่ต้องใช้วิธี LINQ นี่เป็นเรื่องน่ารำคาญเป็นพิเศษในสถานที่ที่คุณรู้ว่าคุณต้องการเพียงบันทึกเดียวและต้องใส่แบบสอบถามทั้งหมดไว้ในวงเล็บ
Ziv

2
เพียงบันทึกคุณสามารถเลือก (x => x.ItemInfo) .OfType <GeneralMerchInfo> () แทน Where () เลือก () เลือก <) Cast <> () ซึ่งฉันเชื่อว่าเร็วกว่า (ใหญ่ O ของ 2n ฉันคิดว่าแทน n * 2m) แต่คุณพูดถูกแลัวไวยากรณ์แลมบ์ดานั้นดีกว่ามากจากมุมมองที่อ่านได้
Ed James

16

ฉันพบว่ารูปแบบการทำงานนั้นน่าพึงใจยิ่งขึ้น ข้อยกเว้นเพียงอย่างเดียวคือถ้าฉันต้องการเข้าร่วมมากกว่าสองชุด The Join () บ้าเร็วมาก


ตกลง ... ฉันชอบรูปลักษณ์และความสามารถในการอ่านจากวิธีการขยายยกเว้น (ตามที่กล่าวไว้) เมื่อเข้าร่วม ผู้ขายชิ้นส่วน (เช่น Telerik) ใช้วิธีการขยายอย่างมาก ตัวอย่างที่ฉันคิดคือ Rad Controls ใน ASP.NET MVC คุณต้องมีความเชี่ยวชาญเป็นอย่างมากโดยใช้วิธีการขยายเพื่อใช้ / อ่าน
Catchops

มาพูดแบบนี้ ฉันมักจะใช้ lambdas เว้นแต่ว่ามีผู้เข้าร่วมที่เกี่ยวข้อง เมื่อมีการเข้าร่วมไวยากรณ์ LINQ มีแนวโน้มที่จะอ่านง่ายขึ้น
ฌอน

10

สายเกินไปที่จะเพิ่มคำตอบอีกหรือไม่

ฉันเขียนโค้ด LINQ-to-object จำนวนมากและฉันยืนยันว่าอย่างน้อยในโดเมนนั้นที่ดีที่จะเข้าใจไวยากรณ์ทั้งสองเพื่อที่จะใช้สิ่งใดก็ตามที่ทำให้โค้ดง่ายขึ้น - ซึ่งไม่ใช่จุดแบบไวยากรณ์เสมอไป

แน่นอนว่ามีบางครั้งที่ dot-syntax IS เป็นทางไป - คนอื่น ๆ ได้ให้หลายกรณี อย่างไรก็ตามฉันคิดว่าความเข้าใจมีการเปลี่ยนแปลงสั้น ๆ - ถ้าคุณจะแร็พ ดังนั้นฉันจะให้ตัวอย่างที่ฉันเชื่อว่าความเข้าใจมีประโยชน์

ต่อไปนี้เป็นวิธีแก้ปริศนาการแทนที่ตัวเลข: (โซลูชันที่เขียนโดยใช้ LINQPad แต่สามารถทำงานแบบสแตนด์อโลนในแอปคอนโซล)

// NO
// NO
// NO
//+NO
//===
// OK

var solutions =
    from O in Enumerable.Range(1, 8) // 1-9
                    //.AsQueryable()
    from N in Enumerable.Range(1, 8) // 1-9
    where O != N
    let NO = 10 * N + O
    let product = 4 * NO
    where product < 100
    let K = product % 10
    where K != O && K != N && product / 10 == O
    select new { N, O, K };

foreach(var i in solutions)
{
    Console.WriteLine("N = {0}, O = {1}, K = {2}", i.N, i.O, i.K);
}

//Console.WriteLine("\nsolution expression tree\n" + solutions.Expression);

... ผลลัพธ์ใด:

N = 1, O = 6, K = 4

ไม่เลวร้ายเกินไปตรรกะไหลเป็นเส้นตรงและเราจะเห็นว่ามันเกิดขึ้นด้วยวิธีการแก้ไขที่ถูกต้องเพียงครั้งเดียว ปริศนานี้ง่ายต่อการแก้ด้วยมือ: ให้เหตุผลว่า 3>> N0 และO> 4 * N บอกเป็นนัย 8> = O> = 4 นั่นหมายความว่ามี 10 กรณีทดสอบด้วยมือสูงสุด (2 สำหรับN-by- 5 สำหรับO) ฉันหลงทางมากพอ - ปริศนานี้มีให้เพื่อใช้เป็นภาพประกอบของ LINQ

การแปลงคอมไพเลอร์

มีหลายคอมไพเลอร์ทำเพื่อแปลสิ่งนี้เป็น dot-syntax ที่เทียบเท่ากัน นอกจากนี้ตามปกติสองและต่อมาfromคำสั่งได้รับกลายเป็นSelectManyสายที่เรามีletข้อที่จะกลายเป็นSelectสายที่มีการคาดการณ์ซึ่งทั้งสองใช้โปร่งใสตัวระบุ ในขณะที่ฉันกำลังจะแสดงการต้องตั้งชื่อตัวระบุเหล่านี้ในรูปแบบของจุดจะห่างจากความสามารถในการอ่านของวิธีการนั้น

ฉันมีเคล็ดลับสำหรับการเปิดเผยสิ่งที่คอมไพเลอร์ทำในการแปลรหัสนี้เป็นรูปแบบจุด หากคุณไม่ใส่เครื่องหมายข้อคิดเห็นทั้งสองบรรทัดด้านบนและเรียกใช้อีกครั้งคุณจะได้รับผลลัพธ์ต่อไปนี้:

N = 1, O = 6, K = 4

แผนผังนิพจน์โซลูชัน System.Linq.Enumerable + d_ b8.SelectMany (O => ช่วง (1, 8), (O, N) => ใหม่ <> f _AnonymousType0 2(O = O, N = N)).Where(<>h__TransparentIdentifier0 => (<>h__TransparentIdentifier0.O != <>h__TransparentIdentifier0.N)).Select(<>h__TransparentIdentifier0 => new <>f__AnonymousType12 (<> h_ TransparentIdentifier0 = NO > h _TransparentIdentifier0, NO = ((10 * <> h_ TransparentIdentifier0.N) + <> h _TransparentIdentifier0.O))) เลือก (<> h_ TransparentIdentifier1 => ใหม่ <> f _AnonymousType2 2(<>h__TransparentIdentifier1 = <>h__TransparentIdentifier1, product = (4 * <>h__TransparentIdentifier1.NO))).Where(<>h__TransparentIdentifier2 => (<>h__TransparentIdentifier2.product < 100)).Select(<>h__TransparentIdentifier2 => new <>f__AnonymousType32 (<> h_TransparentIdentifier2 = <> h _TransparentIdentifier2) <> h_ TransparentIdentifier2.product% 10))) โดยที่ (<> h _TransparentIdentifier3 => (((<> h_ TransparentIdentifier3.K! = <> h _TransparentIdentifier3. <> h_ TransparentIdentifier2h _TransparentIdentifier1. <> h_TransparentIdentifier0.O) andalso (<> H _TransparentIdentifier3.K! = <> h_ TransparentIdentifier3. <> H _TransparentIdentifier2. <> h_ TransparentIdentifier1. <> H _TransparentIdentifier0.N)) andalso ((<> h_ TransparentIdentifier3. <> H _TransparentIdentifier2 ผลิตภัณฑ์ / 10) == <> h_ TransparentIdentifier3. <> h _TransparentIdentifier2. <> h_ TransparentIdentifier1. <> h _TransparentIdentifier0.O)) เลือก (<> h_ TransparentIdentifier3 => ใหม่ <> f _AnonymousTypeifier4 > h_ TransparentIdentifier3. <> h _TransparentIdentifier2. <> h_ TransparentIdentifier1. <> h _TransparentIdentifier0.N,O = <> h_ TransparentIdentifier3. <> h_TransparentIdentifier2. <> h_ TransparentIdentifier1. <> h _TransparentIdentifier0.O, K = <> h__TransparentIdentifier3.K))

การใส่ตัวดำเนินการ LINQ แต่ละรายการในบรรทัดใหม่แปลตัวระบุ "ไม่สามารถบรรยายได้" เป็นตัวที่เราสามารถ "พูด" เปลี่ยนประเภทนิรนามให้อยู่ในรูปแบบที่คุ้นเคยและเปลี่ยนAndAlsoนิพจน์ต้นไม้ - ต้นไม้ศัพท์แสงเพื่อ&&แสดงการเปลี่ยนแปลงที่คอมไพเลอร์ ในรูปแบบจุด:

var solutions = 
    Enumerable.Range(1,8) // from O in Enumerable.Range(1,8)
        .SelectMany(O => Enumerable.Range(1, 8), (O, N) => new { O = O, N = N }) // from N in Enumerable.Range(1,8)
        .Where(temp0 => temp0.O != temp0.N) // where O != N
        .Select(temp0 => new { temp0 = temp0, NO = 10 * temp0.N + temp0.O }) // let NO = 10 * N + O
        .Select(temp1 => new { temp1 = temp1, product = 4 * temp1.NO }) // let product = 4 * NO
        .Where(temp2 => temp2.product < 100) // where product < 100
        .Select(temp2 => new { temp2 = temp2, K = temp2.product % 10 }) // let K = product % 10
        .Where(temp3 => temp3.K != temp3.temp2.temp1.temp0.O && temp3.K != temp3.temp2.temp1.temp0.N && temp3.temp2.product / 10 == temp3.temp2.temp1.temp0.O)
        // where K != O && K != N && product / 10 == O
        .Select(temp3 => new { N = temp3.temp2.temp1.temp0.N, O = temp3.temp2.temp1.temp0.O, K = temp3.K });
        // select new { N, O, K };

foreach(var i in solutions)
{
    Console.WriteLine("N = {0}, O = {1}, K = {2}", i.N, i.O, i.K);
}

ซึ่งถ้าคุณเรียกใช้คุณสามารถตรวจสอบว่ามันออกมาอีกครั้ง:

N = 1, O = 6, K = 4

... แต่คุณจะเขียนโค้ดแบบนี้หรือไม่?

ฉันพนันได้เลยว่าคำตอบคือ NONBHN (ไม่เพียง แต่ไม่เท่านั้น แต่ไม่มีนรก!) - เพราะมันซับซ้อนเกินไป แน่ใจว่าคุณสามารถสร้างชื่อตัวระบุที่มีความหมายได้มากกว่า "temp0" .. "temp3" แต่ประเด็นก็คือพวกเขาไม่ได้เพิ่มอะไรลงในรหัส - พวกเขาไม่ได้ทำให้โค้ดทำงานได้ดีขึ้น ทำให้โค้ดอ่านดีขึ้นพวกเขาน่าเกลียดแค่โค้ดและถ้าคุณทำมันด้วยมือไม่ต้องสงสัยเลยว่าคุณจะเลอะมันซักครั้งหรือสามครั้งก่อนจะทำให้ถูกต้อง นอกจากนี้การเล่น "เกมชื่อ" นั้นยากพอสำหรับตัวระบุที่มีความหมายดังนั้นฉันจึงยินดีต้อนรับตัวแบ่งจากเกมชื่อที่ผู้เรียบเรียงให้ฉันในแบบสอบถามความเข้าใจ

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

  • ความซับซ้อนของJoinและGroupJoin: การกำหนดขอบเขตของตัวแปรช่วงในjoinอนุประโยคความเข้าใจแบบสอบถามเปลี่ยนความผิดพลาดที่อาจจะรวบรวมในรูปแบบจุดลงในข้อผิดพลาดรวบรวมเวลาในไวยากรณ์ความเข้าใจ
  • เมื่อใดก็ตามที่คอมไพเลอร์จะแนะนำตัวระบุโปร่งใสในการแปลงความเข้าใจความเข้าใจจะคุ้มค่า ซึ่งรวมถึงการใช้ข้อใดข้อหนึ่งต่อไปนี้: fromคำสั่งหลายข้อjoin& join..intoข้อและletข้อ

ฉันรู้จักร้านวิศวกรรมมากกว่าหนึ่งแห่งในบ้านเกิดของฉันซึ่งมีไวยากรณ์ความเข้าใจที่ผิดกฎหมาย ฉันคิดว่านี่เป็นเรื่องที่น่าเสียดายเพราะความเข้าใจไวยากรณ์เป็นเพียงเครื่องมือและสิ่งที่มีประโยชน์ ฉันคิดว่ามันเหมือนพูดว่า"มีหลายสิ่งที่คุณสามารถทำได้ด้วยไขควงที่คุณไม่สามารถทำกับสิ่วได้เพราะคุณสามารถใช้ไขควงเป็นสิ่วสิ่วถูกแบนตั้งแต่นี้ไปภายใต้พระราชกฤษฎีกาของกษัตริย์"


-1: ว้าว OP กำลังมองหาคำแนะนำเล็กน้อย คุณปั่นนวนิยาย! คุณจะช่วยทำให้เรื่องนี้แน่นขึ้นบ้างไหม?
Jim G.

8

คำแนะนำของฉันคือการใช้ไวยากรณ์ความเข้าใจแบบสอบถามเมื่อการแสดงออกทั้งหมดสามารถทำได้ในไวยากรณ์ความเข้าใจ นั่นคือฉันต้องการ:

var query = from c in customers orderby c.Name select c.Address;

ไปยัง

var query = customers.OrderBy(c=>c.Name).Select(c=>c.Address);

แต่ฉันต้องการ

int count = customers.Where(c=>c.City == "London").Count();

ไปยัง

int count = (from c in customers where c.City == "London" select c).Count();

ฉันหวังว่าเราจะได้มาพร้อมกับไวยากรณ์ที่ทำให้ดีกว่าในการผสมทั้งสอง สิ่งที่ต้องการ:

int count = from c in customers 
            where c.City == "London" 
            select c 
            continue with Count();

แต่น่าเศร้าที่เราทำไม่ได้

แต่โดยทั่วไปมันเป็นเรื่องของการตั้งค่า ทำสิ่งที่ดูดีกับคุณและเพื่อนร่วมงานของคุณ


3
หรือคุณอาจพิจารณาแยกความเข้าใจจากการเรียกโอเปอเรเตอร์ LINQ อื่น ๆ ผ่านการรีแฟคเตอร์ "แนะนำอธิบายตัวแปร" ตัวอย่างเช่นvar londonCustomers = from c in ...; int count = londonCustomers.Count();
devgeezer

3

SQL-like เป็นวิธีที่ดีในการเริ่มต้น แต่เนื่องจากมีข้อ จำกัด (รองรับเพียงสิ่งก่อสร้างที่ภาษาปัจจุบันของคุณรองรับ) ในที่สุดนักพัฒนาก็จะไปที่รูปแบบวิธีการขยาย

ฉันต้องการที่จะทราบว่ามีบางกรณีที่อาจนำไปใช้อย่างง่ายดายตามสไตล์ SQL

นอกจากนี้คุณสามารถรวมทั้งสองวิธีในหนึ่งแบบสอบถาม


2

ฉันมักจะใช้ไวยากรณ์ที่ไม่ใช่แบบสอบถามเว้นแต่ฉันจะต้องกำหนดตัวแปรกลางทางแม้ว่าแบบสอบถามเช่น

from x in list
let y = x.DoExpensiveCalulation()
where y > 42
select y

แต่ฉันเขียนไวยากรณ์ที่ไม่ใช่แบบสอบถามเช่น

x.Where(c => filter)
 .Select(c => datatransform)

2

ฉันมักจะใช้ฟังก์ชั่นส่วนต่อขยายเนื่องจากการสั่งซื้อ นำตัวอย่างง่ายๆของคุณไปใช้ใน SQL คุณเขียน select first- แม้ว่าที่จริงแล้วจะถูกประมวลผลที่ใดก่อน เมื่อคุณเขียนโดยใช้วิธีการขยายแล้วฉันรู้สึกควบคุมได้มากขึ้น ฉันได้รับ Intellisense เกี่ยวกับสิ่งที่เสนอฉันเขียนสิ่งต่าง ๆ ตามลำดับที่เกิดขึ้น


ฉันคิดว่าคุณจะพบว่าในไวยากรณ์ "query comprehension" การเรียงลำดับในหน้านั้นเหมือนกับลำดับการดำเนินการที่เกิดขึ้น LINQ ไม่ได้ใส่ "select" ก่อนซึ่งแตกต่างจาก SQL
Eric Lippert

1

ฉันชอบฟังก์ชั่นส่วนขยายด้วย

อาจทำให้ฉันมีความก้าวร้าวน้อยกว่าในใจ

มันให้ความรู้สึกอ่านง่ายขึ้นเช่นกันโดยเฉพาะถ้าคุณใช้เฟรมเวิร์กของบุคคลที่สามซึ่งมี linq api


0

นี่คือฮิวริสติกที่ฉันติดตาม:

ชอบการแสดงออกของ LINQ มากกว่า lambdas เมื่อคุณเข้าร่วม

ฉันคิดว่า lambdas ที่มีผู้ร่วมเล่นดูยุ่งเหยิงและอ่านยาก

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.