講義メモ ・p.204「静的クラス」から p.204 静的クラス ・静的メンバのみによるクラスは、静的クラスにすることができる ・書式: static class クラス名 {…} ・義務はないが、静的クラスにしておくことで、誤って静的ではないメンバが出現することを防止できる アレンジ演習:p.204 static02.cs ・静的クラスにインスタンスメンバを追記してエラーになることを確認しよう 作成例 //p.204 static02.cs C#2.0以降 using System; static class MyClass { //静的クラス public static int x; //静的データメンバ // int y; //エラーになる public static void showX() { //静的メソッド System.Console.WriteLine("x = {0}", x); } } class static02 { public static void Main() { MyClass.x = 10; //静的データメンバなのでクラス名指定で扱う MyClass.showX(); //静的メソッドなのでクラス名指定で扱う } } p.205 静的メンバとインスタンスメンバの混在 ・1クラスに静的メンバとインスタンスメンバの混在が可能だが、下記のルールがある ① 静的メソッドはインスタンス変数を扱えない(区別できないので) ② 静的メソッドはインスタンスメソッドを呼べない(〃) ③ 静的メソッドはthisを使えない(thisはインスタンスを指すので) ・なお、インスタンスメンバから静的メンバへのアクセスには制限はない(クラスに1つしかないので) p.205 static03.cs 解説図  https://ha241.rundog.org/wp-content/uploads/2024/08/DSC_9304.jpg アレンジ演習:p.205 static03.cs ・静的メソッドでインスタンス変数を扱うとエラーになることを確認しよう ・静的メソッドでインスタンスメソッドを呼ぶとエラーになることを確認しよう ・静的メソッドでthisを使うとエラーになることを確認しよう 作成例 //アレンジ演習:p.205 static03.cs using System; class Cat { static int NoOfTail; //静的データメンバ string Name; //インスタンス変数 public void SetName(string strName) { //インスタンスメソッド Name = strName; //インスタンス変数を扱える } public void ShowCat() { //インスタンスメソッド if (Name == null) { //インスタンス変数を扱える Console.WriteLine("名前が設定されていません"); return; } Console.WriteLine("猫の名前は{0}で尾の数は{1}本です", Name, NoOfTail); //静的データメンバも扱える } public static void setCatTail(int no) { //静的メソッド //ここでインスタンスフィールドにアクセス不可 //Name = "マイケル"; NoOfTail = no; //静的データメンバは扱える //ShowCat(); //インスタンスメソッドを呼ぶとエラー //this.name = ""; //thisを使うとエラーになる } } class static03 { public static void Main() { Cat.setCatTail(1); Cat mycat = new Cat(); Cat yourcat = new Cat(); mycat.ShowCat(); mycat.SetName("マイケル"); yourcat.SetName("パトリシア"); mycat.ShowCat(); yourcat.ShowCat(); } } p.207 プロパティ ・メソッドに近い記述により、外部からは変数のように扱える仕掛がプロパティ。  ※ データベースなどでは「属性」のことをプロパティというが、C#のプロパティは意味が異なる ・変数などを扱うプロパティを定義して、外部に利用を許可することで、外部からはプロパティがデータそのものに見える ・プロパティの中でデータの利用を制限したり、更新を不可にしたりできるので、データの安全性を高めることが可能  例: 身長を表す変数に負の数を代入できるが、身長を扱うプロパティの中で負の数の代入を却下できる ・プロパティで扱う変数をprivate指定にし、プロパティをpublicにすると良い ・プロパティは1つのデータを受け渡すので、そのデータ型がプロパティの型になる ・プロパティにはgetとsetの2つのアクセッサ(アクセス記述)があり、どちらかは省略可能 ・外部で、プロパティを利用するとgetの内容が、プロパティに代入するとsetの内容が実行される ・getの基本構文は「get { return 式や値; }」で、returnが必ず実行される必要がある ・setの基本構文は「set { 変数など = value; }」で、代入した値がvalueキーワード経由で渡される  定義例(負の身長を排除できる):  private double height; //身長を直接操作することを禁止  pubic double high { get { return height; } set { height = (value > 0) ? value : 0; } } アレンジ演習:p.208 prop01.cs ・各アクセッサにコンソール出力を追記し、自動的に呼び出されていることを確認しよう 作成例 //p.208 prop01.cs using System; class MyClass { double bl; //直接利用できない=プロパティ経由で扱う public double blprop { //プロパティ(blの値を返すのでdouble型) get { //getアクセッサ(プロパティを利用すると呼ばれる) Console.WriteLine("getが呼ばれた"); //【追加】 return bl; } set { //setアクセッサ(プロパティに代入すると呼ばれる) Console.WriteLine("setが呼ばれた"); //【追加】 bl = value; } } } class prop01 { public static void Main() { MyClass mc = new MyClass(); mc.blprop = 165.2; //setアクセッサが呼ばれる Console.WriteLine("bl = {0}", mc.blprop); //getアクセッサが呼ばれる } } p.207 プロパティ:setアクセッサにおける不適切な代入の防止 ・setアクセッサに条件を記述することで、代入できる値や相手を制限できる ・なお、setアクセッサを省略すると読み込み専用に、getアクセッサを省略すると閲覧不可にできる アレンジ演習:p.209 prop02.cs ・どちらかのgetアクセッサを省略すると「設定はできるが閲覧ができない」となることを確認しょう 作成例 //アレンジ演習:p.209 prop02.cs using System; class BMI { double bw, bl; public double blprop { //プロパティ(身長用) //get { //削除可能 // return bl; //無条件で返す //} set { if (value <= 0) { //代入値が0以下なら Console.WriteLine("身長に0または負の値は設定できません"); return; //何もしないで戻る } bl = value; //代入値を身長に代入 } } public double bwprop { //プロパティ(体重用) //get { //削除可能 // return bw; //無条件で返す //} set { if (value <= 0) { //代入値が0以下なら Console.WriteLine("体重に0または負の値は設定できません"); return; //何もしないで戻る } bw = value; //代入値を体重に代入 } } public double CalcBMI() { if (bl == 0.0 || bw == 0.0) { Console.WriteLine("データがセットされていません"); return 0.0; } return bw / Math.Pow(bl, 2.0); //BMIを計算して返す } } class prop02 { public static void Main() { BMI mybmi = new BMI(); double bl, bw; do { Console.Write("身長(m)--- "); string strBl = Console.ReadLine(); bl = double.Parse(strBl); mybmi.blprop = bl; //プロパティblpropのsetアクセッサを呼ぶ } while (bl <= 0.0); do { Console.Write("体重(kg)--- "); string strBw = Console.ReadLine(); bw = double.Parse(strBw); mybmi.bwprop = bw; //プロパティbwpropのsetアクセッサを呼ぶ } while (bw <= 0.0); //Console.WriteLine("bl = {0}, bw = {1}", // mybmi.blprop, mybmi.bwprop); //プロパティblpropとbwpropのgetアクセッサを呼べなくなるだけ Console.WriteLine("BMI = {0:#.#}", mybmi.CalcBMI()); } } p.212 インデクサ ・主に、配列をデータメンバとして持つクラスにおいて「オブジェクト名[添字]」とすることで要素を扱える仕組みがインデクサ ・アクセッサと似た記述が可能なので、データの保護にも役立つ ・定義書式: データ型 this[インデックス型 インデックス] { get {…} set {…} } ・getの基本構文は「get { return 配列名[インデックス]; }」 ・setの基本構文は「set { 配列名[インデックス] = value; }」 アレンジ演習:p.213 indexer01.cs ・「""」が代入されたら要素に代入しないようにしよう 作成例 //アレンジ演習:p.213 indexer01.cs using System; class MyClass { string[] name = new string[5]; //データメンバである配列 public string this[int i] { //インデクサの定義(データはstringで、インデックスはint) get { return name[i]; //インデックスを添字として配列の要素を得て返す } set { if (value != "") { name[i] = value; } //インデックスを添字として配列の要素に値を代入(""以外) } } } class indexer01 { public static void Main() { MyClass mc = new MyClass(); mc[0] = "一郎"; //オブジェクト名[インデックス]なので、インデクサのsetが呼ばれる mc[1] = "次郎"; mc[2] = "三郎"; mc[3] = "四郎"; mc[4] = "五郎"; mc[2] = ""; //インデクサのsetでチェックするので代入されない for (int i = 0; i < 5; i++) { Console.WriteLine(mc[i]); //オブジェクト名[インデックス]なので、インデクサのgetが呼ばれる } } } p.212 インデクサ:インデクサによるインデックスのチェック ・インデクサのsetおよびgetでインデックスの値をチェックできるので、範囲外の添字を指定したアクセスを安全に排除できる アレンジ演習:p.214 indexer02.cs ・インデックスに負の数が与えられても異常終了しないようにインデクサで対処しよう 作成例 //p.214 indexer02.cs using System; class MyIndexer { int[] array; //配列の宣言のみ int nMax; //要素数 public int this[int n] { //インデクサの定義(データはintで、インデックスはint) get { if (n < nMax && n >= 0) { //【変更】要素数よりインデックスが小さければ return array[n]; //インデックスを添字として用いて配列の要素値を返す } else { return 0; //でなければ0を返す } } set { if (n < nMax && n >= 0) { //【変更】要素数よりインデックスが小さければ array[n] = value; //インデックスを添字として用いて配列の要素に代入 } } } public MyIndexer(int i) { //コンストラクタ(引数は要素数) array = new int[i]; //要素数の分の配列を定義する nMax = i; //引数を要素数とする } } class indexer02 { public static void Main() { MyIndexer mi = new MyIndexer(20); //20要素の配列を持つクラスオブジェクトを生成 for (int i = 0; i < 20; i++) { mi[i] = i * i; //インデクサのsetを呼ぶ } for (int i = 0; i < 20; i++) { Console.WriteLine("{0} * {0} = {1}", i, mi[i]); //インデクサのgetを呼ぶ } //わざとに配列の範囲を超える mi[-30] = 30; //【変更】インデクサのsetで無視される Console.WriteLine("mi[-30] = {0}", mi[-30]); //【変更】インデクサのgetが0を返す } } p.212 文字列をインデックスとするインデクサ ・整数ではないインデックスを受け取ってインデクサの中で配列の要素にアクセスする処理を記述することができる ・よって、文字列をインデックスとするインデクサや、実数をインデックスとするインデクサも可能になる アレンジ演習:p.217 indexer03.cs ・インデクサにsetがないので、setを呼ぶような処置を記述するとエラーになることを確認しよう 作成例 //p.217 indexer03.cs using System; class MyIndexer { string[] mon = {"Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"}; public int this[string MonthName] { //インデクサの定義(データはintで、インデックスはstring) get { for (int i = 0; i < 12; i++) { //配列の全要素について繰返す if (MonthName == mon[i]) //インデックスと同値? return i + 1; //添字+1を「月」として返す } return 0; //当てはまる要素がなければ「0月」として返す } } } class indexer03 { public static void Main() { MyIndexer mi = new MyIndexer(); Console.WriteLine("Mayは{0}番目の月です", mi["May"]); Console.WriteLine("Decは{0}番目の月です", mi["Dec"]); Console.WriteLine("xは{0}番目の月です", mi["x"]); // mi["A"] = 5; //インデクサにsetがないのでエラーになる } } p.218 多次元のインデクサ ・2つ以上のインデクスを持つインデクサを記述できる ・2次元の書式: データ型 this[インデックス型① インデックス①, インデックス型② インデックス②] { get {…} set {…} } アレンジ演習:p.219 indexer04.cs ・インデクサのgetにインデックスの範囲チェックを加えよう ・インデックスが不適切な時は""を返す 作成例 //アレンジ演習: p.219 indexer04.cs using System; class MyClass { string[,] name; //2次元配列の宣言のみ public string this[int i, int j] { //2次元のインデクサ(データはstringで、インデックスはint,int) get { if (i >= 0 && i < 2 && j >= 0 && j < 5) { //【追加】 return name[i, j]; //2次元配列の該当する要素値を返す } else { //【以下追加】 return ""; } } } public MyClass() { //コンストラクタ name = new string[,]{ //2次元配列の初期化 {"田中", "佐藤", "吉田", "加藤", "粂井"},{"工藤", "竹中", "斉藤", "太田", "杉本"} }; } } class indexer04 { public static void Main() { MyClass mc = new MyClass(); for (int i = 0; i < 2; i++) { for (int j = 0; j < 5; j++) { Console.WriteLine("{0}組{1}番--{2}", i + 1, j + 1, mc[i, j]); } } Console.WriteLine("{0}組{1}番--{2}", 3, 12, mc[3, 12]); //異常終了せず、インデクサが""を返す } } p.220 インデクサのオーバーロード ・インデクサのシグニチャはインデックスのデータ型と数と並びであり、シグニチャの異なるインデクサを複数記述できる ・これをインデクサのオーバーロードという アレンジ演習:p.220 indexer05.cs ・インデクサのgetにインデックスの範囲チェックを加えよう ・インデックスが不適切な時は0を返す ・データはintで、インデックスはdoubleであるインデクサを追記しよう ・インデクスの値をintにキャストして、this[int]を呼ぶようにしよう 作成例 //アレンジ演習:p.220 indexer05.cs using System; class MyOverLoad { int[] a = new int[3] { 1, 2, 3 }; int[,] b = new int[2, 2] { { 100, 200 }, { 300, 400 } }; public int this[int i] { //インデクサ(データはintで、インデックスはint) get { if (i >= 0 && i < a.Length) { //【追加】 return a[i]; } else { //【以下追加】 return 0; } } } public int this[int i, int j] { //インデクサのオーバーロード(データはintで、インデックスはint,int) get { if (i >= 0 && i < 2 && j >= 0 && j < 2) { //【追加】 return b[i, j]; } else { //【以下追加】 return 0; } } } public int this[double i] { //インデクサのオーバーロード(データはintで、インデックスはdouble) get { return this[(int)i]; //インデックスをキャストしてインデクサthis[int]を呼ぶ } } } class indexer05 { public static void Main() { MyOverLoad mo = new MyOverLoad(); for (int i = 0; i < 3; i++) { Console.WriteLine("mo[{0}] = {1}", i, mo[i]); } for (int i = 0; i < 2; i++) { for (int j = 0; j < 2; j++) { Console.WriteLine("mo[{0}, {1}] = {2}", i, j, mo[i, j]); } } Console.WriteLine("mo[-2] = {0}", mo[-2]); Console.WriteLine("mo[10, 2] = {0}", mo[10, 2]); Console.WriteLine("mo[3.14] = {0}", mo[3.14]); } } p.222 練習問題1 ヒント ・p.209 prop02.csを単純化すると良い ・double型のデータメンバ1つのみになる ・Mainメソッドで負の数や0を与えて動作を確認すること p.222 練習問題2 ヒント ・p.217 indexer03.csをベースにすると良い ・コンストラクタで生徒数を入力する部分は、p.214 indexer02.csが参考になる ・生徒名を格納するstring型配列と、点数を格納するint型配列を宣言して用いると楽  ※ 生徒名と点数をメンバとするクラスオブジェクトの配列にできればさらに良い ・要素の生成はコンストラクタで行うと良い ・Mainメソッドで格納結果を表示すること 提出:アレンジ演習:p.220 indexer05.cs