Tercih edilen yaklaşım okuma döngüsünü sonlandırmak için hangi yol?


13

Okunacak öğelerin sayısının bilinmediği bir okuyucu yinelemeniz gerektiğinde ve bunu yapmanın tek yolu, sonuna kadar okumaya devam etmektir.

Bu genellikle sonsuz bir döngüye ihtiyacınız olan yerdir.

  1. Her zaman vardır trueki olmalı gösterir breakveya returndeyim yere bloğunun içinde.

    int offset = 0;
    while(true)
    {
        Record r = Read(offset);
        if(r == null)
        {
            break;
        }
        // do work
        offset++;
    }
  2. Döngü için çift okuma yöntemi vardır.

    Record r = Read(0);
    for(int offset = 0; r != null; offset++)
    {
        r = Read(offset);
        if(r != null)
        {
            // do work
        }
    }
  3. Tek okuma döngüsü vardır. Tüm diller bu yöntemi desteklemez .

    int offset = 0;
    Record r = null;
    while((r = Read(++offset)) != null)
    {
        // do work
    }

Hangi yaklaşımın, en okunabilir ve yaygın olarak kullanılan bir hata getirme olasılığının düşük olduğunu merak ediyorum.

Bunlardan birini her yazmam gerektiğinde "daha iyi bir yol olmalı" diye düşünüyorum .


2
Neden ofseti koruyorsunuz? Çoğu Akış Okuyucu basitçe "sonrakini oku" ya izin vermez mi?
Robert Harvey

@RobertHarvey benim şimdiki ihtiyacı okuyucunun sonuçları sayfalandırmak için ofset kullanan temel bir SQL sorgusu vardır. Boş bir sonuç döndürene kadar sorgu sonuçlarının ne kadar uzun olduğunu bilmiyorum. Ancak, soru için bu gerçekten bir gereklilik değildir.
Reactgular

3
Şaşkın görünüyorsunuz - soru başlığı sonsuz döngülerle ilgili, ancak soru metni döngülerin sonlandırılmasıyla ilgili. Klasik çözüm (yapılandırılmış programlama günlerinden itibaren) bir ön okuma yapmak, verileriniz varken döngü yapmak ve döngüdeki son eylem olarak tekrar okumaktır. Çok basit (hata gereksinimini karşılayan), en yaygın olanı (50 yıldır yazıldığı için). En okunaklı bir görüş meselesidir.
andy256

@ andy256 karıştı benim için kahve öncesi durum.
Reactgular

1
Ah, bu yüzden doğru prosedür 1) Klavyeden kaçınırken kahve için, 2) Kodlama döngüsüne başlayın.
andy256

Yanıtlar:


49

Buraya bir adım atacağım. Kodun seçici ayrıntılarına yoğunlaşıyorsunuz ancak büyük resmi kaçırıyorsunuz. Örnek döngülerinize bir göz atalım:

int offset = 0;
while(true)
{
    Record r = Read(offset);
    if(r == null)
    {
        break;
    }
    // do work
    offset++;
}

Bu kodun anlamı nedir ? Anlamı "bir dosyadaki her kayıt için biraz çalışma". Ancak kod böyle görünmüyor . Kod "ofseti koru. Bir dosya açın. Son koşulu olmayan bir döngü girin. Bir kayıt okuyun. Boşluk testi yapın." Bütün bunlar işe başlamadan önce! Sormanız gereken soru şudur: " Bu kodun görünümünü anlamıyla eşleştirebilir miyim? " Bu kod şöyle olmalıdır:

foreach(Record record in RecordsFromFile())
    DoWork(record);

Şimdi kod niyetinde olduğu gibi okunur. Mekanizmalarınızı anlambiliminizden ayırın . Orijinal kodunuzda, mekanizmayı - döngünün ayrıntıları - anlambilim ile - her kayıt için yapılan işi karıştırırsınız.

Şimdi uygulamak zorundayız RecordsFromFile(). Bunu uygulamanın en iyi yolu nedir? Kimin umrunda? Kimsenin bakacağı kod bu değil. Temel mekanizma kodu ve on satır uzunluğunda. İstediğiniz gibi yazın. Buna ne dersin?

public IEnumerable<Record> RecordsFromFile()
{
    int offset = 0;
    while(true)
    {
        Record record = Read(offset);
        if (record == null) yield break;
        yield return record;
        offset += 1;
    }
}

Artık tembel olarak hesaplanmış bir kayıt dizisini manipüle ettiğimize göre, her türlü senaryo mümkün hale geliyor:

foreach(Record record in RecordsFromFile().Take(10))
    DoWork(record);

foreach(Record record in RecordsFromFile().OrderBy(r=>r.LastName))
    DoWork(record);

foreach(Record record in RecordsFromFile().Where(r=>r.City == "London")
    DoWork(record);

Ve bunun gibi.

Bir döngü yazdığınızda, kendinize "bu döngü bir mekanizma veya kodun anlamı gibi mi okuyor?" Diye sorun. Cevap "bir mekanizma gibi" ise, o mekanizmayı kendi yöntemine taşımaya çalışın ve anlamı daha görünür hale getirmek için kodu yazın.


3
+1 sonunda mantıklı bir cevap. Ben de tam olarak bunu yapacağım. Teşekkürler.
Aralık'ta Reaktif

1
"Bu mekanizmayı kendi yöntemine taşımaya çalışın" - bu, Extract Method yeniden düzenleme gibi görünüyor değil mi? "Parçayı, adı yöntemin amacını açıklayan bir yönteme dönüştürün."
gnat

2
@gnat: Ben önermek ne sadece kod bir yığın başka bir yere taşımak gibi düşünüyorum "özü yöntemi", biraz daha fazla dahil. Yöntemleri çıkarmak, kodun anlambilimi gibi daha fazla okunmasını sağlamak için kesinlikle iyi bir adımdır. Yöntem çıkarmanın, politikaları ve mekanizmaları birbirinden ayırmaya yönelik düşünülerek yapılmasını öneriyorum.
Eric Lippert

1
@gnat: Kesinlikle! Bu durumda ayıklamak istediğim ayrıntı, ilkeyi korurken tüm kayıtların dosyadan okunma mekanizmasıdır. Politika "her kayıtta biraz iş yapmak zorundayız" dır.
Eric Lippert

1
Anlıyorum. Bu şekilde, okunması ve bakımı daha kolaydır. Bu kodu inceleyerek, politika ve mekanizmaya ayrı ayrı odaklanabiliyorum, beni dikkatimi bölmeye
zorlamıyor

19

Sonsuz bir döngüye ihtiyacınız yok. C # okuma senaryolarında asla birine ihtiyacınız olmamalıdır. Bu, gerçekten bir ofset tutmanız gerektiğini varsayarak, tercih ettiğim yaklaşımdır:

Record r = Read(0);
offset=1;
while(r != null)
{
    // Do work
    r = Read(offset);
    offset++
}

Bu yaklaşım, okuyucu için bir kurulum adımı olduğunu kabul eder, bu nedenle iki okuma yöntemi çağrısı vardır. Durum while, okuyucuda hiç veri olmaması durumunda, döngünün üstündedir.


6

Durumunuza bağlıdır. Ancak aklıma gelen daha "C # -ish" çözümlerinden biri de yerleşik IEnumerable arabirimini ve bir foreach döngüsünü kullanmaktır. IEnumerator arabirimi yalnızca MoveNext'i true veya false ile çağırır, bu nedenle boyut bilinmiyor. Ardından sonlandırma mantığınız bir kez - numaralandırıcıda - yazılır ve birden fazla noktada tekrarlamanız gerekmez.

MSDN, bir IEnumerator <T> örneği sağlar . Ayrıca, IEnumerator <T> öğesini döndürmek için bir IEnumerable <T> oluşturmanız gerekir.


Bir kod örneği verebilir misiniz?
Reactgular

teşekkürler, yapmaya karar verdim. Her seferinde sonsuz bir döngüye sahip olduğunuzda yeni sınıflar oluşturmayı düşünmüyorum, ancak sorunu çözer.
Reactgular

Evet, ne demek istediğini biliyorum - çok fazla yük. Yineleyicilerin gerçek dehası / problemi, bir koleksiyon üzerinde aynı anda iki şeyi tekrarlayabilecek şekilde ayarlanmış olmalarıdır. Bunu o kadar çok kez bu işlevselliği gerekmez olabilir sadece nesneleri IEnumerable ve IEnumerator hem uygulamak etrafında sarıcı var. Bakmanın başka bir yolu, yinelediğiniz temeldeki koleksiyonun kabul edilen C # paradigması göz önünde bulundurularak tasarlanmamış olmasıdır. Ve sorun deđil. Yineleyicilerin ek bir avantajı, tüm paralel LINQ öğelerini ücretsiz olarak alabilmenizdir!
J Trana

2

Başlatma, koşul ve artış işlemleri olduğunda C, C ++ ve C # gibi dillerin döngülerini kullanmayı seviyorum. Bunun gibi:

for (int offset = 0, Record r = Read(offset); r != null; r = Read(++offset)){
    // loop here!
}

Ya da bunun daha okunabilir olduğunu düşünüyorsanız. Ben şahsen ilkini tercih ederim.

for (int offset = 0, Record r = Read(offset); r != null; offset++, r = Read(offset)){
    // loop here!
}
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.