루프에서 열거하는 동안 컬렉션에서 항목을 제거하려는 고전적인 경우가 있습니다.
List<int> myIntCollection = new List<int>();
myIntCollection.Add(42);
myIntCollection.Add(12);
myIntCollection.Add(96);
myIntCollection.Add(25);
foreach (int i in myIntCollection)
{
if (i == 42)
myIntCollection.Remove(96); // The error is here.
if (i == 25)
myIntCollection.Remove(42); // The error is here.
}
변경이 발생한 후 반복이 시작될 InvalidOperationException
때 열거자가 기본 컬렉션이 변경되는시기를 좋아하지 않기 때문에가 발생합니다.
반복하는 동안 컬렉션을 변경해야합니다. 이를 방지하는 데 사용할 수있는 패턴 이 많이 있지만 그중 어느 것도 좋은 해결책이없는 것 같습니다.
-
이 루프 내부를 삭제하지 말고 대신 메인 루프 이후에 처리하는 별도의 “삭제 목록”을 유지하십시오.
이것은 일반적으로 좋은 해결책이지만 제 경우에는 항목을 실제로 삭제하기 위해 항목을 실제로 삭제하기 위해 코드의 논리 흐름이 변경 될 때까지 항목이 “대기”상태로 즉시 사라져야합니다.
-
항목을 삭제하는 대신 항목에 플래그를 설정하고 비활성으로 표시하면됩니다. 그런 다음 패턴 1의 기능을 추가하여 목록을 정리하십시오.
이 것이 내 요구 모두를 위해 작동하지만 그것은 것을 의미합니다 많은 코드가 비활성 플래그를 항목에 액세스 할 때마다 시간을 확인하기 위해 변경해야합니다. 이것은 내 취향에 비해 너무 많은 관리입니다.
-
어떻게 든 패턴 2의 아이디어를
List<T>
. 이 수퍼리스트는 비활성 플래그, 팩트 이후의 객체 삭제를 처리하고 비활성으로 표시된 항목을 열거 소비자에게 노출하지 않습니다. 기본적으로 패턴 2 (및 이후 패턴 1)의 모든 아이디어를 캡슐화합니다.이와 같은 클래스가 있습니까? 누구든지 이것에 대한 코드가 있습니까? 아니면 더 좋은 방법이 있습니까?
-
myIntCollection.ToArray()
대신 액세스myIntCollection
하면 문제가 해결되고 루프 내부에서 삭제할 수 있다고 들었습니다 .이것은 나에게 나쁜 디자인 패턴처럼 보이거나 괜찮을까요?
세부:
-
목록에는 많은 항목이 포함되며 그중 일부만 제거합니다.
-
루프 내에서 모든 종류의 프로세스, 추가, 제거 등을 수행 할 것이므로 솔루션은 상당히 일반적이어야합니다.
-
삭제해야하는 항목이 루프의 현재 항목 이 아닐 수 있습니다. 예를 들어, 30 개 항목 루프의 항목 10에 있고 항목 6 또는 항목 26을 제거해야 할 수 있습니다.이 때문에 배열을 뒤로 걸어가는 작업이 더 이상 작동하지 않습니다. ;영형(
답변
가장 좋은 해결책은 일반적으로 다음 RemoveAll()
방법 을 사용하는 것입니다.
myList.RemoveAll(x => x.SomeProp == "SomeValue");
또는 특정 요소를 제거 해야하는 경우 :
MyListType[] elems = new[] { elem1, elem2 };
myList.RemoveAll(x => elems.Contains(x));
물론 루프가 제거 목적으로 만 사용된다고 가정합니다. 추가 처리 가 필요한 경우 열거자를 사용하지 않기 때문에 일반적으로 for
또는 while
루프 를 사용하는 것이 가장 좋은 방법입니다 .
for (int i = myList.Count - 1; i >= 0; i--)
{
// Do processing here, then...
if (shouldRemoveCondition)
{
myList.RemoveAt(i);
}
}
뒤로 이동하면 요소를 건너 뛰지 않습니다.
편집에 대한 응답 :
겉보기에 임의의 요소를 제거하려는 경우 가장 쉬운 방법은 제거하려는 요소를 추적 한 다음 한 번에 모두 제거하는 것입니다. 이 같은:
List<int> toRemove = new List<int>();
foreach (var elem in myList)
{
// Do some stuff
// Check for removal
if (needToRemoveAnElement)
{
toRemove.Add(elem);
}
}
// Remove everything here
myList.RemoveAll(x => toRemove.Contains(x));
답변
a를 열거 List<T>
하고 제거 해야한다면 a while
대신 루프를 사용하는 것이 좋습니다 .foreach
var index = 0;
while (index < myList.Count) {
if (someCondition(myList[index])) {
myList.RemoveAt(index);
} else {
index++;
}
}
답변
이 게시물이 오래되었다는 것을 알고 있지만 저에게 효과가있는 것을 공유 할 것이라고 생각했습니다.
열거 할 목록의 복사본을 만든 다음 for each 루프에서 복사 된 값을 처리하고 소스 목록을 사용하여 제거 / 추가 / 무엇이든 할 수 있습니다.
private void ProcessAndRemove(IList<Item> list)
{
foreach (var item in list.ToList())
{
if (item.DeterminingFactor > 10)
{
list.Remove(item);
}
}
}
답변
목록을 반복해야하고 루프 중에 수정할 수있는 경우 for 루프를 사용하는 것이 좋습니다.
for (int i = 0; i < myIntCollection.Count; i++)
{
if (myIntCollection[i] == 42)
{
myIntCollection.Remove(i);
i--;
}
}
물론주의해야합니다. 예를 들어 i
항목이 제거 될 때마다 감소합니다. 그렇지 않으면 항목을 건너 뜁니다 (대안은 목록을 뒤로 이동하는 것입니다).
Linq가 있다면 RemoveAll
dlev가 제안한대로 사용해야합니다.
답변
목록을 열거 할 때 보관할 목록을 새 목록에 추가합니다. 그런 다음 새 목록을myIntCollection
List<int> myIntCollection=new List<int>();
myIntCollection.Add(42);
List<int> newCollection=new List<int>(myIntCollection.Count);
foreach(int i in myIntCollection)
{
if (i want to delete this)
///
else
newCollection.Add(i);
}
myIntCollection = newCollection;
답변
코드를 추가해 보겠습니다.
List<int> myIntCollection=new List<int>();
myIntCollection.Add(42);
myIntCollection.Add(12);
myIntCollection.Add(96);
myIntCollection.Add(25);
foreach에있는 동안 목록을 변경하려면 다음을 입력해야합니다. .ToList()
foreach(int i in myIntCollection.ToList())
{
if (i == 42)
myIntCollection.Remove(96);
if (i == 25)
myIntCollection.Remove(42);
}
답변
도움이 될 수있는 사람들을 위해이 Extension 메서드를 작성하여 조건 자와 일치하는 항목을 제거하고 제거 된 항목 목록을 반환합니다.
public static IList<T> RemoveAllKeepRemoved<T>(this IList<T> source, Predicate<T> predicate)
{
IList<T> removed = new List<T>();
for (int i = source.Count - 1; i >= 0; i--)
{
T item = source[i];
if (predicate(item))
{
removed.Add(item);
source.RemoveAt(i);
}
}
return removed;
}