如何从嵌套方法“继续”ForEach循环?

时间:2011-05-05 21:07:32

标签: c# c#-4.0

我有一个ForEach循环来处理相当大的联系人列表。我没有在这个主循环中进行大量处理,而是调用一个方法,该方法又调用一个方法等。方法调用其他类中的其他方法,也许是其他名称空间。

如果检测到条件并且我想转到下一个联系人,如何从当前方法中断?我在主要ForEach循环的几个级别的方法中。

通常,您可以使用ForEach中的continue关键字跳转到集合中的下一个元素。在这种情况下,continue不是一个选项。当我输入continue时,它会以红色加下划线并带有“未解决的消息”注释。

那么,我该怎么办?

8 个答案:

答案 0 :(得分:21)

你在这里走的是一条糟糕的道路;退一步重新考虑你的设计。

一般来说,使用试图影响其调用者控制流的方法是一个非常糟糕的主意。方法是调用者的服务方,而不是主服务方。该方法不会决定呼叫者接下来会做什么;这不是它的业务。相反,一种方法:

  • 执行计算并返回结果,或
  • 修改某个州,然后
  • 如果操作无法成功完成,
  • 会抛出异常

有一些高级控制流样式,其中callees与调用者一起工作以确定“接下来会发生什么” - 例如,Continuation Passing Style。但你不应该去那里。他们很难理解。

答案 1 :(得分:6)

你的支持方法应该返回一个值,该值在main方法的for循环中检查,然后从那里continue,如果这是值所示的那个。

使用异常是另一种选择,但它们通常被认为较慢 - 我不太确定C#。当以这种方式使用时,它们通常也被认为是不良形式。应该在异常情况下抛出异常,而不是流量控制的正常部分。可以说有可能以这种方式使用它们,例如Web框架Play!,但你可能不在其中一个。

答案 2 :(得分:3)

你可能有一个标志......类似于bool conditionDetected,当检测到一个条件时你只需将它设置为true并从方法中获得if (conditionDetected) return;直到你到达顶部if( conditionDetected) continue到下一个...然后你再次将它设置为false并继续......你得到一个错误,因为当你转移到另一个方法时你不在foreach循环内部

答案 3 :(得分:2)

没有简单的方法来实现这一目标。您可以强制foreach循环的下一次迭代的唯一地方是直接来自它的正文。因此,为此,您必须退出方法并返回foreach循环的主体。

有几种方法可以实现这个目标

  • 抛出异常:请不要这样做。不应将例外用作控制流机制
  • 退出方法,以及您与foreach之间的所有方法循环,​​返回代码导致正文执行continue语句

答案 4 :(得分:2)

如果我正确理解你的问题,你就有这样的for循环:

for(int i = 0; i < 100; i++)
{
  DoComplexProcessing();
}

DoComplexProcessing然后调用另一个方法,该方法调用另一个方法,依此类推。

一旦你失败了,比如4级,你会发现一个条件(无论它是什么)并想要中止你DoComplexProcessing的迭代。

假设这是正确的,我要做的是有一个与方法链一起作为out参数的对象在每个级别,一旦找到“坏”条件,我将返回null(或者一些当null不是选项时的其他默认值)并将引用对象设置为意味着“中止”的状态。然后,每个方法将检查该“中止”状态,然后执行相同的“返回null,将对象设置为'中止'”调用。

这样的事情:

TracerObject tracer = new tracer("good");
for(int i = 0; i < 100; i++)
{
  DoComplexProcessing(out tracer)
  if(tracer.status == "abort") DoSomethingElse()
}

下一个方法可能会这样做

DoComplexProcessing(out TracerObject tracer)
{
   var myObject = new MyObject()
   myObject.Property = DoSlightlyLessComplexProcessing(myObject, out tracer)
   if(tracer.Status == "abort")
   {
     //set myObject.Property to some default value
   }
   return myObject;
}
}

答案 5 :(得分:1)

一种解决方案是抛出一个自定义异常,它会冒出来并最终被foreach循环捕获。

确保您使用的是自定义异常(即拥有自己的类型,而不只是使用catch(Exception)语句),这样您就知道自己肯定会抓住正确的异常。

catch块中,只需继续使用foreach循环(或适当处理)。

try {
    MethodWithIteration(i);
} catch (ProcessingFailedException) {
    continue;
}

如果由于某个特定原因导致失败,那么最好恰当地命名异常,这样就不会有用于控制应用程序流的异常类型,而且它实际上是有意义的。将来你可能希望处理这个问题。


foreach(DoSomethingWithMe doSomething in objList)
{
    // Option 1 : Custom Processing Exception
    try
    {
        ProcessWithException(doSomething);
    } catch(ProcessingFailedException)
    {
        // Handle appropriately and continue
        // .. do something ..
        continue;
    }

    // Option 2 : Check return value of processing
    if (!ProcessWithBool(doSomething))
        continue;

    // Option 3 : Simply continue on like nothing happened
    // This only works if your function is the only one called (may not work with deeply-nested methods)
    ProcessWithReturn(doSomething);
}

答案 6 :(得分:1)

为了扩展Erics的答案,解决方案是重构你的循环,以便外循环对它调用的长时间运行的方法有更多的控制和影响。

例如,假设您有“跳过”和“取消”按钮,让用户可以跳过合同,或者完全取消处理 - 您可以将代码修改为:

foreach (var contact in contacts)
{
    if (Cancel)
    {
        return;
    }
    ContactProcessor processor = new ContactProcessor(contact);
    processor.Process(contact);
}

class ContactProcessor
{
    public bool Skip { get; set; }

    private readonly Contact contact;
    public ContactProcessor(Contact contact)
    {
        this.contact = contact;
    }

    public void Process()
    {
        if (!this.Skip)
        {
            FooSomething();
        }
        if (!this.Skip)
        {
            BarSomething();
        }
        // Etc...
    }

    publiv void BarSomething()
    {
        // Stuff goes here
        if (this.contact.WTF())
        {
            this.Skip = true;
        }
    }
}

(显然有很多人要在这里充实)

我们的想法是,如果对处理的控制对您很重要,那么负责该处理的方法和类应该具有控制“烘焙”的机制。让一个完整的类负责处理,如果通常是一个很好的方法来封装它 - 这也使得报告进度的事情变得非常容易。

上面允许ContactProcessor的任何方法检测它是否应该跳过处理(不涉及异常!),并设置Skip标志。它还可能允许外部循环设置Skip标志(例如,基于用户输入)。

答案 7 :(得分:0)

使用continue你需要直接在循环中,所以你需要知道循环中的中断,但是你不能简单地从方法中返回一个bool吗?

int[] ints = {1,2,3,4,5,6};

foreach (var k in ints)
{
  if (Continue(k))
  {
       continue;
  }
}


bool Continue(int k)
{
    return what's suitable
}