C# 初級講座
VB2019 Visual Studio 2022

第24回 while と do による繰り返し

2022/12/11

この記事が対象とする製品・バージョン

Visual Studio 2022 Visual Studio 2022 対象です。
Visual Studio 2019 Visual Studio 2019 対象です。
Visual Studio 2017 Visual Studio 2017 対象外ですが、参考になります。
Visual Studio 2015 Visual Studio 2015 対象外ですが、参考になります。
Visual Studio 2013 Visual Studio 2013 対象外ですが、参考になります。
Visual Studio 2012 Visual Studio 2012 対象外ですが、参考になります。
Visual Studio 2010 Visual Studio 2010 対象外ですが、参考になります。
Visual Studio 2008 Visual Studio 2008 対象外ですが、参考になります。
Visual Studio 2005 Visual Studio 2005 対象外ですが、参考になります。
Visual Studio.NET 2003 Visual Studio.NET 2003 × 対象外です。
Visual Studio.NET 2002 Visual Studio.NET (2002) × 対象外です。
  Visual Studio Code 対象外ですが、参考になります。

 

目次

 

1.while と do の概要

while(読み方:While=ワイル) または do (読み方:do=ドゥー) を使うと、条件付きで処理を繰り返すことができます。

C#の繰り返しの構文には、この他にこれまで説明した for と foreach があり、違いを簡単にまとめると次の通りです。

while 条件付きで処理を繰り返します。
do 条件付きで処理を繰り返します。(whileと違って条件を最後に判断します。)
for 回数を指定して処理を繰り返します。
foreach コレクションや配列の要素ごとに繰り返します。

それから、ForEachメソッドなど似たような機能のメソッドもありますが、これらは繰り返して処理を実行するというよりも、対象それぞれに対して処理を実行するという意味合いです。結果としては繰り返しと同じような使い方ができる場合が多いです。

 

さて、次の例では無限にメッセージを表示します。実際にやってみたい方はWindows フォーム アプリ で試すとやりやすいです。なぜなら、MessageBoxクラスが既定で使用できるからです。MessageBox.Showはメッセージとともに表示されるOKボタンを押すまで次の命令を実行しないので、メッセージは1つずつ表示されます。実行しても画面がメッセージにあふれて操作できなくなるということはありません。

このプログラムを停止するにはVisual Studioの停止ボタンを押してください。

while (true)
{
    MessageBox.Show("無限に表示しますよ。");
    MessageBox.Show("停止するにはVisual Studioの停止ボタンを押してください。");
}

 

2.while による繰り返しの実践

2-1.CPUメーター

while の構文を眺める前に、1つサンプルを作ってwhileに慣れておきましょう。

実際に一緒に試してみることをお勧めします。

CPU使用率をリアルタイムに表示するアプリケーションを作って見ます。

下記の設定で新規プロジェクトを作成してください。

プロジェクトテンプレート Windows フォーム アプリ
プロジェクト名 CPUMeter
ソリューション名 CPUMeter
フレームワーク .NET 6.0以上

 

画面には左にLabel、右にPictureBoxを次のように配置します。右側の緑の棒はPictureBoxです。

 

プロパティ設定は下記の通りです。

コントロールの名前 種類 プロパティ/イベント 備考
Form1 Form プロパティ Text CPUメーター  
    プロパティ MaxmizeBox false 最大化できなくします。
    プロパティ FormBorderStyle FixedSingle サイズ変更できなくします。
lblCPU Label プロパティ (Name) lblCPU  
    プロパティ Text -  
    プロパティ Font Verdane サイズ 24  
picCPU PictureBox プロパティ (Name) picCPU  
    プロパティ BackColor LimeGreen  

 

2-2.CPU使用率の取得

CPU使用率を取得するには、PerformanceCounterクラスのNextValueメソッドを使用します。(読み方:PerformanceCount=パフォーマンスカウンター、NextValue=ネクストバリュー)。

この例をフォームの Shown イベントハンドラー(読み方:Shown=ショウン)に記載してください。Shownイベントはフォームが表示される直後に発生します。

参考:イベントハンドラーを作成する方法

private void Form1_Shown(object sender, EventArgs e)
{
    using var cpuCounter = new System.Diagnostics.PerformanceCounter("Processor", "% Processor Time", "_Total");
    cpuCounter.NextValue(); //1回目は必ず 0なので呼び出すだけで何もしない。

    //0.5秒間なにもしない
    Task.Delay(500).Wait();

    //前回NextValueを呼び出してから0.5秒間の間に採取された値を取得
    float cpuValue = cpuCounter.NextValue();

    //値を四捨五入して整数で表示
    MessageBox.Show($"現在のCPU使用率は {cpuValue:0} %");
}

PerformanceCounterクラスはWindowsのパフォーマンスカウンターの機能を利用してCPU使用率などさまざまな指標を取得できます。何を取得したいのかをコンストラクターの引数に指定します。

パフォーマンスカウンターは少し癖があって最初は必ず 0 を返します。その後、少し待機して再度値を取得すると、その待機していた間に観測された値を取得できます。

そのため上記の例では2回 NextValueを呼び出して、その間に0.5秒何もしない時間を挿入しています。指定した時間何もしない機能は Task.Delay.Wait で実現できます。Delayの引数には待機する時間をミリ秒で指定します。この例では500ミリ秒を指定しているので0.5秒待機します。

PerformanceCounterクラスは使い終わったらDisposeメソッド(読み方:Dispose=ディスポーズ)を呼び出すことがルールになっているので、宣言時に using を付けます。宣言に using を付けると不要になったタイミングで自動的に Dispose メソッドが呼び出されます。エラーが発生した場合のことなどを考えると『必ず』Disposeを呼ぶというのは結構難しく複雑なプログラムになってしまいますから、たいていは using を使って.NETに任せるのが上策です。DispoaseメソッドではPerformanceCounterクラスが使用していたリソースを開放します。

この例を実行すると、直前0.5秒間のCPU使用率を表示することはできます。やってみてください。

 

2-3.CPUを使用率をフォームに表示

この段階ではまだ1回値が表示されるだけです。最終的にはリアルタイム(のように)CPU利用率を表示したいので、CPU使用率の取得は繰り返して何度でも実行する必要があります。while を使って繰り返すように改造してみましょう。

いきなり最終形にいくのではなく、まずはちょっとだけ改造してみます。次のようにしてみてください。

private void Form1_Shown(object sender, EventArgs e)
{
    using var cpuCounter = new System.Diagnostics.PerformanceCounter("Processor", "% Processor Time", "_Total");
    cpuCounter.NextValue(); //1回目は必ず 0なので呼び出すだけで何もしない。

    //0.5秒間なにもしない
    Task.Delay(500).Wait();

    //前回NextValueを呼び出してから0.5秒間の間に採取された値を取得
    float cpuValue = cpuCounter.NextValue();

    picCPU.Width = (int)(cpuValue * 5);
    lblCPU.Text = cpuValue.ToString("0");
}

 

ポイントはMessageBox.Show を削除して、その代わりに、フォームに貼り付けたラベル(lblCpu)とピクチャーボックス(picCpu)でCPU使用率を表現するに変更した点です。ピクチャーボックスのサイズの計算はこの例では簡略に書いており、CPU使用率の5倍の幅になるようにしています。そのためCPU使用率が100%のとき、ピクチャーボックスの幅は 500ピクセルになります。

実行するとこのようになります。

 

2-4.CPU使用率をリアルタイムに表示

それでは、while を使用して、CPU使用率取得の繰り返して、(ほぼ)リアルタイムでの表示を実現してみましょう。

while(true) { … } と記述すると { } で挟んだ箇所が繰り返して実行されます。どこを挟めばよいでしょうか。次のようになります。

private void Form1_Shown(object sender, EventArgs e)
{
    using var cpuCounter = new System.Diagnostics.PerformanceCounter("Processor", "% Processor Time", "_Total");
    cpuCounter.NextValue(); //1回目は必ず 0なので呼び出すだけで何もしない。

    while (true)
    {
        //0.5秒間なにもしない
        Task.Delay(500).Wait();

        //前回NextValueを呼び出してから0.5秒間の間に採取された値を取得
        float cpuValue = cpuCounter.NextValue();

        picCPU.Width = (int)(cpuValue * 5);
        lblCPU.Text = cpuValue.ToString("0");

        Application.DoEvents();
    }
}

実行するとちゃんとCPU使用率の変化に応じて画面が変わっていくのが確認できます。CPU使用率に変化をつけたい場合は、CNNのWebサイトを開いたり、Visual Studioをもう1つ起動するなど、CPUを使いそうな操作をしてみてください。

この例では ループの 最後に Application.DoEventsメソッド (読み方:Application=アプリケーション、DoEvents=ドゥーイベンツ)を呼び出しています。

これは何をやっているかというと、画面の更新(この場合、lblCpuとpicCpuの更新)を実際の画面に反映する機能です。

どういうことかというと、このプログラムだとwhile (true) { … } で処理が無限に繰り返されるため、Shownイベントが終わらないのです。イベントが終わらないとそのイベント内で指示している画面の更新(この場合、lblCpuとpicCpuの更新)は実際の画面にはなかなか反映されません。Application.DoEventsメソッドを呼び出すとそのときまでにたまっていた画面の更新処理などを処理してくれるのでイベントが終わらなくても画面が更新されるというわけです。一般的にApplication.DoEventsに頼らないといけない構造はあまり良い構造ではないのですが、今回は while を説明したかったので少し目をつむります

 

2-5.ループに条件をつける

ところで、この例には問題があって、フォームの右上の×ボタンを押すとフォームは消えるのですが、実行は終了しません。Visual Studioを確認すると、フォームが見えなくなった後も停止ボタンが押せる状態になっており、プログラムが動作を続けていることが確認できます。

whileの力で無限に処理が繰り返されているので、フォームが閉じてもプログラムが終わらないのです。見えない画面を一生懸命更新し続けます。Visual Studioの停止ボタンをクリックすると終了させることができますが、ここはもうちょっとスマートに改造してみましょう。

次のように1か所変えるだけで、フォームが閉じたときにプログラムが終了するようになります。

private void Form1_Shown(object sender, EventArgs e)
{
    using var cpuCounter = new System.Diagnostics.PerformanceCounter("Processor", "% Processor Time", "_Total");
    cpuCounter.NextValue(); //1回目は必ず 0なので呼び出すだけで何もしない。

    while (this.Visible)
    {
        //0.5秒間なにもしない
        Task.Delay(500).Wait();

        //前回NextValueを呼び出してから0.5秒間の間に採取された値を取得
        float cpuValue = cpuCounter.NextValue();

        picCPU.Width = (int)(cpuValue * 5);
        lblCPU.Text = cpuValue.ToString("0");

        Application.DoEvents();
    }
}

後でまた説明しますが、while の ( ) の中には、このループを継続する条件を記述します。( ) の中の式が true である限りループは継続されます。

そういうわけなので true と記述した場合は、無限にループします。フォームの Visibleプロパティ(読み方:Visible=ビジブル)はフォームが表示されているとき true、表示されていないとき false になります。そのため、これを条件にすると フォームが表示されているときだけループし、フォームが閉じられるなど非表示になるとループが終了します。

なお、フォームが何か別のウィンドウの裏に隠れてユーザーの目から見えないだけの状態はVisibleはtrueです。今回のように×ボタンを押してフォームを閉じた場合などにVisibleはfalseになります。

 

 

2-6.Timerでもっと良くする

大分それっぽいプログラムができましたが、whileの説明がしたかったので普通やらないような強引な作りになってしまいました。CPUメーターのようなものをWindowsフォームアプリで本格的に作りたい場合はTimerコンポーネントを使用するほうが本来は適切です。今回使用したPerformanceCounterクラスはCPU使用率の他にもメモリやCPU温度などさまざまな指標を取得できるので、なにか実用に役立てられるかもしれません。

Timerコンポーネントを使用する場合は、ツールボックスからフォームに貼り付けて(Timerはコンポーネントなのでフォームに貼り付けると、フォームの下の領域に表示されます)、プロパティウィンドウでIntervalプロパティを500、Enabledプロパティをtrueにします。後はTickイベントハンドラーを使って次のようにプログラムするだけです。

System.Diagnostics.PerformanceCounter cpuCounter = new("Processor", "% Processor Time", "_Total");

private void timer1_Tick(object sender, EventArgs e)
{
    float cpuValue = cpuCounter.NextValue();

    picCPU.Width = (int)(cpuValue * 5);
    lblCPU.Text = cpuValue.ToString("0");
}

プログラムが短い上にこっちの方がスムーズに動作します。 このように do や while の代わりに Timer を使うことでよくなる場合も多いです。

 

このプログラムでは変数 cpuCounter はプロシージャーの外で宣言するようにしました。中に入れると、0.5秒ごとに毎回 new されるので、効率が悪いし使い物になりません。覚えていますか?PerformanceCounter.NextValueメソッドは最初の1回は 0 を返すのです。毎回 new すると毎回 0 を返します。

プロシージャの外に出して class 直下で変数を宣言した場合、クラス がインスタンス化されるときに初期化(この場合は new)されます。なお、クラス直下の変数のことをクラスのフィールドと呼ぶのでした。

クラス 直下の変数(=フィールド)の宣言は型推論できないので、var で宣言できません。そのためvarの位置に明示的に型を指定する記述方法になります。 = の右側は var のときと同じで良いのですが、これだと、 = の左と右の両方に「System.Diagnostics.PerformanceCounter」という長い記述を書くことになって嫌なので、右側の方を省略しました。型を明示してnewするときは=の右側の型名を省略できるというルールを覚えていたでしょうか。

 

3.while と do の書き方

3-1.whileの構文

while 構文をまとめると次の通りです。

赤い部分は必ず記述する必要があります。条件を囲む ( ) は必須です。処理が1行の場合 { } は不要ですが、記述方法を統一するためにほとんどのプログラマーは常に { } を記述します。

while (条件)

{
    繰り返して実行する処理
}

条件には式または値を指定します。条件が true になる場合、1回処理を実行し、また条件を確認します。条件がまだ true ならもう1回処理を実行します。これを繰り返します。

条件が false になるか、途中で break や return が実行された場合はループが終了します。

コードスニペットも用意されている。Visual Studioで while に続けて TAB を2回押すと 空の while が挿入されます。

 

3-2.doの構文

while と ほぼ同じ機能の do もあります。while と do の違いは条件を最初に書くか最後に書くかです。

doの場合もwhileと同様 条件を囲む ( ) は必須で、処理が1行の場合 { } は不要ですが、記述方法を統一するためにほとんどのプログラマーは常に { } を記述します。

do

{
    繰り返して実行する処理
} while (条件);

do の構文にはちょっと奇妙なところがあります。まず、一番最後にキーワード while が登場する点が私には妙に見えます。それから、末尾には セミコロン ; を記述する点も妙に見えます。 while や if では末尾に セミコロンを書きませんが、do の場合はこのセミコロンは必須です。

コードスニペットはやはり有効です。 do に続けて TABを2回押すと空の do の構文が挿入されます。だから、こういう細かいことを暗記する必要はありません。

 

 

3-3.while と do の違い

while は 一番初めに条件を評価するので、初めから条件が false の場合、処理は1度も実行されません。do は 一番最後に条件を表kするので、初めから条件が false の場合でも、1回は処理が実行されます。

つまり、次のwhileの例は実行しても何も出力されません。

while (false)
{
    System.Diagnostics.Debug.WriteLine("OK?");
}

Debug.WriteLineで出力される場所

 

しかし、次のdoの例は1回だけ OK? と出力されます。

do
{
    System.Diagnostics.Debug.WriteLine("OK?");
} while (false);

Debug.WriteLineで出力される場所

 

ちなみに、上述のwhileの例のように絶対に実行されないプログラムをVisual Studioが検知すると、緑の波線で教えてくれます。「到達できないコードが検出されました。」

機械的に条件が判断できない場合は、Visual Studioが検知できないときもあります。

 

 

3-4.条件の書き方

条件に指定できるものは if 文と同じです。

条件は式で指定します。式の結果は bool型である必要があります。

true や false 、x や y などの単一の値も式の一種(単項式)ですので、指定できます。 i == 5, i < 5 のような式ももちろん指定できます。

次の例は、Windows フォーム アプリを前提にしています。ユーザーがメッセージボックスで 「はい」をクリックしている限りループが繰り返されます。

DialogResult answer = DialogResult.Yes;
while (answer == DialogResult.Yes)
{
    answer = MessageBox.Show("まだ続けますか?", "質問", MessageBoxButtons.YesNo);
}

MessageBox.Show は単純な例では引数に表示するメッセージだけを指定しますが、第3引数に表示するボタンを指定することもできます。ボタンはMessageBoxButtons列挙型でしています。YesNoを指定すると「はい」と「いいえ」の2つのボタンが表示されます。ユーザーが何を選択したかはMessageBox.Showの戻り値になります。これは DialogResult列挙型です。「はい」を選択した場合 Yes が戻り値です。

 

3-5.break

break を使うと、ループをすぐに終了できます。これは for や foreach のループと同じです。while自体に条件を記述する機能があるため、break を使う場合は、whileの条件とは別に if などを使って追加の条件判断を行う使い方になります。

良い例が思いつかなかったので変な例ですいません。

次の例は、前掲の例と同じ機能で、「いいえ」を押すまでループを続行します。

while (true)
{
    var answer = MessageBox.Show("まだ続けますか?", "質問", MessageBoxButtons.YesNo);
    if (answer == DialogResult.No)
    {
        break;
    }
}

while の条件は true なので無限ループですが、途中で DialogResult.No の場合 break するように if 文を記述しているので、この効果で終了します。

 

3-6.continue

continue を使うと、その周の残りの実行を飛ばして次の周を実行します。これも for や foreach と同じです。

ひらがなで構成されるランダムな文字列を作成するプログラムを使って実例を見てみましょう。

Unicodeのコードポイントから文字を生成するにはchar.ConvertFromUtf32メソッド(読み方:ConvertFromUtf32=コンバートフロムユーティーエフさんじゅうに)を使用します。

たとえば「学」のUnicodeのコードポイントは、23398 です。文字の世界は16進数で表現することが多いので、16進数で表現すると 0x5B66 です。「0x」 はC#で、それが16進数であることを示す目印です。

 

そのため、プログラムから次のようにして「学」という文字列を生成できます。

string manabu = char.ConvertFromUtf32(0x5B66);
System.Diagnostics.Debug.WriteLine(manabu); //「学」と出力されます。

 

ランダムな字を生成するためには文字コードの部分をランダムな数値に置き換えましょう。

ひらがなの文字コードはUnicodeではだいたい0x3041 ~ 0x3096 なので、ランダムなひらがなを1文字生成するプログラムは次のようになります。

const int hiraganaStart = 0x3041; //Unicodeでおおよそ最初のひらがなのコードポイント
const int hiraganaEnd = 0x3096; //Unicodeでおおよそ最後のひらがなのコードポイント

string moji = char.ConvertFromUtf32(Random.Shared.Next(hiraganaStart, hiraganaEnd + 1));
System.Diagnostics.Debug.WriteLine(moji); 

 

 

1文字生成するだけならこれで十分です。

これを while などでループさせて複数の文字を生成しようとするとちょっと問題があります。

ひらがなの範囲には小さい「ゃゅょ」が含まれているのですがランダムに生成すると「かゅ」など読めない組み合わせが作成されてしまうかもしれません。「きゅ」なら読めます。

そこでループの中で「ゃゅょ」が生成された場合は、その直前の字が「きしちにひみりぎじびぴ」のどれかでない場合は採用しないというプログラムにしてみます。

const int hiraganaStart = 0x3041; //Unicodeでおおよそ最初のひらがなのコードポイント
const int hiraganaEnd = 0x3096; //Unicodeでおおよそ最後のひらがなのコードポイント

string result = "";
string lastMoji = "";

while(result.Length < 100)
{
    string moji = char.ConvertFromUtf32(Random.Shared.Next(hiraganaStart, hiraganaEnd + 1));

    if (("ゃゅょ".Contains(moji)) && !"きしちにひみりぎじびぴ".Contains(lastMoji))
    {
        continue;
    }

    result += moji;
    lastMoji = moji;
}

System.Diagnostics.Debug.WriteLine(result); 

ループの中でランダムに生成した1文字は変数 moji に格納されます。

moji が 「ゃゅょ」であるかの確認は、"ゃゅょ".Contains(moji) で行っています。文字列型は string クラスなので、stringクラスのメソッドを使用できます。Containsメソッド(読み方:Contains=コンテインズ)は、対象を含んでいるか確認するメソッドです。

この確認方法は、ちょっと人間の完成と違って 『"ゃゅょ" は moji を含んでいますか?』という確認の仕方になります。違う言い方をすると『mojiは"ゃゅょ"のどれかですか?』という意味で、こっち方が人間の完成に一致しますね。

"ゃゅょ"のどれかであれば && の効果がでもう1つの確認を実施します。 『lastMojiは"きしちにひみりぎじびぴ"でいませんか?』という確認です。頭に ! がついているので戻り値のbool型がtrueがfalseに、falseがtreuに反転します。つまり逆の意味ということです。

lastMojiという変数はループの最後で moji を代入していますから、つまり、前の周で生成された文字という意味になります。1周目は初期値の "" です。

そして、この2つの条件に合致する場合は、continue することで、result に文字を追加することなく、直ちに次の周を開始します。

そうでない場合は result の末尾に moji を追加します。

100文字生成するまで処理を続けることにしたのでUntilで文字数が100になるまでという条件をつけました。

 

 

4.練習問題

問1.処理を100回繰り返したいとき、最も適しているものはどれでしょうか?
for
foreach
while
do

 

問2.何回 "OK" と表示されるでしょうか?
do
{
    System.Diagnostics.Debug.WriteLine("OK"); 
} while (false);
0
1
無限

 

次の回は、ラムダ式を扱います。

C#初級講座講座 次の回へ