program story

목록에서 항목을 제거하는 지능적인 방법

inputbox 2020. 9. 23. 07:35
반응형

목록에서 항목을 제거하는 지능적인 방법 C #에서 열거하는 동안


루프에서 열거하는 동안 컬렉션에서 항목을 제거하려는 고전적인 경우가 있습니다.

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. 항목을 삭제하는 대신 항목에 플래그를 설정하고 비활성으로 표시하면됩니다. 그런 다음 패턴 1의 기능을 추가하여 목록을 정리하십시오.

    것이 내 요구 모두를 위해 작동하지만 그것은 것을 의미합니다 많은 코드가 비활성 플래그를 항목에 액세스 할 때마다 시간을 확인하기 위해 변경해야합니다. 이것은 내 취향에 비해 너무 많은 관리입니다.

  3. 어떻게 든 패턴 2의 아이디어를 List<T>. 이 수퍼리스트는 비활성 플래그, 팩트 이후의 객체 삭제를 처리하고 비활성으로 표시된 항목을 열거 소비자에게 노출하지 않습니다. 기본적으로 패턴 2 (및 이후 패턴 1)의 모든 아이디어를 캡슐화합니다.

    이와 같은 클래스가 있습니까? 누구든지 이것에 대한 코드가 있습니까? 아니면 더 좋은 방법이 있습니까?

  4. 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++;
  }
}

이 게시물이 오래되었다는 것을 알고 있지만 저에게 효과가있는 것을 공유 할 것이라고 생각했습니다.

Create a copy of the list for enumerating, and then in the for each loop, you can process on the copied values, and remove/add/whatever with the source list.

private void ProcessAndRemove(IList<Item> list)
{
    foreach (var item in list.ToList())
    {
        if (item.DeterminingFactor > 10)
        {
            list.Remove(item);
        }
    }
}

When you need to iterate through a list and might modify it during the loop then you are better off using a for loop:

for (int i = 0; i < myIntCollection.Count; i++)
{
    if (myIntCollection[i] == 42)
    {
        myIntCollection.Remove(i);
        i--;
    }
}

Of course you must be careful, for example I decrement i whenever an item is removed as otherwise we will skip entries (an alternative is to go backwards though the list).

If you have Linq then you should just use RemoveAll as dlev has suggested.


As you enumerate the list, add the one you want to KEEP to a new list. Afterward, assign the new list to the 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;

Let's add you code:

List<int> myIntCollection=new List<int>();
myIntCollection.Add(42);
myIntCollection.Add(12);
myIntCollection.Add(96);
myIntCollection.Add(25);

If you want to change the list while you're in a foreach, you must type .ToList()

foreach(int i in myIntCollection.ToList())
{
    if (i == 42)
       myIntCollection.Remove(96);
    if (i == 25)
       myIntCollection.Remove(42);
}

How about

int[] tmp = new int[myIntCollection.Count ()];
myIntCollection.CopyTo(tmp);
foreach(int i in tmp)
{
    myIntCollection.Remove(42); //The error is no longer here.
}

If you're interested in high performance, you can use two lists. The following minimises garbage collection, maximises memory locality and never actually removes an item from a list, which is very inefficient if it's not the last item.

private void RemoveItems()
{
    _newList.Clear();

    foreach (var item in _list)
    {
        item.Process();
        if (!item.NeedsRemoving())
            _newList.Add(item);
    }

    var swap = _list;
    _list = _newList;
    _newList = swap;
}

For those it may help, I wrote this Extension method to remove items matching the predicate and return the list of removed items.

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

참고URL : https://stackoverflow.com/questions/7193294/intelligent-way-of-removing-items-from-a-listt-while-enumerating-in-c-sharp

반응형