替换列表中的项目 - 到位

时间:2018-03-02 15:16:00

标签: c# list enumerator

我遇到了一些简单的C#代码问题,我很容易在C / C ++中修复它。 我想我错过了一些东西。

我想执行以下操作(修改列表中的项目):

//pseudocode
void modify<T>(List<T> a) {
  foreach(var item in a) {
    if(condition(item)) {
      item = somethingElse;
    }
  }
}

我理解foreach循环集合被视为不可变,因此上面的代码无法工作。

因此,我尝试了以下内容:

void modify<T>(List<T> a) {
  using (var sequenceEnum = a.GetEnumerator())
  {
    while (sequenceEnum.MoveNext())
    {
      var m = sequenceEnum.Current;
      if(condition(m)) {
         sequenceEnum.Current = somethingElse;
      }
    }
  }
}

天真地认为Enumerator是我的Element的某种指针。显然,调查员也是不可改变的。

在C ++中我会写这样的东西:

template<typename T>
struct Node {
    T* value;
    Node* next;
}

然后能够修改*value而不触及Node中的任何内容,因此在父集合中:

Node<T>* current = a->head;
while(current != nullptr) {
    if(condition(current->value)) 
        current->value = ...
    }
    current = current->next;
}

我真的不得不使用不安全的代码吗?

或者我是否在循环中调用下标的可怕性?

4 个答案:

答案 0 :(得分:4)

您还可以使用简单的for循环。

void modify<T>(List<T> a)
{
    for (int i = 0; i < a.Count; i++)
    {
        if (condition(a[i]))
        {
            a[i] = default(T);
        }
    }
}

答案 1 :(得分:2)

简而言之 - 不要修改列表。你可以用

达到预期的效果
a = a.Select(x => <some logic> ? x : default(T)).ToList()

一般来说,C#中的列表在迭代期间是不可变的。你可以继续使用。删除所有或类似的方法。

答案 2 :(得分:1)

使用类似的东西:

List<T> GetModified<T>(List<T> list, Func<T, bool> condition, Func<T> replacement)
{
   return list.Select(m => if (condition(m)) 
                        { return m; }
                       else
                        { return replacement(); }).ToList();
}

用法:

originalList = GetModified(originalList, i => i.IsAwesome(), null);

但这也会让您在跨线程操作中遇到麻烦。尝试尽可能使用不可变实例,尤其是使用IEnumerable。

如果你真的想修改list的实例:

//if you ever want to also remove items, this is magic (why I iterate backwards)
for (int i = list.Count - 1; i >= 0; i--)
{
   if (condition)
   {
     list[i] = whateverYouWant;
   }
}

答案 3 :(得分:1)

如文档here中所述,System.Collections.Generic.List<T>System.Collections.ArrayList的通用实现,在索引访问器中具有O(1)复杂性。非常像C ++ std::vector<>,插入/添加元素的复杂性是不可预测的,但访问是时间常数(复杂性方面,关于缓存等)。

您的C ++代码段的等效内容为LinkedList

至于您的集合在迭代过程中的不变性,GetEnumerator方法here的文档中明确说明了这一点。实际上,在枚举期间(foreach内,或直接使用IEnumerator.MoveNext):

  

枚举器可用于读取集合中的数据,但它们   不能用于修改底层集合。

此外,修改列表将使枚举器无效并且通常会抛出异常:

  

只要收集仍然存在,枚举器仍然有效   不变。如果对集合进行了更改,例如添加,   修改或删除元素,枚举器是不可恢复的   无效,其行为未定义。

我相信各种类型的集合之间的接口契约一致性会导致您的误解:它可能实现一个可变列表,但接口契约不需要它。

想象一下,您希望实现一个在枚举期间可变的列表。枚举器是否会引用(或检索)条目或条目本身?条目本身将使其不可变,例如,在链接列表中插入元素时,引用将无效。

如果你想使用标准的Collections库,@ Igor提出的简单的for循环似乎是最好的方法。否则,您可能需要自己重新实现它。