Sonuçları önbelleğe almayacak şekilde LINQ uygulanarak ne avantaj sağlandı?


20

Bu, LINQ kullanarak ayaklarını ıslatan insanlar için bilinen bir tuzaktır:

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;
        }
    }
}

Özgün koleksiyonu oluşturmak için sağlanan her ad için, select işlevi yeniden değerlendirilmeye devam ettiğinden ve sonuçta ortaya çıkan Recordnesne yeniden oluşturulduğundan , bu "False" yazdıracaktır . Bunu düzeltmek ToListiçin sonuna basit bir çağrı eklenebilir GenerateRecords.

Microsoft bu şekilde uygulayarak ne gibi avantajlar elde etmeyi umuyordu?

Uygulama neden sonuçları dahili bir dizi olarak önbelleğe almasın? Olanların belirli bir kısmı ertelenmiş yürütme olabilir, ancak yine de bu davranış olmadan uygulanabilir.

LINQ tarafından döndürülen bir koleksiyonun belirli bir üyesi değerlendirildikten sonra, dahili bir referans / kopya tutmamak, bunun yerine varsayılan bir davranış olarak aynı sonucu yeniden hesaplayarak ne gibi bir avantaj sağlanır?

Tekrar tekrar hesaplanan bir koleksiyonun aynı üyesi için mantıkta özel bir ihtiyaç olduğu durumlarda, bu isteğe bağlı bir parametre ile belirtilebilir ve varsayılan davranışın aksini yapabilir. Buna ek olarak, ertelenmiş uygulama ile elde edilen hız avantajı, aynı sonuçları sürekli olarak yeniden hesaplamak için geçen zamana göre azaltılır. Son olarak, bu LINQ için yeni olanlar için kafa karıştırıcı bir blok ve sonuçta herkesin programında ince hatalara yol açabilir.

Bunun ne avantajı var ve Microsoft bu görünüşte çok kasıtlı bir karar verdi?


1
GenerateRecords () yönteminizde ToList () öğesini çağırmanız yeterlidir. return listOfNames.Select(x => new Record(Guid.NewGuid(), x)).ToList(); Bu size "önbelleğe alınmış kopyanızı" verir. Sorun çözüldü.
Robert Harvey

1
Biliyorum, ama bunu neden ilk başta gerekli hale getireceklerini merak ediyordum.
Panzercrisis

11
Tembel değerlendirmenin önemli faydaları olduğundan, en azından "oh, bu arada, bu kayıt en son istediğiniz zamandan beri değişti; İşte yeni sürüm", bu da kod örneğinizin tam olarak gösterdiği şey.
Robert Harvey

Yemin edebilirim ki, son 6 ay içinde neredeyse aynı cümleleri okudum ama şimdi bulamıyorum. En yakın bulabildiğim 2016 yılında stackoverflow üzerinde idi: stackoverflow.com/q/37437893/391656
Mr.Mindor

29
Son kullanma politikası olmayan bir önbellek için bir adımız var: "bellek sızıntısı". Geçersiz kılma politikası olmayan bir önbellek için bir adımız var: "hata çiftliği". Her olası LINQ sorgusu için çalışan, her zaman doğru bir süre sonu ve geçersiz kılma politikası önermezseniz , sorunuz kendiliğinden yanıt verir.
Eric Lippert

Yanıtlar:


51

Sonuçları önbelleğe almayacak şekilde LINQ uygulanarak ne avantaj sağlandı?

Sonuçların önbelleğe alınması herkes için işe yaramaz. Çok az miktarda veriye sahip olduğunuz sürece harika. Aferin. Peki ya verileriniz RAM'inizden büyükse?

LINQ ile ilgisi yoktur, ancak IEnumerable<T>genel olarak arayüzle ilgilidir.

File.ReadAllLines ve File.ReadLines arasındaki farktır . Bir RAM içine tüm dosyayı okuyacak ve çizgi ile satır büyük dosyalarla çalışabilmek için (bunlar sürece diğer vereceğim sahip bir satır sonu).

Kolayca önbellek her şey ya çagiran dizisini hayata tarafından önbelleğe istiyor olabilir .ToList()veya .ToArray()üzerinde. Ama yok bizler değil önbelleğe istiyoruz, şansına sahip değil bunu.

Ve ilgili bir notta: aşağıdakileri nasıl önbelleğe alırsınız?

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

Yapamazsın. Bu yüzden IEnumerable<T>olduğu gibi var.


2
Son örneğiniz, gerçek bir sonsuz dizi (Fibonnaci gibi) olsaydı ve sadece ilginç olmayan sonsuz bir sıfır dizisi olmasaydı daha cazip olurdu.
Robert Harvey

23
@RobertHarvey Bu doğru, sadece anlayacak bir mantık olmadığında sonsuz bir sıfır akışı olduğunu fark etmenin daha kolay olduğunu düşündüm.
nvoigt

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

2
Düşündüğüm örnek Enumerable.Range(1,int.MaxValue)- ne kadar bellek kullanacağına dair bir alt sınır bulmak çok kolay.
Chris

4
Ben çizgisinde gördüğüm başka bir şey while (true) return ...oldu while (true) return _random.Next();rasgele sayıların sonsuz akışı oluşturmak için.
Chris

24

Microsoft bu şekilde uygulayarak ne gibi avantajlar elde etmeyi umuyordu?

Doğruluk? Yani, numaralandırılabilir çekirdek çağrılar arasında değişebilir. Önbellekleme yanlış sonuçlar verir ve tüm "ne zaman / nasıl bu önbelleği geçersiz kılabilirim?"

Eğer düşünürsek LINQ aslen (varlık çerçevesi veya SQL doğrudan benzeri) veri kaynaklarına LINQ yapmak için bir araç olarak tasarlandı, enumerable edildi veritabanları ne anlama beri değişime gidiyor yapmak .

Bunun da ötesinde, Tek Sorumluluk İlkesi endişeleri vardır. Çalışan ve üzerinde önbellek oluşturma bazı sorgu kodu yapmak, sorgu ve önbellek kod oluşturmak, ancak sonra önbelleği kaldırmak çok daha kolaydır.


3
Var ICollectionolan ve muhtemelen OP'nin IEnumerabledavranmayı beklediği şekilde davranır
Caleth

Açık bir veritabanı imlecini okumak için IEnumerable <T> kullanıyorsanız, ACID işlemlerine sahip bir veritabanı kullanıyorsanız sonuçlarınız değişmemelidir.
Doug

4

LINQ, başlangıçtan beri ve amaçlandığı için, işlevsel programlama dillerinde popüler olan Monad modelinin genel bir uygulamasıdır ve bir Monad, aynı çağrı dizisi göz önüne alındığında her zaman aynı değerleri vermekle sınırlı değildir (aslında, kullanımı fonksiyonel programlamada, saf işlevlerin deterministik davranışından kaçmaya izin veren bu özellik nedeniyle popülerdir).


4

Bahsedilmeyen bir diğer neden ise, çöp orta sonuçları yaratmadan farklı filtreleri ve dönüşümleri birleştirme olasılığıdır.

Örneğin bunu ele alalım:

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

LINQ yöntemleri sonuçları hemen hesaplasaydı, 3 koleksiyonumuz olurdu:

  • Sonuç nerede
  • Sonuç seçin
  • Grup sonucu

Bunlardan sadece sonuncusunu önemsiyoruz. Orta sonuçları kaydetmenin bir anlamı yok, çünkü bunlara erişimimiz yok ve sadece yıllara göre filtrelenmiş ve gruplandırılmış arabalar hakkında bilmek istiyoruz.

Bu sonuçlardan herhangi birinin kaydedilmesine ihtiyaç duyulduysa, çözüm basittir: çağrıları ayırın ve arayın .ToList()ve bir değişkene kaydedin.


Yan not olarak, JavaScript'te, Array yöntemleri sonuçları hemen hemen döndürür, bu da dikkatli olmazsa daha fazla bellek tüketimine yol açabilir.


3

Temel olarak, bu kod - Guid.NewGuid ()bir Selectifade içine koymak - son derece şüphelidir. Bu kesinlikle bir tür kod kokusu!

Teorik olarak, bir Selectifadenin mutlaka yeni veriler yaratmasını değil, mevcut verileri almasını beklerdik . Select'in farklı şekillerde birleştirilmiş içerik üretmek veya hatta ek sütunlar hesaplamak için birden çok kaynaktan gelen verileri birleştirmesi makul olsa da, yine de işlevsel ve saf olmasını bekleyebiliriz. NewGuid ()İçini koymak, işlevsel olmayan ve saf olmayan hale getirir.

Verilerin oluşturulması, seçimden ayrılabilir ve bir tür yaratma işlemine sokulabilir, böylece seçim saf ve tekrar kullanılabilir kalabilir veya başka bir seçenek yalnızca bir kez yapılmalı ve sarılmalı / korunmalıdır - bu olduğu .ToList ()öneri.

Ancak, açık bir şekilde söylemek gerekirse, sorun bana önbellek eksikliğinden ziyade seçim içindeki yaratılışın karıştırılmasıdır. Seçimin NewGuid()içine koymak bana programlama modellerinin uygunsuz bir karışımı gibi geliyor.


0

Ertelenmiş yürütme, LINQ kodu yazanların (kesin olarak, kullanarak IEnumerable<T>) sonucun hemen hesaplanıp hafızada saklanıp saklanmayacağını açıkça seçmesine izin verir. Başka bir deyişle, programcıların uygulamalarına en uygun depolama alanı dengesine karşı hesaplama süresini seçmelerine olanak tanır.

Uygulamaların çoğunun sonuçları hemen istediği söylenebilir, bu nedenle LINQ'nun varsayılan davranışı olması gerekirdi. Ancak, List<T>.ConvertAllbu davranışı sunan ve Çerçeve oluşturulduğundan bu yana yapılan çok sayıda başka API (ör. ) Vardır, oysa LINQ tanıtılıncaya kadar yürütmeyi ertelemenin bir yolu yoktu. Bu, diğer cevapların gösterdiği gibi, derhal yürütme kullanılırken imkansız (tüm mevcut depolamayı tüketerek) bazı hesaplama türlerini etkinleştirmek için bir ön koşuldur.

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.