今週の話題

販売本数ランキング 今回トップは「スーパーマリオブラザーズ ワンダー(Switch)」GO!
『フォートナイト』でプログラミングやデジタル技術を習得―茨城県で「FORTNITE UEFN クリエイティブ講座」開催 GO!
第24回「GDC Awards」ノミネート作品発表!『バルダーズ・ゲート3』と『ゼルダの伝説 ティアキン』が最多7部門で選出 GO!
ユービーアイソフト、サブスクは「今後大きな成長が見込める」と自信―PC向けサブスクプランをリブランディング GO!
「理想を追い求めたゲームが存在する余地が無くなる」…『バルダーズ・ゲート3』開発者がゲームのサブスクリプションに否定的な見解示す GO!
吉田直樹氏が『FF17』に対する胸の内を語る―「同じおじさんがやっていくよりは、若い世代にチャレンジしてほしい」GO!
『プロセカ』クリエイターがLive2D制作フローや演出のノウハウを惜しみなく紹介―Colorful Palette講演レポート【alive 2023】GO!

『LOST ARK』サービス終了告知―満足できるサービスの提供が困難であると判断 GO!
『GTA5』マイケル役俳優が自身の声を無許可使用したAIチャットボットに猛反発 GO!

前回のコメント

・今日やった例外ではいろいろな方法で例外を見ることができることがわかりました。
 ただ例外のネストでは、どういうところで便利なのかなーと感じました・・・

 確かにテキストのサンプルプログラムではメリットがわかりづらいですね。
 下記に一例を示しますので、理解を深めてください。

using System;
class exception08 {
    public static void Main() {
        int i = 0; double d = 0;
        try { //例外処理対象①
            try { //例外処理対象②
                Console.Write("整数:");  i = int.Parse(Console.ReadLine()); //オーバフロー、形式例外発生
            }  catch (OverflowException de) { //オーバフロー例外はここで
                i = int.MaxValue; //最大値とする
            }
            Console.Write("実数:");  d = double.Parse(Console.ReadLine()); //形式例外発生
        }
        catch (FormatException fe) { //形式例外はまとめてここで
            Console.WriteLine("数値ではありません"); return;
        }
        Console.WriteLine("{0} + {1} = {2}", i, d, i + d);
    }
}

ゲーム開発演習 講義メモ

:文字の描画、画面遷移、画面再描画 など

テーマ21 文字の描画(再掲載+α)

・フォームアプリケーションではフォントや書式を指定して文字列を画面上に描画できる
・この時、フォント、ブラシ、位置を指定できる
・フォントは、System.Drawing.Fontクラスのコンストラクタで生成できる
・このコンストラクタは多数のオーバーロードがあるが、フォント名とポイント数(大きさ)を指定する Font(string, float)が便利
・例: Font f = new Font("メイリオ", 15);
・太字などの書式を指定するにはFont(string, float, FontStyle)を用いると良い
・FontStyleはSystem.Drawing.FontStyle列挙型で、列挙子のBoldで太字にできる
・例: Font f = new Font("メイリオ", 15, FontStyle.Bold);
・文字の描画はGraphics.DrawStringメソッドで行う
・書式例: e.Graphics.DrawString(文字列, フォント, ブラシ, 左上X座標, 左上Y座標)

演習19 スコアの表示

・画面右上(400,10)から文字列「SCORE:000,000」を表示しよう
・フォントはメイリオ、色は黄色、サイズは20、スタイルはBoldとする

作成例

//演習19 スコアの表示
using System; //フォームアプリケーションに必須
using System.Windows.Forms; //フォームアプリケーションに必須
using System.Drawing; //Image用
class Program : Form { //Formクラスを継承
    Image backb = Image.FromFile("backb.bmp"); //背景画像の読込
    Image enemy = Image.FromFile("enemy.gif"); //アイテム画像の読込
    Image burn = Image.FromFile("burn.gif"); //効果用画像の読込
    Pen mypen = new Pen(Color.Red, 2); //ペンを生成(初期値は赤、2)
    Brush bred = new SolidBrush(Color.FromArgb(63, 255, 0, 0)); //ブラシを生成(赤の透明色)
    Brush byel = new SolidBrush(Color.Yellow); //【追加】ブラシを生成(黄色)
    Font f20 = new Font("メイリオ", 20, FontStyle.Bold); //【追加】フォントを生成(メイリオ20P太字)
    void DrawCircle(PaintEventArgs e, Pen p, int x, int y, int r) { //ペン、中心座標、半径を指定して円を描画
        e.Graphics.DrawEllipse(p, x - r, y - r, 2 * r, 2 * r); //円の描画を呼ぶ
    }
    protected override void OnPaint(PaintEventArgs e) { //Formのメソッドをオーバーライド
        base.OnPaint(e); //まず、Formクラスにおけるメソッドの内容(基本再描画処理)を実行
        e.Graphics.DrawImage(backb, 0, 0); //背景画像を(0,0)から描画
        e.Graphics.FillRectangle(bred, 78, 411, 485, 64); //判定エリアを透明赤で塗りつぶす
        e.Graphics.DrawRectangle(mypen, 78, 411, 485, 64); //判定エリアの描画
        e.Graphics.DrawString("SCORE:000,000", f20, byel, 400, 10); //【追加】スコアの描画
    }
    void OnKeyDown(object o, KeyEventArgs e) { //キーボードが押された時に呼ばれるメソッド
        if(e.KeyCode.ToString() == "Escape") { //Escキーが押されたら
            Close(); //フォームアプリケーション終了
        }
    }
    Program() { //コンストラクタ
        KeyDown += new KeyEventHandler(OnKeyDown); //キーボードが押された時に呼ばれるメソッドを登録
    }
    static void Main() { //publicの指定は任意
        Program p = new Program(); //継承したフォームのインスタンスを生成
        p.Width = 660; //インスタンスのWidthプロパティに幅を代入
        p.Height = 520; //インスタンスのHeightプロパティに高さを代入
        p.Text = "Game"; //インスタンスのTextプロパティにフォーム名を代入
        p.ControlBox = false; //ControlBoxを非表示にする
        p.FormBorderStyle = FormBorderStyle.Fixed3D; //フォームサイズ変更を禁止
        Application.Run(p); //インスタンスを画面に出す
    }
}

テーマ22 画面遷移

・フォームアプリケーションでは、画面ごとに表示内容を与え、切り替えながら進めることが多い
・これを画面遷移といい、例えば「スタート画面⇔プレイ画面⇒ゲームオーバー画面⇒スタート画面」と示す
・プログラムにおいては、画面をゲームモードとして表し、これを示す変数を定義して用いると良い
・例: int gamemode = 0; //0:スタート画面、1:プレイ画面、9:ゲームオーバー画面
・OnPaintメソッド等において、ゲームモードごとに異なる処理を記述し、OnKeyDownメソッドなどにおいて、ゲームモードを変更すれば良い

テーマ23 画面再描画

・プログラム側で動的に画面を書き換えた場合、画面への反映をWindow.Forms.Controlクラスの Invalidate()メソッドを呼ぶことで
 依頼する必要がある

演習20 タイトル画面の追加

・クラス変数gamemodeを0で初期化しておく(0:スタート画面、1:プレイ画面)
・gamemodeが0であれば、背景画像、タイトル「GAME1」、メッセージ「Hit Enter to Start」を表示
・gamemodeが1であれば、背景画像、判定エリア、スコアを表示
・タイトル「GAME1」はメイリオ、80ポイント、太字、黄色、(100,100)から
・メッセージ「Hit Enter to Start」はメイリオ、25ポイント、太字、黄色、(200,300)から

提出:演習20 タイトル画面の追加

講義メモ

テキスト篇:p.329「exception04.cs」から
ゲーム開発演習:文字の描画、画面遷移、画面再描画 など

p.329(catchの複数記述):再掲載+α

・catchにおいて例外クラスを指定することで、発生した例外毎に異なる対処をすることができる
・書式: 
 try {例外処理対象} 
 catch(例外クラス名① 引数) {例外①処理} 
 catch(例外クラス名② 引数) {例外②処理} 
 :
・ただし、例外処理対象で例外が発生すると、記述の上から順にマッチングされるので、対応する例外クラスの基本クラスが先に記述されていると
 「絶対に実行されない文が出来るのでエラー」となるので注意
・最後に「catch (Exception){…}」を記述することで「上記のどれにも当てはまない例外が発生したら」という記述ができる
・if文のelseやswitch文のdefaultに近い位置づけとなる
・なお、この記述により「どんな例外が起きても異常終了しない(p.323の構文)」となるので注意

p.329 exception04.cs

//p.329 exception04.cs
using System;
class exception04 {
    public static void Main() {
        int x = 10, y = 0;
        try { //例外処理対象
            Console.WriteLine("{0} / {1} = {2}", x, y, x / y); //ゼロ除算例外発生
        }
        catch (IndexOutOfRangeException io) { //配列範囲外例外処理(実行されない)
            Console.WriteLine(io.Message); //例外クラスのプロパティで例外メッセージを得て表示
        }
        catch (DivideByZeroException) { //ゼロ除算例外処理(実行される)
            Console.WriteLine("0で割っちゃだめだよ");
        }
        catch (Exception ex) { //汎用例外処理(実行されない):if文のelseやswitch文のdefaultに近い位置づけ
            Console.WriteLine(ex.Message);
        }
    }
}

アレンジ演習:p.329 exception04.cs

・p.330上のように、catchブロックの順序を変更するとエラーになることを確認しよう
・基本の例外クラスのcatchにより、派生の例外クラスで示す例外を処理できる
・IndexOutOfRangeExceptionクラスとDivideByZeroExceptionクラスはどちらも、Exceptionクラスの派生クラスだが、
 この2クラスの間には継承関係はないので、catchブロックの順序を変更しても良い
・しかし、catch (Exception ex)を、catch (IndexOutOfRangeException io)やcatch (DivideByZeroException)の前に置くと
 「絶対に実行されない文が出来るのでエラー」となる

作成例(実行はできない)

//アレンジ演習:p.329 exception04.cs
using System;
class exception04 {
    public static void Main() {
        int x = 10, y = 0;
        try { //例外処理対象
            Console.WriteLine("{0} / {1} = {2}", x, y, x / y); //ゼロ除算例外発生
        }
        catch (DivideByZeroException) { //ゼロ除算例外処理(実行される)
            Console.WriteLine("0で割っちゃだめだよ");
        }
        catch (Exception ex) { //汎用例外処理(実行されない):if文のelseやswitch文のdefaultに近い位置づけ
            Console.WriteLine(ex.Message);
        }
        catch (IndexOutOfRangeException io) { //配列範囲外例外処理(実行されない) 【エラーになる】
            Console.WriteLine(io.Message); //例外クラスのプロパティで例外メッセージを得て表示
        }
    }
}

p.330 finallyブロック

・例外処理において例外発生の有無にかかわらず実行したい処理がある場合、finallyブロックを用いて追記できる
・利用例としてはデータベースや通信などの外部リソースからの切断のような、後始末が多い
・例外が発生しなければ、tryブロックの最後の行の次に、finallyブロックの内容が実行され、処理は続行
・例外が発生しcatchされた場合は、catchブロックの最後の行の次に、finallyブロックの内容が実行され、処理は続行
・例外が発生しcatchされなかった場合は、finallyブロックの内容が実行され、その後に異常終了

p.331 exception05.cs

//p.331 exception05.cs
using System;
class exception05 {
    public static void Main() {
        string strWarusu; //入力用
        int x; //整数変換結果
        bool bEnd = false; //終了フラグをオフに
        while (true) { //無限ループ
            Console.Write("割る数--- ");
            strWarusu = Console.ReadLine();
            try { //例外処理対象
                x = int.Parse(strWarusu); //形式例外発生の可能性あり
                Console.WriteLine("10 / {0} = {1}", x, 10 / x); //ゼロ除算例外の可能性あり
            }
            catch (DivideByZeroException d) { //ゼロ除算例外処理
                Console.WriteLine(d.Message);
            }
            catch (Exception e) { //ゼロ除算以外の例外処理
                Console.WriteLine(e.Message);
            }
            finally { //例外発生の有無にかかわらず実行する処理
                Console.Write("続けますか(Y/N)---");
                if (Console.ReadLine()[0] == 'N') {
                    bEnd = true; //終了フラグをオンに
                }
            }
            if (bEnd) { //終了フラグがオン?
                break; //繰返しを抜ける
            }
        }
    }
}

アレンジ演習:p.331 exception05.cs

・「例外が発生しcatchされなかった場合」を確認しよう
・その為に「catch (Exception e)」を「catch (FormatException e)」に書き換えよう
・そして、int型の最大値を超える入力を行って、finallyブロックの内容が実行された後、異常終了することを確認しよう

作成例

//アレンジ演習:p.331 exception05.cs
using System;
class exception05 {
    public static void Main() {
        string strWarusu; //入力用
        int x; //整数変換結果
        bool bEnd = false; //終了フラグをオフに
        while (true) { //無限ループ
            Console.Write("割る数--- ");
            strWarusu = Console.ReadLine();
            try { //例外処理対象
                x = int.Parse(strWarusu); //形式例外発生の可能性あり
                Console.WriteLine("10 / {0} = {1}", x, 10 / x); //ゼロ除算例外の可能性あり
            }
            catch (DivideByZeroException d) { //ゼロ除算例外処理
                Console.WriteLine(d.Message);
            }
            catch (FormatException e) { //【変更】形式例外処理
                Console.WriteLine(e.Message);
            }
            finally { //例外発生の有無にかかわらず実行する処理
                Console.Write("続けますか(Y/N)---");
                if (Console.ReadLine()[0] == 'N') {
                    bEnd = true; //終了フラグをオンに
                }
            }
            if (bEnd) { //終了フラグがオン?
                break; //繰返しを抜ける
            }
        }
    }
}

p.333(例外のコールスタック)

・p.326で例外クラスのStackTraceプロパティでコールスタックとある通り、メソッドからメソッドを呼ぶ構造はスタックで表現されている
・そして、呼ばれているメソッドで例外が発生すると、即時に異常終了することはなく、呼んでいるメソッドの呼び出した位置で
 例外が発生したことになる
・よって、そこに例外処理を記述して対応できるし、あえて対応しないことで、そのメソッドを呼び出している
 さらに上位のメソッドに任せることもできる
例:
void Method1() { 
 Method2(); //①メソッド2を呼ぶ、⑥ゼロ除算例外が戻されるが例外処理がないので異常終了
}
void Method2() { 
 Method3(); //②メソッド3を呼ぶ、⑤ゼロ除算例外が戻されるが例外処理がないのでMethod1に投げる
}
void Method3() { 
 int a = 0; b = 1 / a; //③ゼロ除算例外発生、④例外処理がないのでMethod2に戻す(投げる)
}

p.333 throw文

・プログラマが意図的に例外を発生させる(例外を投げる)ために用いるのがthrow文
・書式: throw 例外オブジェクト;
・例外オブジェクトは、catchブロックで受け取ったものでも良く「new 例外クラス()」で生成しても良い
・なお、catchブロックの中で受け取った例外オブジェクトをthrowすることで、例外処理を行ってから、
 同じ例外を理由として異常終了させる(または呼び出し側に任せる)ことができる
・書式例:
 try {…}
 catch (例外クラス 仮引数) { 例外処理; throw 仮引数; }
例:
void Method1() { 
 try {
  Method2(); //①メソッド2を呼ぶ、⑥例外が戻されるので例外処理へ
 } catch {
  例外処理; //⑦Method2やMethod3における例外をまとめて処理できる
 }
}
void Method2() { 
 Method3(); //②メソッド3を呼ぶ、例外が戻されるが例外処理がないのでMethod1に投げる
}
void Method3() { 
 throw new Exception(); //③例外を発生させる、④例外処理がないのでMethod2に戻す(投げる)
}
・特に、他のクラスにあるメソッドや、共通で用いられるメソッドの場合、呼ばれる側で例外が発生した場合に、
 どうすれば良いか決められないことが多い
・よって、例外処理は呼び出しの大本で行うべきであり(例:画面に表示、通信で通知など)呼ばれる側に例外処理を書く必要はないことが多い
・また、この仕掛けを利用することで、呼び出し側に事態を知らせることにも用いることができる
例:
void Method1() { 
 try {
  Method2(●); //①メソッド2を呼ぶ、③例外が戻されるので例外処理へ
 } catch {
  例外処理; //④Method2における例外を処理できる
 }
}
void Method2(●) { 
 if (● < 0) throw new Exception(); //②負の数なら例外を起こす、例外処理がないのでMethod1に投げる
}

p.334 exception06.cs

//p.334 exception06.cs
using System;
class MyClassA { //部品的なクラス①
    public void Calc() { //部品的なメソッド①
        int x = 10, y = 0;
        int[] arr = new int[5]{1, 2, 3, 4, 5};
        try { //例外処理対象
            Console.WriteLine("{0}, {1}", arr[x], x / y); //配列範囲外例外が発生
        } catch (IndexOutOfRangeException i) { //配列範囲外例外処理
            Console.WriteLine(i.Message);
            DivideByZeroException d = new DivideByZeroException(); //ゼロ除算例外オブジェクトを生成
            Console.WriteLine("外側にthrowします");
            throw d; //ゼロ除算例外オブジェクトを呼び出し元に投げる(不適切だがテスト用)
        }
    }
}
class MyClassB { //部品的なクラス②
    public void Calc() { //部品的なメソッド②
        MyClassA a = new MyClassA(); //部品的なクラス①のインスタンス生成
        try { //例外処理対象
            a.Calc(); //部品的なメソッド①を呼ぶ(ゼロ除算例外オブジェクトが投げられてくる)
        }
        catch (DivideByZeroException d) { //ゼロ除算例外処理
            Console.WriteLine("外側のcatch節です");
            Console.WriteLine(d.Message);
        }
    }
}
class exception06 {
    public static void Main() {
        MyClassB b = new MyClassB(); //部品的なクラス②のインスタンス生成
        b.Calc(); //部品的なメソッド②を呼ぶ(内部で例外が発生し処理されて正常終了する)
    }
}

アレンジ演習:p.334 exception06.cs

・部品的なメソッド①の例外処理では、i.Messageの表示のみを行い、起こった例外を投げるだけにしよう
・部品的なメソッド②の例外処理では、本来の配列範囲外例外の対処を行うようにしよう

作成例

//アレンジ演習:p.334 exception06.cs
using System;
class MyClassA { //部品的なクラス①
    public void Calc() { //部品的なメソッド①
        int x = 10, y = 0;
        int[] arr = new int[5]{1, 2, 3, 4, 5};
        try { //例外処理対象
            Console.WriteLine("{0}, {1}", arr[x], x / y); //配列範囲外例外が発生
        } catch (IndexOutOfRangeException i) { //配列範囲外例外処理
            Console.WriteLine(i.Message);
            // DivideByZeroException d = new DivideByZeroException(); //【削除】ゼロ除算例外オブジェクトを生成
            // Console.WriteLine("外側にthrowします"); //【削除】
            throw i; //【変更】引数で受け取った例外オブジェクトを呼び出し元に投げる
        }
    }
}
class MyClassB { //部品的なクラス②
    public void Calc() { //部品的なメソッド②
        MyClassA a = new MyClassA(); //部品的なクラス①のインスタンス生成
        try { //例外処理対象
            a.Calc(); //部品的なメソッド①を呼ぶ(ゼロ除算例外オブジェクトが投げられてくる)
        }
        catch (IndexOutOfRangeException d) { //【変更】配列範囲外例外処理
            Console.WriteLine("外側のcatch節です");
            Console.WriteLine(d.Message);
        }
    }
}
class exception06 {
    public static void Main() {
        MyClassB b = new MyClassB(); //部品的なクラス②のインスタンス生成
        b.Calc(); //部品的なメソッド②を呼ぶ(内部で例外が発生し処理されて正常終了する)
    }
}

p.336(単独のthrow)

・catchブロックの中においては、単独のthrow文が記述可能で、catchした例外がそのまま投げられる
・例外処理を行ってから、同じ例外を理由として異常終了させたい場合や、呼び出し側に任せたい場合に用いる

p.336 exception07.cs

//p.336 exception07.cs
using System;
class MyClass { //部品的なクラス
    int x = 5, y = 0;
    public void Calc() { //部品的なメソッド
        try {
            Console.WriteLine("{0}", x / y); //ゼロ除算例外が発生
        }
        catch (DivideByZeroException d) { //ゼロ除算例外処理
            Console.WriteLine(d.Message);
            throw; //引数で受け取ったゼロ除算例外オブジェクトを呼び出し元に投げる
        }
    }
}
class MyClassB { //メインのクラス
    public static void Main() {
        MyClass mc = new MyClass(); //部品的なクラスのインスタンス生成
        try {
            mc.Calc(); //部品的なメソッドを呼ぶ(ゼロ除算例外オブジェクトが投げられてくる)
        }
        catch (DivideByZeroException d) { //ゼロ除算例外処理
            Console.WriteLine(d.TargetSite); //例外を投げたメソッド名「Void Calc()」を表示
        }
    }
}

p.337 tryのネスト

・tryブロックの中に、さらにtry-catch構造を記述できる
・これにより、複雑な例外処理が必要だったり、例外処理内容が重複する場合の記述をシンプルにできる

p.337 正誤:exception08.cs

・013行目の「"{0},{1}"」は「"{0}"」の誤り(コンパイルエラーにはならないが)

p.337 exception08.cs

//p.337 exception08.cs
using System;
class exception08 {
    public static void Main() {
        int x = 10, y = 0;
        try { //例外処理対象①
            try { //例外処理対象②
                Console.WriteLine("{0}", x / y); //ゼロ除算例外発生
            }
            catch (IndexOutOfRangeException i) { //例外処理対象②の配列範囲外例外処理(実行されない)
                Console.WriteLine(i.Message);
            }
        }
        catch (DivideByZeroException d) { //例外処理対象①のゼロ除算例外処理
            Console.WriteLine(d.Message);
        }
    }
}

アレンジ演習:p.337 exception08.cs

・例外処理対象②でゼロ除算例外ではなく、配列範囲外例外を発生させて、動作がどう変わるか確認しよう

作成例

//アレンジ演習:p.337 exception08.cs
using System;
class exception08 {
    public static void Main() {
        int x = 10, y = 0;
        try { //例外処理対象①
            try { //例外処理対象②
                int[] a = {0}; //【追加】
                Console.WriteLine("{0}", a[1]); //【変更】配列範囲外例外発生
            }
            catch (IndexOutOfRangeException i) { //例外処理対象②の配列範囲外例外処理
                Console.WriteLine(i.Message);
            }
        }
        catch (DivideByZeroException d) { //例外処理対象①のゼロ除算例外処理(実行されない)
            Console.WriteLine(d.Message);
        }
    }
}

p.338 独自の例外を作る

・全例外クラスの基本クラスであるExceptionクラスは、プログラマが継承するクラスを記述できる
・また、(一部を除き)各例外クラスについても、プログラマが継承するクラスを記述できる
 ※ 継承不可: IndexOutOfRangeExceptionクラス等
・よって、プログラマが必要に応じて、意味の近い例外クラスを継承するクラスを記述して用いると良い
・この時、p.326で説明したToString()メソッドをオーバーライドし、Messageプロパティをnew指定で隠ぺいすることで、
 新たなクラスの説明文を表示するようにすると良い
・exception09.csのように、既存の例外クラスのプロパティやメソッドの内容を書き換えた自前の例外を作りたい場合にも利用できる

p.339 exception09.cs

//p.339 exception09.cs
using System;
class MyEx : DivideByZeroException { //ゼロ除算例外クラスを継承した自前の例外クラス
    public new string Message = "0で割るエラーです"; //プロパティの隠ぺい
    public new string HelpLink = "http://www.kumei.ne.jp/c_lang/";  //同上
    public override string ToString() { //メソッドのオーバーライド
        return "0で割ってはいけません!!";
    }
}
class exception09 {
    public static void Main() {
        int x;
        Console.Write("割る数(整数)--- ");
        string strWaru = Console.ReadLine();
        try { //例外処理対象
            x = int.Parse(strWaru);
            if (x == 0) {
                throw new MyEx(); //自前の例外を投げる
            }
            Console.WriteLine("12 / {0} = {1}",x, 12 / x);
        }
        catch (MyEx me) { //自前の例外の処理
            Console.WriteLine(me.ToString()); //オーバーライドしたメソッドを実行
            Console.WriteLine(me.Message); //隠ぺいしたプロパティを実行
            Console.WriteLine(me.HelpLink + "を参照"); //隠ぺいしたプロパティを実行
        }
        catch (Exception e) { //自前の例外以外の例外処理
            Console.WriteLine(e.Message);
        }
    }
}

今週の話題

販売本数ランキング 今回トップは「桃太郎電鉄ワールド ~地球は希望でまわってる!~(Switch)」GO!
『アスタタ』不発のgumi、SBIが株を追加取得しても距離を取るのはなぜか?【ゲーム企業の決算を読む】GO!
「龍が如くスタジオ」のコアメンバーとなる人材を広く募集―セガ、中途採用オンラインカジュアル面談会を1月25日・26日・27日に開催 GO!
任天堂が時価総額10兆円を超える…DS・Wiiが絶好調だった2007年以来の高値、スイッチ新型に期待高まる GO!
カヤック、2023年世界市場アプリDL数で日本企業1位獲得を報告―新規ハイカジタイトル13本リリース GO!
新作ゲームのUIが複雑すぎる!?『スーサイド・スクワッド』皮切りにユーザー間でスマートなUIと煩雑なUIの議論勃発 GO!
Live2Dデザイナー向けに充実の研修を用意! f4samuraiが明かす採用ポイントと研修カリキュラム【alive 2023セッションレポート】GO!

SteamがAI利用タイトル容認に舵を切る―違法なコンテンツなど問題には適切なチェックで対処狙う GO!
Twitchがスタッフの約35%を解雇予定、昨年のレイオフを合わせると900人程の削減に―海外メディア報道 GO!

前回のコメント

今日やった例外は、記述の仕方は簡単そうでしたが必ず使うというわけでもなくて
ケースバイケースで見極めが大事なのかな感じました。

 そうですね。
 状況によっては必須の仕組みと構文ですので、しっかり理解を深めてください。

自分で作っているプログラムに組み込んでいろいろ試してみようと思います!

 是非。

講義メモ

・テキスト篇:p.320「練習問題」から
・ゲーム開発演習:文字の描画、画面遷移、画面再描画 など

p.320 練習問題 ヒント

・練習問題1は、p.308 lambda02.csと同じなので、さっそく練習問題2に進みましょう。
・練習問題2は、p.315 event02.csを基にしてアレンジしよう
・デリゲートの宣言を「戻り値有り、引数有り」にする
・その影響で、イベントを実行した結果をreturnする必要がある
・また、イベントフィールドがnullである場合にも何かをreturnする必要がある。今回は(入力を正の数のみとして)
 -1を返すとしよう
・event02と同じ仕掛けで2つの整数を順に受け取り、和を表示したら終了すれば良い(繰返す必要はない)
・イベントで呼ばれるクラスを作成しなくても良いのでシンプルにできる

p.320 練習問題2 ヒント:EventClassクラス

・「delegate void Handler(char ch); //デリゲートの宣言(戻り値なし、引数有り)」は
 「delegate int Handler(int x, int y); //デリゲートの宣言(戻り値有り、引数有り)」とする
・これにOnKeyHitメソッドを合わせるので「public int OnKeyHit(int x, int y) {」とする
・イベントを発生させる部分も合わせて「return KeyHit(x, y);」とする
・そして、イベントフィールドが無(準備中)である場合、-1を返すとするので
 「else { return -1; }」を加えよう

p.320 練習問題2 ヒント:ShowクラスとMainメソッド

・1つにまとめよう
・キー入力1回目の値を保持しておき、2回目のキー入力で2数を渡してイベントを発生させ、結果を表示するようにしよう

作成例

// p.320 練習問題2
using System;
delegate int Handler(int x, int y); //デリゲートの宣言(戻り値有り、引数有り)
class EventClass { //イベントの発生を担うクラスを定義
    public event Handler KeyHit; //イベントフィールドをイベント名KeyHitで定義
    public int OnKeyHit(int x, int y) { //KeyHitイベントを発生させるメソッド
        if (KeyHit != null) //イベントフィールドがnullでなければ
            return KeyHit(x, y); //イベントを発生させ結果を返す
        else
            return -1; //イベントフィールドがnullなら-1を返す
    }
}
class lambda02 {
    public static void Main() {
        ConsoleKeyInfo cki; //キーの情報を返すためのデータ構造をもつ構造体
        EventClass ec = new EventClass(); //イベントの発生を担うクラス
        ec.KeyHit += (x, y) => { return x + y; }; //イベントの発生により実行したいメソッドをデリゲートに追加
        int left = -1; //加算用の左辺値
        while (true) { //無限ループ
            if (Console.KeyAvailable) { //キー入力があった?
                cki = Console.ReadKey(false); //押されたキーを取得し表示する
                char ch = cki.KeyChar; //押されたキーの文字を取得
                if (Char.IsDigit(ch)) { //10進数字(0~9)キーか?
                    int a = (int)char.GetNumericValue(ch); //文字を実数に変換し整数にキャスト
                    if (left == -1) { //加算用の左辺値がまだならば
                        left = a; //入力値を左辺値にする
                        Console.WriteLine("に加えるのは?"); //表示
                    } else { //でなければ右辺値なので
                        int sum = ec.OnKeyHit(left, a); //イベントを発生させて和を得る
                        Console.WriteLine("なので\n{0} + {1} = {2}", left, a, sum); //合計を表示
                        break; //繰返しを抜ける
                    }
                }
            }
        }
    }
}

第13章 例外

p.321 例外処理の基礎

・プログラムにおいて異常事態が発生すると、通常、その時点で異常終了する
・プログラム言語によっては、異常事態を例外(Exception)としてプログラム側に通知してくれる
・よって、これを受け取って処理する仕組みを作り込んでおくことで、異常終了を避けられる
・この仕組みが例外処理
・例:double.Parse(文字列)メソッドに、実数変換できない文字列を渡すと、形式例外が発生し、対処を何もしないとその時点で異常終了する
・対処を組み込むには、C#の場合、try-catch構文と、Exceptionクラスの派生クラスを用いる

p.322 exception01.cs

//p.322 exception01.cs
using System;
class MyClass
{
    public static void Main()
    {
        Console.Write("割られる数--");
        string strA = Console.ReadLine();
        double a = double.Parse(strA); //①FormatException発生の可能性あり
        Console.Write("割る数---");
        string strB = Console.ReadLine();
        double b = double.Parse(strB); //②FormatException発生の可能性あり
        Console.WriteLine("{0} ÷ {1} = {2}", a, b, a / b);
    }
}

①でFormatException発生時のメッセージについて

・ハンドルされていない例外: System.FormatException: 入力文字列の形式が正しくありません。
 ⇒ 対処が記述されていないところで、形式例外が発生したことを示す
・場所 System.Number.ParseDouble(String value, NumberStyles options, NumberFormatInfo numfmt)
 場所 System.Double.Parse(String s)
 場所 MyClass.Main() 場所 C:\Users\human\Documents\ha231\chap13\Project1\exception01.cs:行 9
 ⇒ 下から読むと、mainの9行目で例外が発生したことがわかる。その上2行は内部的に呼ばれたメソッド
 ⇒ 最上位にあるのが、呼ばれたメソッドで実際に例外が発生した場所を指す
・呼び出しスタックともいい、呼び出しの順に積み上げたイメージ
・後述のとおり、例外が発生すると呼び出し元に通知されることが繰返される

p.323(例外処理:try-catch構文)

・例外はどんなメソッドでも起こりえるので、対処すべき例外に絞り込む必要がある
・そして、その例外が発生しうる範囲を、tryブロックにする
・tryブロックに続けて、catchブロックを記述し、例外発生時の処置を記述する
・すると、catchブロックの最後の1行を終えた段階で異常終了せずに、ブロックの後続行から再開される
・なお、tryブロックに複数行ある&例外が途中で発生した場合、tryブロック内の後続の処理はスキップされて、catchブロックの1行目に進む
・また、この構文では、どんな例外が発生しても対処できてしまう(後述する対処法がある)

p.324 exception02.cs

//p.324 exception02.cs
using System;
class MyClass {
    public static void Main() {
        double a = 0.0, b = 0.0;
        Console.Write("割られる数--");
        string strA = Console.ReadLine();
        try { //例外処理対象①
            a = double.Parse(strA); //FormatException発生の可能性あり
        } catch { //対象①の例外発生時の処理
            Console.WriteLine("不適切な入力です"); 
        } //異常終了せずに続行する
        Console.Write("割る数---");
        string strB = Console.ReadLine();
        try { //例外処理対象②
            b = double.Parse(strB); //FormatException発生の可能性あり
        } catch { //対象②の例外発生時の処理
            Console.WriteLine("不適切な入力です");
        } //異常終了せずに続行する
        Console.WriteLine("{0} ÷ {1} = {2}", a, b, a / b);
    }
}

アレンジ演習:p.324 exception02.cs

・変数a、bをint型に変更しよう
・すると、入力値によって、形式例外に加えてオーバフロー例外とゼロ除算例外が発生するようになる
・形式例外に加えてオーバフロー例外のどちらの例外が発生しても「不適切な入力です」と表示して先に進んでしまうことを確認しよう
・なお、割る数の入力で例外が発生すると、0になるので、ゼロ除算例外が発生し異常終了する

作成例

//アレンジ演習:p.324 exception02.cs
using System;
class MyClass {
    public static void Main() {
        int a = 0, b = 0;
        Console.Write("割られる数--");
        string strA = Console.ReadLine();
        try { //例外処理対象①
            a = int.Parse(strA); //形式例外,オーバフロー例外発生の可能性あり
        } catch { //対象①の例外発生時の処理
            Console.WriteLine("不適切な入力です"); 
        } //異常終了せずに続行する
        Console.Write("割る数---");
        string strB = Console.ReadLine();
        try { //例外処理対象②
            b = int.Parse(strB); //形式例外,オーバフロー例外発生の可能性あり
        } catch { //対象②の例外発生時の処理
            Console.WriteLine("不適切な入力です");
        } //異常終了せずに続行する
        Console.WriteLine("{0} ÷ {1} = {2}", a, b, a / b); //ゼロ除算例外発生(異常終了)の可能性あり
    }
}

p.325(例外クラス)

・FormatExceptionはクラスであり、Exceptionクラスの派生クラス
・C#では例外をExceptionクラスの派生クラスによって表して、そのオブジェクトを生成して渡す(投げる)仕様になっている
・各例外クラスの中にも継承関係があり、ツリー状に構成されいている
・例:Exceptionの派生クラスがSystemExceptionで、その派生クラスがFormatException。
 また、FormatExceptionの派生クラスもある(FileFormatExceptionなど)
・よって、FormatExceptionを用いて、その派生クラスによる例外にも対処できる

p.325(例外処理:try-catch例外構文)

・catchにおいて例外クラスを指定することで、発生した例外毎に異なる対処をすることができる
・書式: try {例外処理対象} catch(例外クラス名 引数) {例外処理}
・書式: try {例外処理対象} catch(例外クラス名① 引数) {例外①処理} catch(例外クラス名② 引数) {例外②処理} …

p.326(例外クラスのメンバ)

・各例外クラスの基本クラスであるExceptionクラスには、例外処理に利用できるプロパティが含まれている
・string Messageプロパティ:例外メッセージ
・string Sourceプロパティ:例外発生プロジェクト名
・MethodBase TargetSiteプロパティ:例外発生メソッド情報(Console.Write等をするとメソッド名になる)
・string HelpLinkプロパティ:ヘルプファイルのURL
・string StackTraceプロパティ:例外発生メソッドからの呼び出し履歴
・また、Objectクラスのstring ToString()をオーバライドした「例外内容を示す文字列を返す」メソッドも提供されている
※ Objectクラスのstring ToString()はConsole.Write等をすると省略時に自動実行される

p.327 exception03.cs

//p.327 exception03.cs
using System;
class exception03 {
    public static void Main() {
        int[] arr = new int[5];
        try {
            arr[5] = 10; //IndexOutOfRangeException(添字範囲外)発生
        } catch (IndexOutOfRangeException io) { //↑に対する例外処理
            Console.WriteLine(io); //内部的にio.ToString()が自動的に動作
            Console.WriteLine("[io]---------");
            Console.WriteLine(io.Source); //例外発生プロジェクト名
            Console.WriteLine("[io.Source]---------");
            Console.WriteLine(io.Message); //例外メッセージ
            Console.WriteLine("[io.Message]---------");
            Console.WriteLine(io.ToString()); //例外内容を示す文字列
            Console.WriteLine("[io.ToString()]---------");
            Console.WriteLine(io.TargetSite); //例外発生メソッド情報
            Console.WriteLine("[io.TargetSite]---------");
        }
    }
}

p.326 System.Objectクラス

・全クラスの暗黙の基本クラスとして提供されており、プログラマがクラスを定義しても、自動的に、System.Objectクラスの派生クラスになる
・自動的なので、継承を明示する必要はない
・なお、C#の通常の型指定では「object」で.NET型では「System.Object」となるので同じものを指す
・このクラスのもつ特殊なメソッドがstring ToString()で「現在のオブジェクトを表す文字列を返す」と定義されている
・C#が提供するクラスでは、基本的にToString()をオーバライドして、そのクラスを表す文字列を返してくれる
・プログラマが定義するクラスにおいても、共有する場合は、ToString()をオーバライドして、そのクラスを表す文字列を定義することが
 推奨される
・なお、オブジェクトの参照変数をConsole.Write等するとToString()が自動実行される

p.329(catchの複数記述)

・catchにおいて例外クラスを指定することで、発生した例外毎に異なる対処をすることができる
・書式: 
 try {例外処理対象} 
 catch(例外クラス名① 引数) {例外①処理} 
 catch(例外クラス名② 引数) {例外②処理} 
 :
・ただし、例外処理対象で例外が発生すると、記述の上から順にマッチングされるので、対応する例外クラスの基本クラスが先に記述されていると
 「絶対に実行されない文が出来るのでエラー」となるので注意

提出:アレンジ演習:p.324 exception02.cs

今週の話題

販売本数ランキング 今回トップも「スーパーマリオブラザーズ ワンダー(Switch)」GO!
にじさんじライバーの要望に応える徹底的なこだわり―エンジニアとデザイナーが語るANYCOLORの強み【alive 2023】GO!
固定費削減で黒字転換したマイネット―ファンタジースポーツへの固執は吉と出るか【ゲーム企業の決算を読む】GO!
ユーザー制作マップの“ゴーストタウン化”防ぐ―GameWith、『フォートナイト』内でのクリエイティブマップ制作・プロモーションを支援 GO!
アカツキ、ソニーグループ/コーエーテクモホールディングスとの資本業務提携および第三者割当による自己株式処分実施を決議 GO!

過去に『GTA6』の映像を流出させた青年ハッカーに「無期限入院」命令が下る―高い技能や「サイバー犯罪に復帰しようとする意思」から判断 GO!
ブロックチェーンは「実際の通貨を使用したギャンブル」?北米レーティングESRBにてNFT対応ゲームが「成人指定」対象に―Epic Games storeはポリシー変更で対象タイトル販売を継続 GO!
「にじさんじ」ANYCOLOR、“配信荒らし”との間で示談成立…無関係のコメントを執拗に連続投稿 GO!
インディーゲームで契約トラブルを避けるための3つのポイントをPLAYISMが解説【IDC2023】 GO!