我有一个ForEach循环来处理相当大的联系人列表。我没有在这个主循环中进行大量处理,而是调用一个方法,该方法又调用一个方法等。方法调用其他类中的其他方法,也许是其他名称空间。
如果检测到条件并且我想转到下一个联系人,如何从当前方法中断?我在主要ForEach循环的几个级别的方法中。
通常,您可以使用ForEach中的continue
关键字跳转到集合中的下一个元素。在这种情况下,continue
不是一个选项。当我输入continue
时,它会以红色加下划线并带有“未解决的消息”注释。
那么,我该怎么办?
答案 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
}