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वस्तु नए सिरे से बनाई जाती है। इसे ठीक करने के लिए, ToListके अंत में एक साधारण कॉल जोड़ा जा सकता है GenerateRecords

इस तरह से लागू करने से Microsoft को क्या लाभ होने की उम्मीद थी?

कार्यान्वयन केवल एक आंतरिक सरणी के परिणाम को कैश क्यों नहीं करेगा? जो कुछ भी हो रहा है उसका एक विशिष्ट हिस्सा निष्पादन को स्थगित किया जा सकता है, लेकिन इसे अभी भी इस व्यवहार के बिना लागू किया जा सकता है।

एक बार LINQ द्वारा लौटाए गए संग्रह के किसी दिए गए सदस्य का मूल्यांकन किया गया है, आंतरिक संदर्भ / कॉपी नहीं रखने से क्या लाभ मिलता है, लेकिन एक ही परिणाम को एक डिफ़ॉल्ट व्यवहार के रूप में पुनर्गणना करते हुए?

उन स्थितियों में जहां एक संग्रह के एक ही सदस्य के लिए तर्क में एक विशेष आवश्यकता होती है, बार-बार पुनर्गणना होती है, ऐसा लगता है कि एक वैकल्पिक पैरामीटर के माध्यम से निर्दिष्ट किया जा सकता है और यह कि डिफ़ॉल्ट व्यवहार अन्यथा कर सकता है। इसके अलावा, गति लाभ जो आस्थगित निष्पादन द्वारा प्राप्त किया जाता है, अंततः उसी परिणाम को लगातार पुनर्गठित करने में लगने वाले समय के विरुद्ध वापस काट दिया जाता है। अंत में यह उन लोगों के लिए भ्रामक ब्लॉक है जो LINQ के लिए नए हैं, और यह अंततः किसी के कार्यक्रम में सूक्ष्म बग पैदा कर सकता है।

इसका क्या फ़ायदा है, और Microsoft ने ऐसा क्यों किया यह बहुत ही जानबूझकर लिया गया निर्णय है?


1
बस अपने GenerateRecords () विधि में ToList () को कॉल करें। return listOfNames.Select(x => new Record(Guid.NewGuid(), x)).ToList(); यह आपको आपकी "कैश्ड कॉपी" देता है। समस्या सुलझ गयी।
रॉबर्ट हार्वे

1
मुझे पता है, लेकिन मैं सोच रहा था कि उन्होंने पहली जगह में ऐसा क्यों किया होगा।
पैंजरक्रिसिस

11
क्योंकि आलसी मूल्यांकन के महत्वपूर्ण लाभ हैं, जिनमें से कम से कम "ओह, वैसे, यह रिकॉर्ड आपके द्वारा अनुरोध किए जाने के बाद से बदल गया है; यहां नया संस्करण है", जो कि वास्तव में आपके कोड का उदाहरण दिखाता है।
रॉबर्ट हार्वे

मैं कसम खा सकता था कि मैंने पिछले 6 महीनों में यहाँ लगभग एक पहचाने जाने वाले प्रश्न को पढ़ा है, लेकिन मैं इसे अब नहीं पा रहा हूँ। मैं निकटतम पाया जा सकता है 2016 से stackoverflow पर: stackoverflow.com/q/37437893/391656
Mr.Mindor

29
हमारे पास एक कैश के लिए एक समाप्ति की नीति के बिना एक नाम है: "मेमोरी लीक"। हमारे पास अमान्य नीति के बिना कैश के लिए एक नाम है: "बग फार्म"। यदि आप हमेशा सही-सही समाप्ति और अमान्य नीति का प्रस्ताव नहीं करने जा रहे हैं जो हर संभव LINQ क्वेरी के लिए काम करती है तो आपका प्रश्न थोड़े ही उत्तर देता है।
एरिक लिपर्ट

जवाबों:


51

LINQ को इस तरह से लागू करने से क्या लाभ हुआ जो परिणाम को कैश नहीं करता है?

परिणाम कैशिंग बस हर किसी के लिए काम नहीं करेगा। जब तक आपके पास छोटी मात्रा में डेटा है, तब तक बढ़िया। आपके लिए अच्छा हैं। लेकिन क्या होगा यदि आपका डेटा आपके रैम से बड़ा है?

इसका LINQ से कोई लेना-देना नहीं है, लेकिन IEnumerable<T>सामान्य रूप से इंटरफ़ेस के साथ ।

यह File.ReadAllLines और File.ReadLines के बीच अंतर है । एक रैम में पूरी फ़ाइल को पढ़ने, और इसलिए आप बड़ी फ़ाइलों के साथ काम कर सकते हैं अन्य यह दे देंगे करने के लिए आप लाइन द्वारा लाइन, (जब तक वे है लाइन टूट जाता है)।

आप आसानी से कैश सब कुछ आप अपने अनुक्रम या तो बुला materializing द्वारा कैश करना चाहते हैं कर सकते हैं .ToList()या .ToArray()उस पर। लेकिन हममें से जो इसे कैश नहीं करना चाहते, हमारे पास ऐसा नहीं करने का मौका है ।

और संबंधित नोट पर: आप निम्नलिखित को कैश कैसे करते हैं?

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

आप नहीं कर सकते। यही कारण IEnumerable<T>है कि यह मौजूद है।


2
आपका अंतिम उदाहरण अधिक सम्मोहक होगा यदि यह एक वास्तविक अनंत श्रृंखला (जैसे फिबोनेसी) थी, और केवल शून्य का एक अंतहीन स्ट्रिंग नहीं है, जो विशेष रूप से दिलचस्प नहीं है।
रॉबर्ट हार्वे

23
@RobertHarvey यह सच है, मुझे लगा कि यह समझना आसान है कि यह शून्य की एक अंतहीन धारा है जब समझने के लिए कोई तर्क नहीं है।
nvoigt

2
int i=1; while(true) { i++; yield fib(i); }
रॉबर्ट हार्वे

2
मैं जिस उदाहरण के बारे में सोच रहा था Enumerable.Range(1,int.MaxValue)- इसका उपयोग करने के लिए कितनी स्मृति है, इसके लिए एक निचली सीमा से बाहर काम करना बहुत आसान है।
क्रिस

4
दूसरी बात जो मैंने देखी है, वह while (true) return ...थी while (true) return _random.Next();यादृच्छिक संख्याओं की अनंत धारा उत्पन्न करना।
क्रिस

24

इस तरह से लागू करने से Microsoft को क्या लाभ होने की उम्मीद थी?

शुद्धता? मेरा मतलब है, कॉल के बीच में कोर एनुमरबल बदल सकता है। इसे कैशिंग करने से गलत परिणाम आएंगे और पूरे "कब / कैसे मैं उस कैश को अमान्य करूं?"

और अगर आपको लगता है कि LINQ मूल रूप से डेटा स्रोतों (जैसे निकाय फ्रेमवर्क, या SQL सीधे) के लिए LINQ करने के लिए एक साधन के रूप में डिज़ाइन किया गया था, तो enumerable बदलने जा रहा था क्योंकि डेटाबेस क्या करते हैं

उस के शीर्ष पर, एकल जिम्मेदारी सिद्धांत चिंताएं हैं। यह क्वेरी और कैश बनाने के बजाय कुछ क्वेरी कोड बनाना और उसके शीर्ष पर कैशिंग बनाना आसान है, लेकिन फिर कैशिंग को हटा दें।


3
यह उल्लेख के लायक है कि ICollectionमौजूद है, और शायद ओपी IEnumerableव्यवहार करने की उम्मीद कर रहा है जिस तरह से व्यवहार करता है
कैलथ

यदि आप एक खुले डेटाबेस कर्सर को पढ़ने के लिए IEnumerable <T> का उपयोग कर रहे हैं, तो यदि आप ACST लेनदेन के साथ डेटाबेस का उपयोग कर रहे हैं तो आपके परिणाम नहीं बदलने चाहिए।
डग

4

क्योंकि LINQ है, और शुरू से ही इसका उद्देश्य था, कार्यात्मक प्रोग्रामिंग भाषाओं में लोकप्रिय मोनाड पैटर्न का एक सामान्य कार्यान्वयन , और एक मोनाड को कॉल के समान अनुक्रम दिए गए समान मानों को प्राप्त करने के लिए विवश नहीं किया जाता है (वास्तव में, इसका उपयोग कार्यात्मक प्रोग्रामिंग में इस संपत्ति के कारण सटीक रूप से लोकप्रिय है, जो शुद्ध कार्यों के निर्धारक व्यवहार से बचने की अनुमति देता है)।


4

एक और कारण जिसका उल्लेख नहीं किया गया है, वह है कचरे के बीच के परिणामों को बनाए बिना विभिन्न फिल्टर और परिवर्तनों को संक्षिप्त करने की संभावना।

इसे उदाहरण के लिए लें:

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

यदि LINQ विधियों ने तुरंत परिणामों की गणना की, तो हमारे पास 3 संग्रह होंगे:

  • जहां परिणाम
  • परिणाम का चयन करें
  • GroupBy परिणाम

जिनमें से हम केवल आखिरी की परवाह करते हैं। मध्य परिणामों को बचाने का कोई मतलब नहीं है क्योंकि हमारे पास उनकी पहुंच नहीं है, और हम केवल उन कारों के बारे में जानना चाहते हैं जो पहले से ही फ़िल्टर की गई हैं और वर्ष तक समूहीकृत हैं।

यदि इनमें से किसी भी परिणाम को बचाने की आवश्यकता थी, तो समाधान सरल है: कॉल को अलग करें .ToList()और उन पर कॉल करें और उन्हें एक चर में सहेजें।


बस एक साइड नोट के रूप में, जावास्क्रिप्ट में, एरे विधियां वास्तव में तुरंत परिणाम लौटाती हैं, जिससे अगर कोई सावधान न हो तो अधिक मेमोरी खपत हो सकती है।


3

मौलिक रूप से, यह कोड - Guid.NewGuid ()एक Selectस्टेटमेंट के अंदर - अत्यधिक संदिग्ध है। यह निश्चित रूप से किसी प्रकार का एक कोड गंध है!

सिद्धांत रूप में, हम Selectनए डेटा बनाने के लिए एक बयान की अपेक्षा नहीं करेंगे, लेकिन मौजूदा डेटा को पुनः प्राप्त करने के लिए। हालांकि यह चयन करने के लिए उचित है कि विभिन्न स्रोतों के डेटा को अलग-अलग आकार की सामग्री में शामिल करने या अतिरिक्त स्तंभों की गणना करने के लिए, हम अभी भी इसे कार्यात्मक और शुद्ध होने की उम्मीद कर सकते हैं। NewGuid ()अंदर डालने से यह गैर-कार्यात्मक और गैर-शुद्ध हो जाता है।

डेटा का निर्माण चयन से अलग किया जा सकता है और किसी प्रकार के निर्माण कार्य में लगाया जा सकता है, ताकि चयन शुद्ध और पुन: प्रयोज्य रह सके, अन्यथा चयन केवल एक बार किया जाना चाहिए और लिपटा / संरक्षित किया जाना चाहिए - यह है .ToList ()सुझाव।

हालांकि, स्पष्ट होने के लिए, यह मुद्दा मुझे कैशिंग की कमी के बजाय चयन के अंदर सृजन का मिश्रण लगता है। NewGuid()चयन के अंदर रखना मुझे प्रोग्रामिंग मॉडल का अनुचित मिश्रण प्रतीत होता है।


0

आस्थगित निष्पादन उन लोगों को लिखने की अनुमति देता है जो LINQ कोड (सटीक, उपयोग करते हुए IEnumerable<T>) स्पष्ट रूप से चुनते हैं कि परिणाम तुरंत गणना और मेमोरी में संग्रहीत है, या नहीं। दूसरे शब्दों में, यह प्रोग्रामर को स्टोरेज स्पेस ट्रेडऑफ़ के लिए गणना समय चुनने की अनुमति देता है जो उनके आवेदन के लिए सबसे उपयुक्त है।

यह तर्क दिया जा सकता है कि अधिकांश अनुप्रयोग तुरंत परिणाम चाहते हैं, ताकि LINQ का डिफ़ॉल्ट व्यवहार होना चाहिए। लेकिन कई अन्य एपीआई (जैसे List<T>.ConvertAll) हैं जो इस व्यवहार की पेशकश करते हैं और फ्रेमवर्क बनाए जाने के बाद से किया है, जबकि जब तक LINQ पेश नहीं किया गया था, तब तक निष्पादन को स्थगित करने का कोई तरीका नहीं था। जो, जैसा कि अन्य उत्तरों ने प्रदर्शित किया है, कुछ प्रकार की संगणनाओं को सक्षम करने के लिए एक शर्त है, जो तत्काल निष्पादन का उपयोग करते समय असंभव (सभी उपलब्ध भंडारण को समाप्त करके) कर सकते हैं।

हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.