2011-08-05 20 views
11

Gần đây, tôi đã viết rất nhiều mã mà trông như thế này:Việc xóa các mục khỏi Danh sách C# <T> có giữ lại các đơn hàng khác không?

List<MyObject> myList = new List<MyObject>(); 
... 
for(int i = 0; i < myList.Count; ++i) 
{ 
    if(/*myList[i] meets removal criteria*/) 
    { 
    myList.RemoveAt(i); 
    --i; //Check this index again for the next item 
    //Do other stuff as well 
    } 
} 

và tôi chỉ trở thành một chút hoang tưởng rằng có lẽ Danh sách không giữ lại để đối tượng ở loại bỏ. Tôi không biết C# spec cũng đủ để biết chắc chắn. Ai đó có thể xác minh rằng tôi hoặc là sáng hoặc không yêu cầu sự cố với mẫu này?

EDIT: Có lẽ tôi nên làm rõ rằng ở trên là một ví dụ rất đơn giản và nhiều điều hơn xảy ra nếu mục cần phải được loại bỏ vì vậy tôi không nghĩ rằng List<T>.RemoveAll() là áp dụng khủng khiếp ở đây. Mặc dù nó là một chức năng tốt đẹp. Tôi đã thêm nhận xét trong khối if() ở trên để đề cập cụ thể điều đó.

+4

Một lưu ý phụ: Bạn nên lặp qua danh sách ngược thay vì chuyển tiếp. Giả sử bạn xóa danh sách [1] (i = 1). Điều này sẽ làm cho danh sách thay đổi, trong đó phần tử trong danh sách [2] hiện nằm trong danh sách [1]. Bây giờ khi bạn chuyển sang i = 2, bạn đã bỏ qua phần tử hiện nằm trong danh sách [1]. –

+1

Bạn sẽ có thể đơn giản hóa rất nhiều mẫu này bằng phương thức RemoveAll() –

+0

Tại sao bạn không sử dụng LINQ cho điều đó? – Andre

Trả lời

15

List<T> sẽ luôn luôn duy trì thứ tự tương đối khi thêm, chèn và xóa; nó sẽ không phải là một danh sách nếu nó không.

Đây là (ILSpy'ed) mã cho RemoveAt():

public void RemoveAt(int index) 
{ 
    if (index >= this._size) 
    { 
     ThrowHelper.ThrowArgumentOutOfRangeException(); 
    } 
    this._size--; 
    if (index < this._size) 
    { 
     Array.Copy(this._items, index + 1, this._items, index, this._size - index); 
    } 
    this._items[this._size] = default(T); 
    this._version++; 
} 

Note mảng sao chép index + 1-index; đó là các mặt hàng đang được bán buôn thay đổi và "ép" mảng lại với nhau. Nhưng chắc chắn không có sắp xếp lại các yếu tố.

+0

Ngoài cuộc trò chuyện bổ sung (rất hữu ích) về việc chạy qua danh sách ngược lại và xem xét 'RemoveAll()', câu trả lời này là câu hỏi tôi thực sự đã hỏi. Cảm ơn! – chaosTechnician

3

Từ Reflector:

public void RemoveAt(int index) 
{ 
    if (index >= this._size) 
    { 
     ThrowHelper.ThrowArgumentOutOfRangeException(); 
    } 
    this._size--; 
    if (index < this._size) 
    { 
     Array.Copy(this._items, index + 1, this._items, index, this._size - index); 
    } 
    this._items[this._size] = default(T); 
    this._version++; 
} 

Vì vậy, ít nhất là với MS' thực hiện - mục thứ tự không thay đổi trên RemoveAt.

2

Khi bạn gọi RemoveAt, tất cả các yếu tố theo chỉ mục bạn xóa sẽ được sao chép và dịch chuyển về phía trước.

Sắp xếp danh sách vị trí theo thứ tự giảm dần và xóa các phần tử theo thứ tự đó.

foreach (var position in positions.OrderByDescending(x=>x)) 
    list.RemoveAt(position); 

positions là danh sách chỉ mục. list là một trong những bạn muốn xóa từ (nó chứa dữ liệu thực tế).

10

Bạn thực sự đúng, List<T>.RemoveAt sẽ không thay đổi thứ tự của các mục trong danh sách.

đoạn của bạn tuy nhiên có thể được đơn giản hóa để sử dụng List<T>.RemoveAll như thế này:

List<MyObject> myList = new List<MyObject>(); 
... 
myList.RemoveAll(/* Removal predicate */); 

Chỉnh sửa nhận xét sau đây:

myList.Where(/* Removal predicate */).ToList().ForEach(/* Removal code */); 
myList.RemoveAll(/* Removal predicate */); 
+2

Điểm tốt. Không cần phải phát minh lại bánh xe :) +1 –

+0

Điều này cũng nên bảo vệ chống lại việc chuyển danh sách khi mỗi phần tử bị xóa (bỏ qua phần tử ngay sau phần tử đã bị xóa). Tôi muốn đề nghị di chuyển qua danh sách theo thứ tự ngược lại, nhưng anh ấy tốt hơn nhiều! –

+0

Tôi đã cập nhật câu hỏi ban đầu của mình để nói rằng kể từ khi tôi đang làm nhiều hơn chỉ đơn giản là loại bỏ các mục, tôi không nghĩ rằng 'RemoveAll()' sẽ làm các trick cho tôi. – chaosTechnician

4

Trình tự nên được duy trì.Một cách tiếp cận tốt hơn là để đi qua danh sách theo hướng ngược lại:

for(int i = myList.Count - 1; i >= 0; i--) 
{ 
    if(/*myList[i] meets removal criteria*/) 
    { 
     myList.RemoveAt(i); 
    } 
} 

Hoặc bạn có thể sử dụng RemoveAll method:

myList.RemoveAll(item => [item meets removal criteria]); 
+0

Tại sao điều này tốt hơn? –

+4

@Kyle đảo ngược cho vòng lặp là dễ dàng hơn để làm theo và không yêu cầu chỉ số được giảm đi một khi các tiêu chí được đáp ứng. Bằng cách bắt đầu ở cuối danh sách và làm việc ngược, vòng lặp for sẽ tiếp tục chính xác nếu một mục bị xóa. Đối với 'RemoveAll', nó là một lớp lót với một biến vị ngữ và không yêu cầu chúng ta lặp lại danh sách và quản lý chỉ mục. –

5

Mặc dù câu trả lời được chấp nhận là một câu trả lời tuyệt vời cho câu hỏi ban đầu, câu trả lời Cicada của cho thấy một sự thay thế tiếp cận.

Với CLR 4 (VS 2010), chúng tôi có được một cách tiếp cận khác, có lợi thế hơn là chỉ thực thi biến vị ngữ một lần cho mỗi mục (và làm cho nó thuận tiện để tránh viết vị từ hai lần trong mã của chúng tôi).

Giả sử bạn có một IEnumerable<string>:

IEnumerable<string> myList = new[] {"apples", "bananas", "pears", "tomatoes"}; 

Bạn cần phải chia nó thành hai danh sách theo dù các mục vượt qua một số tiêu chí:

var divided = myList.ToLookup(i => i.Length > 6); 

Đối tượng quay trở lại có phần giống như một Dictionary của danh sách. Giả sử bạn muốn giữ lại những cái mà vượt qua các tiêu chí:

myList = divided[true]; 

Và bạn có thể sử dụng một vòng lặp bắt buộc quen thuộc để hoạt động trên các mặt hàng khác:

foreach (var item in divided[false]) 
    Console.WriteLine("Removed " + item); 

Lưu ý rằng không có nhu cầu sử dụng List<T> đặc biệt. Chúng tôi không bao giờ sửa đổi danh sách hiện có - chúng tôi chỉ tạo danh sách mới.

+0

+1 - Điều này gọn gàng! Tôi không biết nó tồn tại. –

Các vấn đề liên quan