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) | 日記 | このブログの読者になる | 更新情報をチェックする

2012年05月08日

jQuery UIのダイアログを使ってみる

jQuery UIのダイアログを使おうと思ったのです。JQuery UIのダイアログの詳しい説明はこちら。基本的な使い方はこんな感じです。


$(selector).dialog({
buttons: {
"はい": function(event) {
//「はい」を押したときに実行される処理
},
"いいえ": function(event) {
//「いいえ」を押したときに実行される処理
}
}
});



で、ダイアログを表示する関数、showDialogを作ることにしました。上記の例はとても単純ですが、本当はもっといろいろオプションを設定しなくてはいけません(しなくてもいいですけど)。ダイアログを表示するのが何箇所もあると、いちいち指定するのは面倒だからな!


機能は大体こんな感じです。
・ダイアログのタイトル、文言を指定できます。
・ボタンの種類(ボタンなし、OK・キャンセル、はい・いいえ、など)を指定できます。
・ボタンを押したときに実行する処理を指定できます。


//ボタンの種類の定数
var ButtonNone = 0;
var ButtonOK = 1;
var ButtonYesNo = 2;
var ButtonYesNoCancel = 3;

//ボタンのキャプション
var ButtonCaptions = [
[],
["OK"],
["はい", "いいえ"],
["はい", "いいえ", "キャンセル"]
];

//第4引数以降はコールバック(ボタンを押したときの処理)
function showDialog(title, message, buttonType, callback) {
//jQuery UIのdialogに渡すためのオブジェクト
var buttons = {};

//showDialogの引数を逃がしておく。
var args = arguments; //(1)の説明を参照してください。

for (var i = 0; i < ButtonCaptions[buttonType].length; i++) {
(function(i){ //(2)の説明を参照してください。
var caption = ButtonCaptions[buttonType][i];
if (caption) {
buttons[caption] = function() {
if ($.isFunction(args[i + 3])) {
//ボタンを押したときの処理を実行する。
args[i + 3]();
}
//ボタンを押したらダイアログを閉じる。
$(this).dialog("close");
}
}
})(i);
}

//(3)の説明を参照してください。
$("<div>" + message + "</div>").dialog({
//その他のオプションはお好みで。
//例えばダイアログを閉じたときのアニメーションとか。
title: title,
buttons: buttons,
close: function(event) {
//ダイアログを閉じたときに、
//メッセージのDOMオブジェクトを破棄する。
$(this).dialog('destroy');
$(event.target).remove();
}
});
}

//showDialogを使ってみる。
showDialog(
"タイトル",
"メッセージ",
ButtonYesNo,
function() {
//「はい」を押したときの処理
},
function() {
//「いいえ」を押したときの処理
}
);




(1)「arguments」ってどこから湧いてきたの?


別の記事(JavaScriptの可変長引数)を参照してください。


(2)なんでそこに「function」が出てくるの?


またもや、別の記事(JavaScriptの変数のスコープ)を参照してください。


(3)「$("<div>" + message + "</div>")」ってやる意味あるの?


jQuery UIのダイアログって、要するにDOMオブジェクトをダイアログっぽく表示するだけです。素直に受け取れば、HTMLファイルにdivタグなんかを使って書いておかなきゃいけません。とても静的です。メッセージの種類だけdivタグを用意するなんて面倒すぎます。もちろん、jQueryでそのdivタグの中身を書き換えて・・・なんてこともできますが、ここではダイアログを使い捨てにするため、こんな書き方をしています。
ラベル:jQueryUI
posted by ぺるたご at 00:14| Comment(0) | TrackBack(0) | 日記 | このブログの読者になる | 更新情報をチェックする

JavaScriptの変数のスコープ

JavaScriptの変数のスコープについて、あまりわかってない私がはまった罠。
下記のコードで、alertで表示されるのは何でしょうか?



失敗例
window.onload = function() {
//ファンクションの配列
var funcList = [];

//0〜2をalertで表示するファンクションを作る。
for (var i = 0; i < 3; i++) {
funcList[i] = function() {
alert(i);
}
}

//0番目のファンクションを実行すると・・・?
funcList[0]();
}




「3」が表示されます。私の意図では「0」が表示されてほしかったのですが・・・。


詳しい説明は他人様のサイトにお任せするとして、すごーくざっくり説明してみます。内側のファンクション(7行目)は変数iを使っています。内側のファンクションを実行するとき(12行目)、JavaScriptのエンジンは「変数iはなんぞや?」と出所を探します。内側のファンクションの中には変数iの定義はないので、さらにその外側を探しに行きます。スコープチェーンってやつですね。めでたく外側のファンクションで見つかったので、その値を使います。カウンターが回りきってるので変数iは「3」になると。


ざっくり説明はこの辺にして、回避策を出します。



回避策
window.onload = function() {
var funcList = [];

for (var i = 0; i < 3; i++) {
//functionで囲む。
(function(n){
funcList[n] = function() {
alert(n);
}
})(i);
}

//ちゃんと「0」が表示される!
funcList[0]();
}




「(function(){・・・})();」を日本語にすると、「匿名関数を定義して即実行」って感じでしょうか。上記の例では、こいつに引数として変数iを渡しています。こうすることによってスコープチェーンが切れるんですね。変数iと匿名関数の引数nはまったく別の参照ってことです。ちなみに、引数の名前を「n」から「i」に変えても正しく動きます。



全部「i」にしてみた
window.onload = function() {
var funcList = [];

for (var i = 0; i < 3; i++) {
//引数の名前を「i」にしてみた。
(function(i){
funcList[i] = function() {
alert(i);
}
})(i);
}

//ちゃんと「0」が表示される!
funcList[0]();
}




同じものを表すのだから同じ名前がいいですね。わかりやすくするために名前を変えるのもいいと思います。お好みでどうぞ。




ラベル:javascript
posted by ぺるたご at 00:07| Comment(1) | TrackBack(0) | 日記 | このブログの読者になる | 更新情報をチェックする

広告


この広告は60日以上更新がないブログに表示がされております。

以下のいずれかの方法で非表示にすることが可能です。

・記事の投稿、編集をおこなう
・マイブログの【設定】 > 【広告設定】 より、「60日間更新が無い場合」 の 「広告を表示しない」にチェックを入れて保存する。


×

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