กำลังสร้างรายการใหม่เพื่อปรับเปลี่ยนคอลเลกชันในแต่ละลูปสำหรับข้อบกพร่องในการออกแบบหรือไม่


14

เมื่อเร็ว ๆ นี้ฉันได้พบกับการดำเนินการที่ไม่ถูกต้องทั่วไปCollection was modifiedใน C # และในขณะที่ฉันเข้าใจอย่างเต็มที่ดูเหมือนว่าจะเป็นปัญหาที่พบบ่อย (google ผลลัพธ์ประมาณ 300k!) แต่มันก็ดูเหมือนจะเป็นตรรกะและตรงไปตรงมาในการปรับเปลี่ยนรายการในขณะที่คุณทำมัน

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

บางคนจะสร้างรายการใหม่เพื่อทำซ้ำ แต่นี่เป็นเพียงการหลีกเลี่ยงปัญหา ทางออกที่แท้จริงคืออะไรหรือปัญหาการออกแบบจริงที่นี่คืออะไร เราทุกคนต้องการทำสิ่งนี้ แต่มันบ่งบอกถึงข้อบกพร่องในการออกแบบหรือไม่?


คุณสามารถใช้ตัววนซ้ำหรือลบออกจากส่วนท้ายของรายการ
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#) statementสาย
SJuan76

ใน Java มีIterator(ทำงานตามที่คุณอธิบาย) และListIterator(ทำงานตามที่คุณต้องการ) นี้จะชี้ว่าเพื่อให้การทำงาน iterator ช่วงกว้างของประเภทคอลเลกชันและการใช้งานIteratorเป็นที่เข้มงวดมาก (เกิดอะไรขึ้นถ้าคุณลบโหนดในต้นไม้สมดุลหรือไม่next()เข้าท่าอีกต่อไป) กับListIteratorการเป็นมีประสิทธิภาพมากขึ้นเนื่องจากมีน้อย ใช้กรณี
SJuan76

@ SJuan76 ในภาษาอื่นยังมีตัววนซ้ำอีกประเภทเช่น C ++ มีตัววนซ้ำไปข้างหน้า (เทียบเท่ากับ Iterator ของ Java), ตัววนซ้ำสองทิศทาง (เช่น ListIterator), ตัววนซ้ำการเข้าถึงแบบสุ่ม, ตัววนซ้ำอินพุต (เช่น Java Iterator ) และตัววนซ้ำผลลัพธ์ (เช่นเขียนอย่างเดียวซึ่งมีประโยชน์มากกว่าเสียง)
จูลส์

คำตอบ:


10

กำลังสร้างรายการใหม่เพื่อปรับเปลี่ยนคอลเลกชันในแต่ละลูปสำหรับข้อบกพร่องในการออกแบบหรือไม่

คำตอบสั้น ๆ : ไม่

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

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

ที่มา: MSDN

โดยวิธีการที่คุณสามารถย่อRemoveAllBooksให้ง่ายreturn new List<Book>()

และเพื่อลบหนังสือฉันขอแนะนำให้ส่งคืนคอลเล็กชันที่กรองแล้ว:

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

ชั้นวางที่เป็นไปได้จะมีลักษณะดังนี้:

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

คุณขัดแย้งกับตัวเองเมื่อคุณตอบว่าใช่แล้วให้ตัวอย่างซึ่งสร้างรายการใหม่ด้วย นอกเหนือจากที่ฉันเห็นด้วย
Esben Skov Pedersen

1
@EsbenSkovPedersen คุณพูดถูก: DI กำลังคิดที่จะแก้ไขข้อบกพร่อง ...
Thomas Junk

6

การสร้างรายการใหม่เพื่อแก้ไขเป็นทางออกที่ดีสำหรับปัญหาที่ตัววนซ้ำที่มีอยู่ของรายการไม่สามารถดำเนินการต่อได้อย่างน่าเชื่อถือหลังจากการเปลี่ยนแปลงเว้นแต่พวกเขารู้ว่ารายการมีการเปลี่ยนแปลงอย่างไร

วิธีแก้ไขอีกวิธีหนึ่งคือการปรับเปลี่ยนโดยใช้ตัววนซ้ำแทนผ่านส่วนต่อประสานรายการเอง ใช้งานได้หากมีเพียงตัววนซ้ำเดียวเท่านั้น - มันไม่ได้แก้ปัญหาสำหรับหลายเธรดที่วนซ้ำในรายการพร้อมกันซึ่ง (ตราบเท่าที่ยอมรับการเข้าถึงข้อมูลเก่า) สร้างรายการใหม่

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

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

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