มีข้อได้เปรียบอะไรบ้างจากการใช้งาน LINQ ในลักษณะที่ไม่ได้แคชผลลัพธ์


20

นี่เป็นข้อผิดพลาดที่ทราบกันดีสำหรับผู้ที่เปียกปอนโดยใช้ LINQ:

public class Program
{
    public static void Main()
    {
        IEnumerable<Record> originalCollection = GenerateRecords(new[] {"Jesse"});
        var newCollection = new List<Record>(originalCollection);

        Console.WriteLine(ContainTheSameSingleObject(originalCollection, newCollection));
    }

    private static IEnumerable<Record> GenerateRecords(string[] listOfNames)
    {
        return listOfNames.Select(x => new Record(Guid.NewGuid(), x));
    }

    private static bool ContainTheSameSingleObject(IEnumerable<Record>
            originalCollection, List<Record> newCollection)
    {
        return originalCollection.Count() == 1 && newCollection.Count() == 1 &&
                originalCollection.Single().Id == newCollection.Single().Id;
    }

    private class Record
    {
        public Guid Id { get; }
        public string SomeValue { get; }

        public Record(Guid id, string someValue)
        {
            Id = id;
            SomeValue = someValue;
        }
    }
}

สิ่งนี้จะพิมพ์ "เท็จ" เนื่องจากสำหรับแต่ละชื่อที่ให้มาเพื่อสร้างคอลเลกชันดั้งเดิมฟังก์ชันที่เลือกจะได้รับการประเมินค่าใหม่และRecordวัตถุผลลัพธ์จะถูกสร้างขึ้นใหม่ เพื่อแก้ไขปัญหานี้เรียกง่ายๆที่จะอาจจะเพิ่มในตอนท้ายของToListGenerateRecords

ไมโครซอฟท์หวังว่าจะได้ประโยชน์อะไรจากการใช้วิธีนี้?

เหตุใดการใช้งานจึงไม่เพียง แต่แคชผลลัพธ์ของอาร์เรย์ภายใน ส่วนหนึ่งที่เฉพาะเจาะจงของสิ่งที่เกิดขึ้นอาจถูกเลื่อนการดำเนินการ แต่ยังสามารถใช้งานได้หากไม่มีพฤติกรรมนี้

เมื่อสมาชิกที่ได้รับคอลเลกชันที่ส่งคืนโดย LINQ ได้รับการประเมินประโยชน์อะไรที่มีให้โดยการไม่เก็บการอ้างอิง / การคัดลอกภายใน แต่จะทำการคำนวณผลลัพธ์เดียวกันซ้ำอีกครั้งซึ่งเป็นพฤติกรรมเริ่มต้น

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

มีข้อได้เปรียบอะไรบ้างในเรื่องนี้และทำไม Microsoft ถึงตัดสินใจอย่างรอบคอบ


1
เพียงเรียก ToList () ในเมธอด GenerateRecords () ของคุณ return listOfNames.Select(x => new Record(Guid.NewGuid(), x)).ToList(); ที่ให้ "สำเนาแคช" ของคุณ แก้ไขปัญหา.
Robert Harvey

1
ฉันรู้ แต่ฉันสงสัยว่าทำไมพวกเขาถึงทำสิ่งนี้ในตอนแรก
Panzercrisis

11
เนื่องจากการประเมินแบบสันหลังยาวมีประโยชน์อย่างมากไม่ใช่อย่างน้อย "โอ้โดยวิธีการบันทึกนี้เปลี่ยนไปตั้งแต่ครั้งสุดท้ายที่คุณขอมันนี่เป็นเวอร์ชั่นใหม่" ซึ่งเป็นตัวอย่างรหัสของคุณอย่างชัดเจน
Robert Harvey

ฉันสาบานได้ว่าฉันได้อ่านคำถามที่เป็นวลีเกือบจะเหมือนกันในช่วง 6 เดือนที่ผ่านมา แต่ตอนนี้ฉันไม่พบมัน ที่ใกล้เคียงที่สุดที่ฉันสามารถหาได้คือตั้งแต่ปี 2016 บน stackoverflow: stackoverflow.com/q/37437893/391656
Mr.Mindor

29
เรามีชื่อแคชโดยไม่มีนโยบายการหมดอายุ: "memory รั่ว" เรามีชื่อแคชโดยไม่มีนโยบายการทำให้ใช้งานไม่ได้: "bug Farm" หากคุณจะไม่เสนอนโยบายการหมดอายุและการตรวจสอบความถูกต้องที่ใช้งานได้กับทุกคำสั่ง LINQ ที่เป็นไปได้คำถามของคุณจะตอบเอง
Eric Lippert

คำตอบ:


51

มีข้อได้เปรียบอะไรบ้างจากการใช้งาน LINQ ในลักษณะที่ไม่ได้แคชผลลัพธ์

การแคชผลลัพธ์ไม่ได้ผลสำหรับทุกคน ตราบใดที่คุณมีข้อมูลจำนวนน้อยมาก ดีสำหรับคุณ. แต่ถ้าข้อมูลของคุณใหญ่กว่า RAM ของคุณล่ะ

มันไม่มีส่วนเกี่ยวข้องกับ LINQ แต่มีIEnumerable<T>อินเตอร์เฟสทั่วไป

มันเป็นความแตกต่างระหว่างFile.ReadAllLinesและFile.ReadLines หนึ่งจะอ่านไฟล์ทั้งหมดเป็น RAM และอื่น ๆ จะให้มันทีละบรรทัดเพื่อให้คุณสามารถทำงานกับไฟล์ขนาดใหญ่ (ตราบใดที่พวกเขามีตัวแบ่งบรรทัด)

คุณสามารถทุกอย่างที่คุณต้องการแคชแคชโดย materializing ลำดับของคุณเรียกร้องอย่างใดอย่างหนึ่ง.ToList()หรือ.ToArray()กับมัน แต่พวกเราที่ไม่ต้องการแคชเรามีโอกาสที่จะไม่ทำเช่นนั้น

และในบันทึกที่เกี่ยวข้อง: คุณจะแคชสิ่งต่อไปนี้ได้อย่างไร?

IEnumerable<int> AllTheZeroes()
{
    while(true) yield return 0;
}

คุณไม่สามารถ. นั่นเป็นเหตุผลที่IEnumerable<T>มีอยู่จริง


2
ตัวอย่างสุดท้ายของคุณน่าสนใจกว่านี้ถ้ามันเป็นซีรี่ย์ที่ไม่มีที่สิ้นสุดจริง ๆ (เช่นฟีโนแนน) และไม่ใช่แค่เลขศูนย์ไม่รู้จบซึ่งไม่น่าสนใจเป็นพิเศษ
Robert Harvey

23
@ RobertHarvey นั่นเป็นความจริงฉันแค่คิดว่ามันง่ายกว่าที่จะเห็นว่ามันเป็นศูนย์ไม่รู้จบเมื่อไม่มีตรรกะให้เข้าใจ
nvoigt

2
int i=1; while(true) { i++; yield fib(i); }
Robert Harvey

2
ตัวอย่างที่ฉันคิดคือEnumerable.Range(1,int.MaxValue)- มันง่ายมากที่จะหาขอบเขตล่างของหน่วยความจำที่จะใช้
Chris

4
สิ่งอื่น ๆ ที่ฉันได้เห็นตามเส้นของwhile (true) return ...คือwhile (true) return _random.Next();การสร้างกระแสสุ่มของตัวเลขสุ่ม
Chris

24

ไมโครซอฟท์หวังว่าจะได้ประโยชน์อะไรจากการใช้วิธีนี้?

ความถูกต้อง? ฉันหมายถึงแกนกลางที่นับได้สามารถเปลี่ยนระหว่างการโทรได้ การแคชมันจะสร้างผลลัพธ์ที่ไม่ถูกต้องและเปิดทั้ง“ เมื่อ / อย่างไรฉันจะทำให้แคชนั้นไม่ถูกต้องได้อย่างไร” ของเวิร์ม

และถ้าคุณพิจารณา LINQ ถูกออกแบบมาเป็นวิธีการที่จะทำ LINQ กับแหล่งที่มาของข้อมูล (เช่นกรอบนิติบุคคลหรือ SQL โดยตรง) นับถูกจะมีการเปลี่ยนแปลงตั้งแต่ที่สิ่งที่ฐานข้อมูลทำ

ยิ่งไปกว่านั้นยังมีหลักการความรับผิดชอบเดียวที่เกี่ยวข้อง มันง่ายกว่ามากในการสร้างโค้ดคิวรีที่ใช้งานและสร้างแคชด้านบนได้มากกว่าการสร้างโค้ดที่เคียวรีและแคช แต่จากนั้นลบแคชออก


3
มันอาจจะคุ้มค่าที่จะกล่าวถึงสิ่งที่ICollectionมีอยู่และอาจเป็นไปในทางที่ OP คาดหวังว่าIEnumerableจะประพฤติตน
Caleth

หากคุณใช้ IEnumerable <T> เพื่ออ่านเคอร์เซอร์ฐานข้อมูลแบบเปิดผลลัพธ์ของคุณไม่ควรเปลี่ยนแปลงหากคุณใช้ฐานข้อมูลที่มีธุรกรรม ACID
Doug

4

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


4

อีกเหตุผลที่ไม่ได้กล่าวถึงคือความเป็นไปได้ในการต่อเชื่อมฟิลเตอร์และการแปลงต่าง ๆ โดยไม่ต้องสร้างผลลัพธ์กลางขยะ

ยกตัวอย่างเช่น

cars.Where(c => c.Year > 2010)
.Select(c => new { c.Model, c.Year, c.Color })
.GroupBy(c => c.Year);

หากวิธี LINQ คำนวณผลลัพธ์ทันทีเราจะมี 3 คอลเลกชัน:

  • ในกรณีที่ผล
  • เลือกผลลัพธ์
  • ผลลัพธ์ของ GroupBy

ซึ่งเราใส่ใจเพียงเรื่องสุดท้ายเท่านั้น ไม่มีจุดใดในการบันทึกผลลัพธ์กลางเนื่องจากเราไม่สามารถเข้าถึงได้และเราต้องการทราบเกี่ยวกับรถยนต์ที่กรองและจัดกลุ่มตามปีแล้วเท่านั้น

หากมีความจำเป็นต้องบันทึกผลลัพธ์ใด ๆ เหล่านี้โซลูชันนั้นง่าย: แยกการโทรออกจากกันและโทรหา.ToList()พวกเขาและบันทึกไว้ในตัวแปร


เช่นเดียวกับบันทึกย่อใน JavaScript วิธี Array จะส่งคืนผลลัพธ์ทันทีซึ่งอาจทำให้มีการใช้หน่วยความจำมากขึ้นหากไม่ระมัดระวัง


3

โดยพื้นฐานแล้วรหัสนี้ - การใส่คำสั่งGuid.NewGuid ()ภายในSelect- น่าสงสัยอย่างมาก นี่เป็นกลิ่นรหัสแน่นอน!

ในทางทฤษฎีเราไม่จำเป็นต้องคาดหวังว่าจะมีSelectคำสั่งในการสร้างข้อมูลใหม่ แต่เพื่อดึงข้อมูลที่มีอยู่ แม้ว่าจะมีเหตุผลที่เลือกให้เข้าร่วมข้อมูลจากหลาย ๆ แหล่งเพื่อสร้างเนื้อหาที่เข้าร่วมที่มีรูปร่างแตกต่างกันหรือแม้แต่คำนวณคอลัมน์เพิ่มเติม แต่เราอาจคาดหวังว่ามันจะทำงานได้จริง การวางNewGuid ()ภายในทำให้ไม่ทำงานและไม่บริสุทธิ์

การสร้างข้อมูลสามารถแยกออกจากการเลือกและนำไปใช้ในการสร้างการเรียงลำดับบางอย่างเพื่อให้การเลือกยังคงบริสุทธิ์และสามารถใช้งานได้อีกครั้งมิฉะนั้นการเลือกควรทำเพียงครั้งเดียวและห่อ / ป้องกัน - สิ่งนี้ คือ.ToList ()ข้อเสนอแนะ

อย่างไรก็ตามเพื่อให้ชัดเจนปัญหาดูเหมือนว่าฉันจะผสมผสานการสร้างภายในการเลือกมากกว่าการขาดแคช การใส่NewGuid()ด้านในของตัวเลือกดูเหมือนจะเป็นการผสมผสานรูปแบบการเขียนโปรแกรมที่ไม่เหมาะสม


0

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

อาจเป็นที่ถกเถียงกันอยู่ว่าแอพพลิเคชั่นส่วนใหญ่ต้องการผลลัพธ์ในทันทีดังนั้นควรเป็นพฤติกรรมเริ่มต้นของ LINQ แต่มี API อื่น ๆ อีกมากมาย (เช่นList<T>.ConvertAll) ที่ให้บริการลักษณะการทำงานนี้และได้ทำตั้งแต่ Framework ถูกสร้างขึ้นในขณะที่จนกว่า LINQ ถูกนำมาใช้ก็ไม่มีทางที่จะมีการดำเนินการรอการตัดบัญชี ซึ่งเป็นคำตอบอื่น ๆ ที่แสดงให้เห็นว่าเป็นข้อกำหนดเบื้องต้นสำหรับการเปิดใช้งานการคำนวณบางประเภทที่อาจเป็นไปไม่ได้ (โดยการใช้พื้นที่เก็บข้อมูลที่เหลืออยู่ทั้งหมด) เมื่อใช้การดำเนินการทันที

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