如何在C#中打破`RemoveAll()`语句

时间:2015-06-25 14:36:38

标签: c#

我正在使用RemoveAll()语句,该语句执行列表的foreach元素,并且基于委托返回的条件,它会从列表中删除元素。像这样:

x.RemoveAll(delegate(string y)
            {
               if (y == "abc")
                   return true;

               return false;
            });

我想从removeAll中断foreach,以便在满足某些条件时,我甚至不再尝试删除元素。像这样:

x.RemoveAll(delegate(string y)
            {
               if (Foo() || Bar())
                   break; //stop trying to remove elements

               if (y == "abc")
                   return true;

               return false;
            });

有没有辅助变量的方法呢?

P.S:使用辅助变量我知道该怎么做。

6 个答案:

答案 0 :(得分:3)

有两个真正的选择。一个变量,告诉您不要删除更多项目:

var done = false;
list.RemoveAll(item => 
{
    if(done) return false;
    if(Foo() || Bar())
    {
        done = true;
        return false;
    }
    return item == "abc";
}

或抛出异常(尽管使用控制流异常实际上是不好的做法)。

list.RemoveAll(item => 
{
    if(Foo() || Bar())
        throw new SomeTypeOfException()
    return item == "abc";
}

如果Foo或Bar真的确实是异常/错误的情况,那么也许你可以证明它是合理的,但它看起来似乎是代码味道。请注意,这在技术上将是唯一使用RemoveAll的方式,而不是在任何后续项目上实际调用委托。

根本问题是,您尝试执行的操作与RemoveAll设计的操作不一致。您真正想要的是一种方法版本,该方法支持取消或对列表内部的充分访问,以创建具有适当取消的可比方法。遗憾的是,您无法访问基础数组,以便能够复制RemoveAll移除多个项目的能力,而不会将所有项目移动到最后,除非您重新创建自己的项目基于整个列表的结构。

答案 1 :(得分:1)

使用break的简单循环将比使用带有触发器的DeleteAll更有效,在某个点之后为所有元素生成false。

为什么不先过滤:

foreach(var item in x.Where(o => Foo(o)).ToList())
    x.Remove(item);

如果你关心效率,那么

for(int i = 0; i++; i < x.Length)
    if(Foo(x[i]))
    {
        x.RemoveAt(i);
        break; // if list is sorted
    }
    else
        i++;

对于未分类的列表,从上到下afaik更为理想。

答案 2 :(得分:1)

您可以使用扩展方法执行此操作:

public static IEnumerable<T> RemoveAllUntil<T>(
    this IEnumerable<T> input, 
    Predicate<T> match, 
    Predicate<T> until)
{
    bool untilFound = false;

    foreach (T element in input)
    {
        if(!untilFound) untilFound = until(element);

        if(untilFound || !match(element))
        {
            yield return element;
        }
    }
}

并像这样使用它:

var strings = new List<string> { "s1", "s2", "s2", "break", "s2", "s3"};
strings = strings.RemoveAllUntil(
        s => s == "s2", 
        s => s == "break")
    .ToList();

这会给你:

  

s1,break,s2,s3

答案 3 :(得分:1)

长评论:删除项目的近似代码,取消并避免多次复制尾部:

void RemoveWithCancelation(this List<T> list, 
     Func<RemoveWithCancelationResult> predicate)
{
   var indexToKeep = -1;

   for (var i = 0; i < list.Count; i++)
   {
      var condition = predicate(list[i]);
      if (condition.Cancel)
          break;
      if (!condition.RemoveItem)
      {
          indexToKeep++;
          list[indexToKeep] = list[i];
      }   
   }
   if (indexToKeep+1 < list.Count)
       list.RemoveRange(indexToKeep+1, list.Count);
}

答案 4 :(得分:0)

我认为这不能在RemoveAll()

中完成

但是,您可以使用TakeWhile()“模拟”中断,然后使用Where()过滤列表

 var newList = x.TakeWhile(elem => elem != "your condition").Where(elem => elem == "abc");

答案 5 :(得分:-1)

你可以利用闭包:

bool stop = false; // the delegate closes over 'stop'
x.RemoveAll(delegate(string y)
{
   if (!stop && y == "abc")
       return true;
   if (Foo() || Bar())
       stop = true;
   return false;
});