付録H オブジェクト指向+List・LINQ チャレンジ課題集(第7〜12章)— 自習日用
この付録の位置づけ
Section titled “この付録の位置づけ”この付録は、第 7 章〜第 12 章(オブジェクト指向の基礎と List<T>・LINQ) を一通り終えた後の 自習日(課題日) に取り組むための課題集です。
自習とはいえ講師は常駐しているので、詰まったら質問してください。提出は 任意 ですが、レベル1・レベル2 は全員に取り組んでもらうことを推奨します。
この課題集は、本編より少し難しめ・挑戦的です
各課題は、本編の章末課題と同じ仕組み(クラス・カプセル化・
List<T>・LINQ など)を、別の題材で、少し複雑な形 で使う練習です。すぐに解けなくて当たり前なので、レベル1 から順に、できるところまで挑戦してください。
本編・付録Fとの違い
Section titled “本編・付録Fとの違い”| 観点 | 本編(各章末) | 本付録 H | 付録 F(発展) |
|---|---|---|---|
| 対象範囲 | その章の内容 | 第 7〜12 章の定着(List<T>・LINQ 含む) | 第 0〜31 章を走り切った人向け |
| 提出 | 必須(タイマー方式) | 任意(自習日) | 任意 |
| 完全なコード | 本文にあり | なし(仕様・ヒント・骨格のみ) | なし |
| 題材 | 社員系が中心 | 業務寄り + 身近 に散らす | 業務系 |
| 難度 | 章ごとに段階的 | やや難しめ・段階的(3 レベル) | 上級寄り |
「同じ仕組み(クラス・コンストラクター・カプセル化など)を 違う題材 で使う」体験が、この付録のいちばんの狙いです。
H-1 取り組み方の原則
Section titled “H-1 取り組み方の原則”本付録には完全なコードを載せません。掲載しているのは:
- 仕様(何を作るか)
- 実行結果例(出力イメージ)
- ヒント(詰まりやすい箇所への指針)
だけです。本編のように「コピペで動く」状態にはしていません。自分で組み立てる練習 のための場だと割り切ってください。
進め方の目安
Section titled “進め方の目安”| レベル | 目安時間 | 取り組み方 |
|---|---|---|
| レベル1(H-3-1〜H-3-4) | 各 20〜45 分 | 全員推奨。第 7〜12 章の典型例を別題材で素振り |
| レベル2(H-4-1〜H-4-3) | 各 60〜90 分 | 時間が許せば全員。複数章を組み合わせる |
| レベル3(H-5-1, H-5-2) | 各 2〜3 時間以上 | 挑戦したい人。ミニアプリ規模。完成しなくても OK、設計の経験を積む |
1 日(7 時間)の自習日なら、レベル1 + レベル2 で一通り が標準ペース。レベル3 は時間が余った人や、もう一段挑戦したい人向けです。
ソリューションの分け方
Section titled “ソリューションの分け方”本編と同じく、課題ごとに別プロジェクト を作成してください。
本付録用のソリューション名は KadaiH で、各課題のプロジェクト名は次のとおりです。
| 課題 | プロジェクト名 |
|---|---|
| H-3-1 家計簿エントリ | KdH_3_1_ExpenseEntry |
| H-3-2 じゃんけん判定 | KdH_3_2_RockPaperScissors |
| H-3-3 書籍在庫 | KdH_3_3_BookInventory |
| H-3-4 家計簿の集計・分析(List・LINQ) | KdH_3_4_ExpenseReport |
| H-4-1 勤怠打刻記録 | KdH_4_1_TimeRecord |
| H-4-2 商品注文の領収書 | KdH_4_2_Receipt |
| H-4-3 映画レビューのランキング(List・LINQ) | KdH_4_3_MovieRanking |
| H-5-1 数当てゲーム | KdH_5_1_GuessGame |
| H-5-2 簡易家計簿アプリ | KdH_5_2_BudgetApp |
各プロジェクトの csproj 設定(<Nullable>disable</Nullable> など)は本編と同じです。
質問のしかた
Section titled “質問のしかた”詰まったときは、講師に丸投げせず、次の 3 点を整理してから相談してください。
- 何をしようとしているか(課題のどの仕様に取り組み中か)
- どこまで動いているか(画面ショット or 入力したコード)
- 何が起きているか(エラーメッセージ、期待した動きと実際の差)
これは配属後にも効く、現場の質問の型 です。
H-2 準備:共通の前提
Section titled “H-2 準備:共通の前提”| 必要なもの | 用途 |
|---|---|
| Visual Studio 2022 | 全課題 |
第 12 章まで履修済み(List<T>・LINQ を含む) | 全課題 |
KadaiH ソリューション(新規) | 全課題 |
データベースは使いません(本付録はクラス設計と List<T>・LINQ の練習に集中します)。
全課題共通のルール
Section titled “全課題共通のルール”| 作業 | 内容 | 参照 |
|---|---|---|
| プロジェクト作成時の設定 | 「最上位レベルのステートメントを使用しない」にチェック | 第 1 章 1-1 |
| csproj の編集 | <Nullable>disable</Nullable> に変更 | 第 1 章 1-1 |
namespace の書き方 | ファイルスコープ形式(namespace XXX;) | 第 7 章 冒頭 |
| クラスファイルの追加 | プロジェクト右クリック → 追加 → クラス | 第 7 章 7-7 |
クラスファイルを自動生成すると、ブロック形式の namespace プロジェクト名 { ... } で作られます。ファイルスコープ形式に書き換えて ください(例:namespace KdH_3_1_ExpenseEntry;)。
提出について
Section titled “提出について”自習日なので 厳密なタイマーや必須提出はありません。ただし、終わった分は 任意で講師にレビュー依頼 ができます。次のいずれかで提出してください。
- Git が使える場合:本編と同じく
git add→git commit→git push- コミットメッセージ例:
AppendixH: H-3-1〜H-4-1完成 / H-4-2は途中
- コミットメッセージ例:
- Git が使えない場合:
KadaiHフォルダをサーバへコピー +提出メモ.txt
H-3 レベル1:ウォームアップ
Section titled “H-3 レベル1:ウォームアップ”第 7〜12 章の典型例を、社員以外の題材で素振りします。全員に推奨 のレベルです。
H-3-1 家計簿エントリ(目安 20〜30 分)
Section titled “H-3-1 家計簿エントリ(目安 20〜30 分)”関連章:第 7 章(クラス)、第 10 章(コンストラクター)
家計簿の 1 件を表す Expense クラスを作成し、配列で 5 件持って 月の合計 と カテゴリ別の内訳 を表示してください。
クラス仕様 Expense
- プロパティ(すべて
public { get; set; }):Date(DateTime):支出日Amount(int):金額(円)Category(string):カテゴリ名(例:"食費"、"交通費"、"光熱費"、"娯楽"、"その他")Memo(string):メモ
- コンストラクター:
Expense(DateTime date, int amount, string category, string memo)
- メソッド:
GetSummary():"2026/05/01 食費 800円 ランチ"の形式で 1 行の文字列を返す
Main メソッド仕様
Expense[]で 5 件のサンプル(同じ月内)を作る- 全件を 1 行ずつ表示
- 空行を 1 行
- 合計金額 を表示
- カテゴリごとの合計(出現したカテゴリだけでよい)を表示
2026/05/01 食費 800円 ランチ2026/05/02 交通費 250円 電車代2026/05/03 光熱費 4500円 電気代2026/05/05 食費 1200円 夕食2026/05/07 娯楽 1800円 映画
合計: 8550円食費: 2000円交通費: 250円光熱費: 4500円娯楽: 1800円- 全件合計は
foreachでtotal += expense.Amount; - カテゴリ別合計は、出現するカテゴリの種類が決まっているなら 5 つの変数で受ける 方法でも OK
- もう少し汎用的にやりたい人は、カテゴリ名の配列 と 金額の配列 を並列に持ち、配列内に同じカテゴリがあれば加算、なければ追加、という処理に挑戦してみてください(発展)
H-3-2 じゃんけん判定(目安 30〜40 分)
Section titled “H-3-2 じゃんけん判定(目安 30〜40 分)”関連章:第 7 章(クラス)、第 8 章(static)、第 11 章(列挙型)
じゃんけんの手を表す 列挙型 と、勝敗を判定する 静的クラス を作り、5 戦分の対戦結果と勝率を表示してください。
列挙型仕様 Hand
RockPaperScissors静的クラス仕様 Judge
IsPlayer1Win(Hand p1, Hand p2):Player1 が勝つときだけtrue(引き分けはfalse)IsDraw(Hand p1, Hand p2):引き分けのときtrueGetResultMessage(Hand p1, Hand p2):"Player1の勝ち"/"Player2の勝ち"/"引き分け"のいずれかを返す
Main メソッド仕様
- 5 戦分の
(Hand p1, Hand p2)を 2 つの配列 か 構造体の配列 で持つ - 各戦の結果を 1 行ずつ表示
- 最後に Player1 の勝率(%) を表示(引き分けは分母から除外)
第1戦: Rock vs Scissors → Player1の勝ち第2戦: Paper vs Scissors → Player2の勝ち第3戦: Rock vs Rock → 引き分け第4戦: Scissors vs Paper → Player1の勝ち第5戦: Paper vs Rock → Player1の勝ち
Player1の勝率: 75%第3戦は引き分けなので、分母 4 戦のうち 3 勝 → 75%。
- 勝敗判定は
switch文 orifの連鎖で。Hand.Rock vs Hand.Scissorsのような書き方は使えないので、p1 == Hand.Rock && p2 == Hand.Scissorsのように分解する - 静的クラスは
static class Judgeで。new Judge()せずにJudge.IsPlayer1Win(...)で呼べる - 勝率の計算で割り算するときは、引き分けを除いた分母が 0 にならないように注意(全戦引き分けなら勝率表示をスキップ)
H-3-3 書籍在庫(目安 30〜45 分)
Section titled “H-3-3 書籍在庫(目安 30〜45 分)”関連章:第 7 章(クラス)、第 10 章(private set、コンストラクター、メソッド)
図書の在庫数を 外から直接書き換えられない ように private set で守り、Borrow(貸出)/Return(返却)メソッドを通してだけ変更できる Book クラスを作成してください。
クラス仕様 Book
- プロパティ(すべて
public { get; private set; }):Title(string):書籍名Isbn(string):ISBNTotalCount(int):総数AvailableCount(int):貸出可能数
- コンストラクター:
Book(string title, string isbn, int totalCount):AvailableCountはtotalCountと同じ値で初期化
- メソッド:
Borrow():貸出。AvailableCount > 0ならAvailableCountを 1 減らしてtrueを返す。在庫なしならfalseReturn():返却。AvailableCount < TotalCountならAvailableCountを 1 増やしてtrueを返す。すでに満数ならfalsePrintStatus():"[書名] 在庫 X/Y"の形式で表示
Main メソッド仕様
- 在庫 3 冊の本を 1 冊作る
Borrow()を 4 回呼ぶ(3 回成功、1 回失敗)Return()を 2 回呼ぶ(2 回成功)- 各操作の結果と最終状態を表示
[C# 入門] 在庫 3/3貸出成功 → [C# 入門] 在庫 2/3貸出成功 → [C# 入門] 在庫 1/3貸出成功 → [C# 入門] 在庫 0/3貸出失敗(在庫なし) → [C# 入門] 在庫 0/3返却成功 → [C# 入門] 在庫 1/3返却成功 → [C# 入門] 在庫 2/3最終状態: [C# 入門] 在庫 2/3AvailableCountをprivate setにすると、Mainからbook.AvailableCount = 5;のような直接代入はエラーになる(これがカプセル化の効果)- 在庫の増減は
Borrow/Returnメソッド経由でしか起きないので、「在庫が変わった瞬間 = 貸出/返却が起きた瞬間」 に絞れる Borrow内ではAvailableCount--;で 1 減らせる(同じクラス内ならprivate setでも代入できる)
H-3-4 家計簿の集計・分析(List と LINQ)(目安 30〜45 分)
Section titled “H-3-4 家計簿の集計・分析(List と LINQ)(目安 30〜45 分)”関連章:第 7 章(クラス)、第 12 章(List<T>、LINQ)
H-3-1 で作った Expense クラスを、今度は List<Expense> で管理し、LINQ を使って集計・分析します。
H-3-1 では配列(Expense[])を使いましたが、本課題では List<T> に置き換える点がポイントです(第 12 章)。
クラス仕様 Expense(H-3-1 と同じ。再利用 or コピーで OK)
- プロパティ(すべて
public { get; set; }):Date(DateTime)、Amount(int)、Category(string)、Memo(string) - コンストラクター:
Expense(DateTime date, int amount, string category, string memo) - メソッド:
GetSummary():"2026/05/01 食費 800円 ランチ"の形式で 1 行返す
Main メソッド仕様
List<Expense>に同じ月の支出を 6〜8 件 用意する(new List<Expense> { new Expense(...), ... }またはAdd)- 全件を
GetSummary()で一覧表示 - 続けて、次を LINQ で順に 求めて表示する:
- 合計金額(
Sum) - 平均金額(
Average、小数が出たらMath.Roundで整数に丸める) - 「食費」の支出だけ を抽出して一覧(
Where)。0 件なら「該当なし」と表示 - 金額の高い順 に全件表示(
OrderByDescending+ToList) - いちばん高い支出 1 件(
OrderByDescending(...).FirstOrDefault()) - 1000 円以上の支出の件数(
Count(条件)) - メモだけの一覧(
Select)
- 合計金額(
2026/05/01 食費 800円 ランチ2026/05/02 交通費 250円 電車代2026/05/03 光熱費 4500円 電気代2026/05/05 食費 1200円 夕食2026/05/07 娯楽 1800円 映画2026/05/09 食費 600円 コーヒー
合計: 9150円平均: 1525円[食費だけ]2026/05/01 食費 800円 ランチ2026/05/05 食費 1200円 夕食2026/05/09 食費 600円 コーヒー[金額の高い順]2026/05/03 光熱費 4500円 電気代2026/05/07 娯楽 1800円 映画2026/05/05 食費 1200円 夕食2026/05/01 食費 800円 ランチ2026/05/09 食費 600円 コーヒー2026/05/02 交通費 250円 電車代いちばん高い支出: 2026/05/03 光熱費 4500円 電気代1000円以上の支出: 3件[メモ一覧]ランチ / 電車代 / 電気代 / 夕食 / 映画 / コーヒーList<Expense>の作り方は第 12 章 12-2 を参照(new List<Expense> { new Expense(...), ... })- 合計は
expenses.Sum(e => e.Amount)、平均はexpenses.Average(e => e.Amount)(Averageはdoubleを返すので、表示時にMath.Roundで丸める) - 「いちばん高い支出」は
expenses.OrderByDescending(e => e.Amount).FirstOrDefault()で取れる(降順に並べて先頭の 1 件)。Maxは第 12 章では扱っていないので、この方法で求めてください - カテゴリ別の内訳(食費・交通費・…を一気にまとめて集計)は、種類ごとに束ねる
GroupByが必要で 第 12 章の範囲外 です。やりたい場合は H-3-1 と同じくforeachで集計してください(LINQ で無理に書こうとしないこと) - 「食費だけ」の抽出結果が 0 件のときの表示(「該当なし」など)も入れる
- メモ一覧の「/」区切りは
string.Join(" / ", メモのリスト)(第 9 章)を使うと簡単
H-4 レベル2:統合課題
Section titled “H-4 レベル2:統合課題”複数の章の内容を組み合わせる課題です。時間が許せば全員 に取り組んでもらいたいレベルです。
H-4-1 勤怠打刻記録(目安 60〜90 分)
Section titled “H-4-1 勤怠打刻記録(目安 60〜90 分)”関連章:第 7 章(クラス)、第 9 章(DateTime の差)、第 10 章(コンストラクター・読み取り専用プロパティ)、第 11 章(構造体、null 許容値型)
勤怠の 1 日分の打刻を表す 構造体 と、社員 1 人の勤怠を集計する クラス を作ります。退勤未打刻の日は null で表現し、集計から除外します。
構造体仕様 TimeRecord
- フィールド or プロパティ:
Date(DateTime):対象日StartTime(DateTime):出勤時刻EndTime(DateTime?):退勤時刻(未打刻ならnull)
- メソッド:
GetWorkHours():double?を返す。EndTimeがnullならnull、そうでなければ(EndTime - StartTime).TotalHours
クラス仕様 Worker
- プロパティ:
Name(string)Records(TimeRecord[]):勤怠記録の配列(7 日分など)
- コンストラクター:
Worker(string name, TimeRecord[] records)
- メソッド:
GetTotalWorkHours():doubleを返す。EndTimeがnullの日は除外して合計GetUnclosedDays():intを返す。EndTimeがnullの日の数PrintSummary():1 日ずつ表示 + 合計・未打刻日数を表示
Main メソッド仕様
- 山田二郎の 5 日分の勤怠を
TimeRecord[]で作る(うち 1 日は退勤未打刻) Workerを作成してPrintSummary()を呼ぶ
[山田二郎の勤怠]2026/05/04 09:00-18:00 (8.00時間)2026/05/05 09:30-18:30 (9.00時間)2026/05/06 09:00-19:30 (10.50時間)2026/05/07 09:00-未打刻 (集計除外)2026/05/08 09:00-18:00 (9.00時間)
合計稼働時間: 36.50時間未打刻の日数: 1日DateTimeの引き算はTimeSpanを返す。TimeSpan.TotalHoursで時間単位のdoubleが得られるDateTime?(null 許容)の判定はendTime.HasValueまたはendTime != nullDateTime?.Valueで値を取り出せる(nullのときに呼ぶと例外なので、必ずHasValueチェック後)- 構造体は値型なので、配列内でも実体を持つ。第 11 章の「構造体は小さなデータの束」の典型例
- 表示の
9:00のような書式はstartTime.ToString("HH:mm")(第 9 章)
H-4-2 商品注文の領収書(目安 60〜90 分)
Section titled “H-4-2 商品注文の領収書(目安 60〜90 分)”関連章:第 7 章(クラス)、第 9 章(override ToString())、第 10 章(コンストラクター・読み取り専用プロパティ)
商品・注文明細・領収書の 3 つのクラスを組み合わせて、領収書全体を表示します。
合計金額は読み取り専用プロパティ で導出し、ToString() を override して整形出力します。
まず、3 つのクラスの関係を図で押さえます。この図を手がかりに、下の仕様に沿って実装 してください。
- 領収書(
Receipt)は、複数の注文明細(OrderItem)を持つ(Items配列) - 各注文明細(
OrderItem)は、商品(Product)を 1 つ持つ Subtotal・Tax・Totalは値を持たず、他の値から計算して返す読み取り専用プロパティ(◇ の関係をたどって合計する)
クラス仕様 Product
- プロパティ(
public { get; set; }):Name(string)UnitPrice(int)
- コンストラクター:
Product(string name, int unitPrice)
クラス仕様 OrderItem
- プロパティ:
Product(Product)Quantity(int)Subtotal(int):読み取り専用プロパティ。Product.UnitPrice * Quantityを返す
- コンストラクター:
OrderItem(Product product, int quantity)
- メソッド:
ToString()をoverride:"ノート × 5 = 900円"の形式で返す
クラス仕様 Receipt
- プロパティ:
CustomerName(string)Items(OrderItem[])Subtotal(int):読み取り専用プロパティ。Items全体のSubtotal合計Tax(int):読み取り専用プロパティ。Subtotalの 10%(小数点以下は切り捨て)Total(int):読み取り専用プロパティ。Subtotal + Tax
- コンストラクター:
Receipt(string customerName, OrderItem[] items)
- メソッド:
Print():領収書全体を整形して表示
Main メソッド仕様
- 商品 3 つ(ノート 180 円、ペン 120 円、消しゴム 100 円など)
- 注文明細 3 件(数量はバラバラに)
- 領収書を作って
Print()を呼ぶ
=== 領収書 ===お客様: 山田二郎 様
ノート × 5 = 900円ペン × 3 = 360円消しゴム × 2 = 200円
小計: 1460円消費税(10%): 146円合計: 1606円OrderItemのSubtotalはgetだけのプロパティ:get { return Product.UnitPrice * Quantity; }ToString()をoverrideする書き方は第 9 章の「ToString のオーバーライド」を復習Receipt.SubtotalはforeachでItemsを合計するgetプロパティ- 消費税は
Subtotal * 10 / 100(int同士の計算で切り捨て)、または(int)(Subtotal * 0.1)
H-4-3 映画レビューのランキング(List と LINQ のメソッドチェーン)(目安 60〜90 分)
Section titled “H-4-3 映画レビューのランキング(List と LINQ のメソッドチェーン)(目安 60〜90 分)”関連章:第 7 章(クラス)、第 10 章(読み取り専用プロパティ)、第 12 章(List<T>、LINQ、メソッドチェーン)
映画 1 本を表す Movie クラスを作り、List<Movie> に対して LINQ のメソッドチェーン(絞り込み → 並び替え → 取り出し)で分析します。
評価点(Score)を ★ の本数で見せる 読み取り専用プロパティ も作ります。
クラス仕様 Movie
- プロパティ(
public { get; set; }):Title(string):作品名Genre(string):ジャンル(例:"アクション"、"コメディ"、"ドラマ")Year(int):公開年Score(double):評価(0.0〜5.0)
- 読み取り専用プロパティ:
Stars(string):Scoreの整数部の数だけ ★ を並べた文字列 を返す(第 10 章の導出プロパティ)
- コンストラクター:
Movie(string title, string genre, int year, double score)
Main メソッド仕様
List<Movie>を 5〜8 件 用意する(下の例では 5 件)- 全作品を
"Title(Genre, Year) Score ★★★★"の形で一覧表示 - 評価の高い順 に全件並べ替えて表示(
OrderByDescending(m => m.Score)) - いちばん評価の高い作品 1 件(
OrderByDescending(...).FirstOrDefault()) - メソッドチェーン:
"アクション"の作品を、評価の高い順に、タイトルだけ 取り出して表示 (Where(...).OrderByDescending(...).Select(m => m.Title).ToList()) - 平均評価(
Average、Math.Roundで小数第 1 位に) - 高評価(4.0 以上)の本数(
Count(条件)) - タイトルの部分一致検索:
Console.ReadLine()でキーワードを受け取り、Where(m => m.Title.Contains(keyword))で検索(0 件なら「該当なし」)
[全作品]カミナリ大作戦(アクション, 2021) 4.0 ★★★★真夜中の図書館(ドラマ, 2019) 4.5 ★★★★笑う家族(コメディ, 2022) 3.0 ★★★追跡(アクション, 2018) 5.0 ★★★★★星屑の旅人(ドラマ, 2023) 3.5 ★★★
[評価の高い順]追跡:5.0真夜中の図書館:4.5カミナリ大作戦:4.0星屑の旅人:3.5笑う家族:3.0
いちばん評価の高い作品: 追跡(5.0)
[アクション作品・評価順のタイトル]追跡カミナリ大作戦
平均評価: 4.0高評価(4.0以上)の本数: 3本
検索キーワードを入力してください:旅星屑の旅人(ドラマ, 2023) 3.5 ★★★- メソッドチェーンの組み立て順は第 12 章 12-5「メソッドチェーンで段階的に処理する」を参照(絞り込み → 並び替え → 取り出し →
ToListの順) Starsはget { return new string('★', (int)Score); }のように書ける(Scoreの 整数部 の本数だけ ★ を作る。(int)で小数を切り捨て=第 2 章のキャスト。★ は表示用の飾りなので、小数は切り捨てで十分)- 評価点は
$"{m.Score:F1}"のように 小数第 1 位まで 表示すると揃う(そのままだと4.0が4と表示される)。平均評価も同様に:F1で表示する - 「上位 N 件だけ」を取り出す
Takeは第 12 章では扱っていません。ここでは「評価の高い順に 全件 並べて表示」すれば十分です(上位 3 件などをやりたい人は、Takeを自分で調べて使ってみる=発展) - 「いちばん評価が高い作品」は
MaxではなくOrderByDescending(m => m.Score).FirstOrDefault()で取る - 平均は
doubleが返る。表示は$"{平均:F1}"で小数第 1 位まで揃える - 検索結果が 0 件のときの表示を必ず入れる
発展(余裕があれば)
Section titled “発展(余裕があれば)”- ジャンルを入力させ、そのジャンルの平均評価 を表示する(
Whereで絞ってからAverage) Take(3)を調べて「評価トップ 3」を表示する(第 12 章では未習なので、調べて使う練習)
H-5 レベル3:ミニアプリ
Section titled “H-5 レベル3:ミニアプリ”ここからは 目的を持った小さなアプリ を作ります。完成しなくても OK、設計の経験を積む ことを目的としてください。 レベル1・レベル2 を全部終わってから挑戦するのがおすすめです。
H-5-1 数当てゲーム(ステージ付き)(目安 2〜3 時間)
Section titled “H-5-1 数当てゲーム(ステージ付き)(目安 2〜3 時間)”関連章:第 7 章(クラス)、第 8 章(static、Random)、第 9 章(File 入出力)、第 10 章(コンストラクター・読み取り専用プロパティ・private set)、第 11 章(列挙型)
1〜100 の範囲の正解を当てる 数当てゲーム を、3 段階の難易度 で遊べるようにします。 ゲーム終了後、結果をファイルに保存 します。
列挙型仕様 Difficulty
EasyNormalHard| 難易度 | 範囲 | 最大試行回数 | ヒント |
|---|---|---|---|
| Easy | 1〜50 | 10 回 | 「もっと大きい」/「もっと小さい」 |
| Normal | 1〜100 | 8 回 | 「もっと大きい」/「もっと小さい」 |
| Hard | 1〜100 | 5 回 | なし(正解 or 不正解だけ) |
クラス仕様 Game
- プロパティ:
Difficulty(Difficulty):get; private set;MaxAttempts(int):読み取り専用プロパティ。難易度から導出RangeMin(int):読み取り専用プロパティ。難易度から導出RangeMax(int):読み取り専用プロパティ。難易度から導出Answer(int):get; private set;(コンストラクター内でランダムに決定)AttemptCount(int):get; private set;(Guess()で増える)IsWon(bool):get; private set;(正解時にtrue)IsFinished(bool):読み取り専用プロパティ。IsWonまたはAttemptCount >= MaxAttempts
- コンストラクター:
Game(Difficulty difficulty):範囲内でAnswerをランダム決定、AttemptCount = 0、IsWon = false
- メソッド:
Guess(int value):stringを返す。判定してAttemptCount++、勝ち判定があればIsWon = true。難易度に応じてメッセージを返す
クラス仕様 GameRecord
- プロパティ:
Difficulty(Difficulty)IsWon(bool)AttemptCount(int)PlayedAt(DateTime)
- コンストラクター:
GameRecord(Difficulty difficulty, bool isWon, int attemptCount, DateTime playedAt)
- メソッド:
ToCsvLine():"Easy,true,6,2026/05/27 14:30"のような CSV 1 行を返す
Main メソッド仕様
- メニュー表示(1: Easy / 2: Normal / 3: Hard / 0: 終了)
- 選んだ難易度でゲーム開始 → ループで
Console.ReadLine()を受けてGuess() - 終了したら結果を表示
- 「もう一度?(y/n)」で続行 or メニューに戻る
- 全終了時、これまでの
GameRecordをrecords.csvに書き出す
=== 数当てゲーム ===難易度を選んでください (1: Easy, 2: Normal, 3: Hard, 0: 終了)> 1[Easy] 1〜50の数を当ててください(最大10回)1回目: 25もっと大きい2回目: 38もっと小さい3回目: 32正解! 3回で当てました
もう一度? (y/n) > n
メニューに戻ります。難易度を選んでください (1: Easy, 2: Normal, 3: Hard, 0: 終了)> 0
結果を records.csv に保存しました(累計: 1件)- ランダム値は
Random.Shared.Next(min, max + 1)(第 8 章で扱った静的プロパティ) - 入力は
Console.ReadLine()をint.TryParseで数値化、失敗時は「数字を入れてください」で再入力 - 導出プロパティ(
MaxAttempts等)はgetの中でswitchして返す - ファイル保存は
File.WriteAllLines("records.csv", lines)(第 9 章) - ヒントなし(Hard)は「ハズレ、正解は X でした」のような必要最小限の表示で
- 配列の長さを後から増やすのは難しいので、
GameRecord[]を最大件数(例:50)で確保し、何件入っているかを別変数で管理する
発展(余裕があれば)
Section titled “発展(余裕があれば)”- CSV 読込で過去の記録を一覧表示するメニューを追加
- 難易度ごとの平均試行回数を表示
- ヒントに「Hot/Cold」(差の絶対値で温度表現)を追加した第 4 難易度を作る
H-5-2 簡易家計簿アプリ(目安 3 時間以上・任意)
Section titled “H-5-2 簡易家計簿アプリ(目安 3 時間以上・任意)”関連章:第 7 章(クラス)、第 9 章(File、DateTime)、第 10 章(コンストラクター・カプセル化)、第 11 章(Nullable)
メニュー駆動の CUI アプリで、出費を記録・一覧・月別集計・CSV 保存/読込ができる 簡易家計簿 を作ります。
H-3-1 の Expense クラスを再利用 or 拡張する形でも、新規に作り直してもどちらでも OK です。
クラス仕様 Expense
(H-3-1 と同じ。再利用する場合はファイルをコピー)
クラス仕様 ExpenseRepository
- プロパティ:
Count(int):読み取り専用プロパティ。現在の件数
- 内部状態:
Expense[]で保持(最大件数を 100 件など固定で用意)、件数はprivate int _count;で管理
- メソッド:
Add(Expense expense):1 件追加(満杯ならfalseを返す)GetAll():Expense[](現在の件数分だけ返す)GetByMonth(int year, int month):指定月のExpense[]GetMonthlySummary(int year, int month):カテゴリごとの合計をstring1 つの整形済みテキストで返す("食費: 12000円\n交通費: 3500円\n..."のような形)SaveToCsv(string path):全件を CSV に書き出すLoadFromCsv(string path):CSV を読んで内部を置き換える
Main メソッド仕様
メニューループ:
=== 簡易家計簿 ===1. 出費を追加2. 全件表示3. 月別集計4. CSV保存5. CSV読込0. 終了> _- 1 追加:日付・金額・カテゴリ・メモを順に入力 →
Add() - 2 全件:
GetAll()をforeachで 1 行ずつ - 3 月別:年と月を入力 →
GetByMonth()で一覧 +GetMonthlySummary()で内訳 - 4 保存:ファイルパス(既定値
expenses.csv)に書き出し - 5 読込:ファイルパスから読み込み(既存データは置き換え)
- 0 終了:アプリ終了
=== 簡易家計簿 ===1. 出費を追加 2. 全件表示 3. 月別集計 4. CSV保存 5. CSV読込 0. 終了> 1日付 (YYYY/MM/DD): 2026/05/15金額: 1200カテゴリ: 食費メモ: ランチ追加しました(現在 1件)
> 3年: 2026月: 5
2026/05/15 食費 1200円 ランチ
[2026年5月 内訳]食費: 1200円合計: 1200円
> 4保存先 (既定: expenses.csv): [Enter]1件を expenses.csv に保存しました
> 0終了します。- 入力検証:
int.TryParse、DateTime.TryParseで失敗したら「正しい形式で入れてください」で再入力 - CSV の 1 行:
string.Join(",", date, amount, category, memo) - CSV の読み込み:
File.ReadAllLines(path)→ 各行をSplit(',')→ 4 要素をExpenseのコンストラクターに渡す - メモにカンマが入る可能性を心配し始めるとキリがないので、「メモには
,を入れない前提」 で割り切って構いません(現場の CSV ライブラリは引用符でエスケープしますが、それは第 22 章で扱います) - 配列の動的拡張は難しいので、最大件数(例:100 件)を決め打ち で持ち、
_countで管理する設計が現実的 - メニューループは
while (true) { ... }+case "0": return;のような形
発展(余裕があれば)
Section titled “発展(余裕があれば)”- 全カテゴリ別の年間集計を表示するメニューを追加
- 「金額の高い順 Top 3」を表示するメニューを追加
- 入力時にカテゴリを選択肢から選ばせる(1: 食費 / 2: 交通費 / …)
- 1 件削除メニューを追加(インデックス指定で削除、配列を 1 つ詰める)
H-6 仕上げ:振り返り
Section titled “H-6 仕上げ:振り返り”時間が残ったら、書いたコードを次の観点で見直してください。
| 観点 | 確認内容 |
|---|---|
| クラスの責任 | 1 つのクラスが「あれもこれも」していないか。Repository に分けられないか |
| カプセル化 | 外から書き換えられたくないプロパティに private set を付けているか |
| コンストラクター | 必要な値を必ず受け取っているか。new した直後に「使えない状態」になっていないか |
| メソッドの名前 | bool を返すなら Is〇〇 / Has〇〇 / Can〇〇、副作用があるなら動詞始まりになっているか |
| マジックナンバー | 60 や 100 のような数字に名前(定数 or 定義済みプロパティ)が付いているか |
void と戻り値 | 「結果を返す」なら void ではなく型を返しているか |
| LINQ の使いどころ | データの変換・集計は LINQ、画面表示など副作用や中断は foreach になっているか(第 12 章 12-5) |
完璧を目指す必要はありません。「書いた後に自分で読み直す」 習慣そのものが、配属後に効きます。
この付録のまとめ
Section titled “この付録のまとめ”- レベル1(H-3-1〜H-3-4):第 7〜12 章の典型例を 別題材で素振り(H-3-4 で
List<T>・LINQ も) - レベル2(H-4-1〜H-4-3):複数章を 組み合わせる 統合課題(H-4-3 は LINQ のメソッドチェーン)
- レベル3(H-5-1〜H-5-2):目的を持ったミニアプリ を設計から実装まで
- 完成しなくても OK。書いて、読み直して、質問する サイクルを回すのがいちばんの収穫
第 12 章までの内容(クラス・カプセル化・List<T>・LINQ)を別題材で使いこなす 腕試し として、また続くオブジェクト指向の発展(第 13〜15 章)に進む前の 土台固め として活用してください。