2012年05月28日

C#で多重ループを脱出したい

2013/04/06記事修正
LILACさんのコメントをもとにサンプルコードを修正しました。

本日のお題は多重ループからの脱出です。C#でループを脱出するときはbreakを使いますが、breakは直近のループしか抜けられません。


こんな場合はどうしましょう?
private void DoSomething()
{
for (int i = 0; i < 10; i++)
{
for (int j = 0; j < 10; j++)
{
if (true)
{
//何かの条件によって・・・
}
}
}
//ループを脱出してここに飛びたい!

//後続処理
}



最善策


まず、最善策は多重ループ部分を別関数に切り出すことです。特にリスクやデメリットも見当たりません。


別関数に切り出す
private void DoSomething()
{
NestedLoopMethod();
//ループを脱出してここに飛びたい!

//後続処理
}

private void NestedLoopMethod()
{
for (int i = 0; i < 10; i++)
{
for (int j = 0; j < 10; j++)
{
if (true)
{
//何かの条件によって・・・
return;
}
}
}
}

別関数に切り出せれば最善ですが、それができるとも限りません。別関数の引数が多くなって嫌だなぁ、とか、論理的にここを切り出すべきではないなぁ、とかいうこともあります。そこで次善策を考えてみます。


何の工夫もないフラグ


とりあえずベタな書き方で実装してみましょう。


何も考えずフラグで
private void DoSomething()
{
var isExitLoop = false;
for (int i = 0; i < 10; i++)
{
for (int j = 0; j < 10; j++)
{
if (true)
{
//何かの条件によって・・・
isExitLoop = true;
}
if (isExitLoop)
{
break;
}

}
if (isExitLoop)
{
break;
}

}
//ループを脱出してここに飛びたい!

//後続処理
}

isExitLoopというフラグを見てループを脱出します。このやり方の嫌なところは、isExitLoopのスコープが関数全域に広がってること。for文の中でしか使われないのに。条件判定をループごとにやるのもウザいです。


フラグをfor文の中に閉じ込める


ということで次はisExitLoopをfor文の中に閉じ込めてみます。


フラグをfor文の中に閉じ込める
private void DoSomething()
{
for (int i = 0, isExitLoop = 0; i < 10 && isExitLoop == 0; i++)
{
for (int j = 0; j < 10 && isExitLoop == 0; j++)
{
if (true)
{
//何かの条件によって・・・
isExitLoop = 1;
break;
}
}
}
//ループを脱出してここに飛びたい!

//後続処理
}

大外のfor文の中でisExitLoopを定義しちゃいます。めでたくスコープはfor文の中に限られたのですが、isExitLoopの型がintになってます。boolで定義しようとするとコンパイルエラーになります。何とかならんのこれ・・・。


カウンター変数を書き換える


boolにできないフラグを使うくらいならこっちのほうがマシ、か?


カウンター変数書き換え
private void DoSomething()
{
for (int i = 0; i < 10; i++)
{
for (int j = 0; j < 10; j++)
{
if (true)
{
//何かの条件によって・・・
i = -1;
j = -1;

break;
}
}
}
//ループを脱出してここに飛びたい!

//後続処理
}

ループを抜けたくなったときに、カウンター変数を継続条件外に書き換えます。上記の例だとカウンター変数iとjは0〜9の値をとるので、-1に書き換えちゃってます。この無理矢理感が何とも・・・。


例外を使ってみる


例外を使って多重ループから脱出してみます。事前にLoopExitExceptionを定義しておいてください。


例外を使った脱出
private void DoSomething()
{
try
{
for (int i = 0; i < 10; i++)
{
for (int j = 0; j < 10; j++)
{
if (true)
{
//何かの条件によって・・・
throw new LoopExitException();
}
}
}
}
catch (LoopExitException)
{
//ループを脱出してここに飛びたい!

//後続処理
}
}

うん、要件は満たしてるけど、例外ってこういう風に使うものじゃないような・・・。


Action+ラムダ式


Actionクラスで匿名関数を作り、即時実行します。


Action+ラムダ式
private void DoSomething()
{
new Action(() =>
{
for (int i = 0; i < 10; i++)
{
for (int j = 0; j < 10; j++)
{
if (true)
{
//何かの条件によって・・・
return;
}
}
}
})();
//ループを脱出してここに飛びたい!

//後続処理
}

個人的にはなかなかいいと思います。欠点は、returnの意味がわかりにくくなること。この場合のreturnは大外の関数(DoSomething)を抜けるのではなく、Actionによる匿名関数を抜けます。


goto


最後はgotoを使ってみます。


gotoによる多重ループ脱出
private void DoSomething()
{
for (int i = 0; i < 10; i++)
{
for (int j = 0; j < 10; j++)
{
if (true)
{
//何かの条件によって・・・
goto ExitLoop;
}
}
}
ExitLoop: ;
//ループを脱出してここに飛びたい!

//後続処理
}

悪名高いgotoを使ってみました。たくさん挙げた次善策の中でも、最もすっきりしてると思うのですがどうでしょう?まさに魔性の輝きを放っていますが、そこは悪名高きgoto、おいそれと気軽に使うのは憚られます。特にお仕事でプログラムを書く場合は、コーディング規約でgotoの使用を禁止されてる場合もあるかと思います。禁止されていなくても、気軽に使うべきではありません。例えば、新人さんがこのコードを見て「gotoって便利じゃね?」とバンバン使い始めたら目も当てられません。強すぎる武器は破滅を呼びますからね。逆に言うと、個人でプログラムを書いていてgoto禁止教に入信していない場合はいいかも。


結論


個人的にはgotoを使いたいです!
タグ:.net C#
posted by ぺるたご at 23:12| Comment(4) | TrackBack(0) | 日記 | このブログの読者になる | 更新情報をチェックする
この記事へのコメント
真っ先に思いついたのがgotoなんだよなあ…
Posted by at 2013年01月18日 17:04
コメントありがとうございます。
文章を面白おかしくするためにgotoを悪者にしてみましたが、この例ではgotoを使うのが一番きれいにまとまってると思います。
Posted by ぺるたご at 2013年01月19日 00:24
var isExitLoop = false;
for (int i = 0; i < 10 && !isExitLoop; i++) {
 for (int j = 0; j < 10 && !isExitLoop; j++) {
  if (true) {
   isExitLoop = true;
   break;
  }
 }
}
っていう複合型と

for (int i = 0; i < 10; i++) {
 for (int j = 0; j < 10; j++) {
  if (true) {
   i = j = 999;
   break;
  }
 }
}
っていう変則的な書き方もできます。
後者は比較的すっきりしている気がします。
Posted by LILAC at 2013年04月04日 23:56
コメントありがとうございます。
LILACさんのコメントをもとにサンプルコードを修正しました。

極稀に多重ループから脱出したいときがあるのですが、C#にそういう構文があればいいのにと思います。
たしかPHPにはあるんですよねぇ・・・。
Posted by ぺるたご at 2013年04月06日 20:25
コメントを書く
お名前:

メールアドレス:

ホームページアドレス:

コメント:

認証コード: [必須入力]


※画像の中の文字を半角で入力してください。
この記事へのトラックバックURL
http://blog.seesaa.jp/tb/272310449

この記事へのトラックバック
×

この広告は1年以上新しい記事の投稿がないブログに表示されております。