Her döngü için bir koleksiyonu değiştirmek için yeni bir Liste oluşturma bir tasarım hatası mı?


14

Son zamanlarda Collection was modifiedC # bu yaygın geçersiz işlem koştu ve ben tam olarak anlamak iken, böyle yaygın bir sorun gibi görünüyor (google, yaklaşık 300k sonuçları!). Ancak, listeyi gözden geçirirken değiştirmek de mantıklı ve anlaşılır bir şey gibi görünüyor.

List<Book> myBooks = new List<Book>();

public void RemoveAllBooks(){
    foreach(Book book in myBooks){
         RemoveBook(book);
    }
}

RemoveBook(Book book){
    if(myBooks.Contains(book)){
         myBooks.Remove(book);
         if(OnBookEvent != null)
            OnBookEvent(this, new EventArgs("Removed"));
    }
}

Bazı insanlar yinelemek için başka bir liste oluşturacak, ancak bu sadece sorunu atlatıyor. Gerçek çözüm nedir veya burada asıl tasarım sorunu nedir? Hepimiz bunu yapmak istiyor gibiyiz , ama bu bir tasarım kusurunun göstergesi mi?


Yineleyici kullanabilir veya listenin sonundan kaldırabilirsiniz.
ElDuderino

@ElDuderino msdn.microsoft.com/en-us/library/dscyy5s0.aspx . You consume an iterator from client code by using a For Each…Next (Visual Basic) or foreach (C#) statementHattı kaçırmayın
SJuan76

Java'da Iterator(tanımladığınız gibi çalışır) ve ListIterator(istediğiniz gibi çalışır) vardır. Bu, çok çeşitli koleksiyon türleri ve uygulamaları üzerinde yineleyici işlevselliği sağlamak için Iterator, son derece kısıtlayıcı (dengeli bir ağaçtaki bir düğümü silerseniz ne olur? next()Artık mantıklıdır), ListIteratordaha az olması nedeniyle daha güçlü olduğu anlamına gelir. kullanım örnekleri.
SJuan76

@ SJuan76 diğer dillerde hala daha fazla yineleyici türü vardır, örneğin C ++ ileri yineleyiciler (Java'nın Yineleyicisine eşdeğer), çift yönlü yineleyiciler (ListIterator gibi), rastgele erişim yineleyicileri, giriş yineleyicileri (isteğe bağlı işlemleri desteklemeyen bir Java Yineleyici gibi) vardır ) ve çıkış yineleyicileri (yani, yalnızca yazıldığından daha kullanışlı olan).
Jules

Yanıtlar:


10

Her döngü için bir koleksiyonu değiştirmek için yeni bir Liste oluşturma bir tasarım hatası mı?

Kısa cevap: hayır

Basitçe söylemek gerekirse , bir koleksiyon boyunca yineleme yaptığınızda ve aynı anda değiştirdiğinizde tanımsız davranışlar üretersiniz. Düşünün silmenext bir sırayla elemanı. Eğer MoveNext()çağrılırsa ne olur ?

Bir numaralandırıcı koleksiyon değişmeden kaldığı sürece geçerliliğini korur. Koleksiyonda öğe ekleme, değiştirme veya silme gibi değişiklikler yapılırsa numaralandırıcı geri döndürülemez şekilde geçersiz kılınır ve davranışı tanımlanmamıştır. Sayıcının koleksiyona özel erişimi yoktur; bu nedenle, bir koleksiyon aracılığıyla numaralandırma özünde güvenli bir prosedür değildir.

Kaynak: MSDN

Bu arada, RemoveAllBookssadece kısaltmak içinreturn new List<Book>()

Bir kitabı kaldırmak için, filtrelenmiş bir koleksiyon döndürmenizi öneririz:

return books.Where(x => x.Author != "Bob").ToList();

Olası bir raf uygulaması şöyle görünecektir:

public class Shelf
{
    List<Book> books=new List<Book> {
        new Book ("Paul"),
        new Book ("Peter")
    };

    public IEnumerable<Book> getAllBooks(){
        foreach(Book b in books){
            yield return b;
        }
    }

    public void RemovePetersBooks(){
        books= books.Where(x=>x.Author!="Peter").ToList();
    }

    public void EmptyShelf(){
        books = new List<Book> ();
    }

    public Shelf ()
    {
    }
}

public static void Main (string[] args)
{
    Shelf s = new Shelf ();
    foreach (Book b in s.getAllBooks()) {
        Console.WriteLine (b.Author);
    }
    s.RemovePetersBooks ();

    foreach (Book b in s.getAllBooks()) {
        Console.WriteLine (b.Author);
    }
    s.EmptyShelf ();
    foreach (Book b in s.getAllBooks()) {
        Console.WriteLine (b.Author);
    }
}

Evet yanıtı verdiğinizde kendinizle çelişirsiniz ve ardından yeni bir liste oluşturan bir örnek sunarsınız. Bunun dışında katılıyorum.
Esben Skov Pedersen

1
@EsbenSkovPedersen haklısın: DI bir kusur olarak değiştirmeyi düşünüyordu ...
Thomas Junk

6

Listeyi değiştirmek için yeni bir liste oluşturmak, listenin mevcut yineleyicilerinin, listenin nasıl değiştiğini bilmedikçe değişiklikten sonra güvenilir bir şekilde devam edememesi sorununa iyi bir çözümdür.

Diğer bir çözüm de modifikasyonları liste arayüzünden ziyade yineleyici kullanarak yapmaktır. Bu yalnızca tek bir yineleyici varsa çalışır - aynı anda liste üzerinde yinelenen birden çok iş parçacığı sorununu çözmez, bu da (eski verilere erişim kabul edilebilir olduğu sürece) yeni bir liste oluşturur.

Çerçeve uygulayıcılarının alabileceği alternatif bir çözüm, listenin tüm yineleyicilerini izlemesini ve değişiklik meydana geldiğinde bunları bilgilendirmesini sağlamaktır. Bununla birlikte, bu yaklaşımın genel giderleri yüksektir - genel olarak birden çok iş parçacığıyla çalışması için, tüm yineleyici işlemlerinin listeyi kilitlemesi gerekir (işlem devam ederken değişmemesini sağlamak için), bu da tüm listeyi yapar operasyonlar yavaş. Önemsiz bellek yükü de olacaktı, ayrıca çalışma zamanının yumuşak referansları desteklemesi gerekiyor ve IIRC, CLR'nin ilk sürümü yoktu.

Farklı bir perspektiften, listeyi değiştirmek için kopyalamak, LINQ'u, genellikle liste üzerinde doğrudan yinelenen IMO'dan daha net bir kodla sonuçlanan değişikliği belirtmek için kullanmanızı sağlar.

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.