Skip to content

付録H オブジェクト指向+List・LINQ チャレンジ課題集(第7〜12章)— 自習日用

この付録は、第 7 章〜第 12 章(オブジェクト指向の基礎と List<T>・LINQ) を一通り終えた後の 自習日(課題日) に取り組むための課題集です。

自習とはいえ講師は常駐しているので、詰まったら質問してください。提出は 任意 ですが、レベル1・レベル2 は全員に取り組んでもらうことを推奨します。

この課題集は、本編より少し難しめ・挑戦的です

各課題は、本編の章末課題と同じ仕組み(クラス・カプセル化・List<T>・LINQ など)を、別の題材で、少し複雑な形 で使う練習です。すぐに解けなくて当たり前なので、レベル1 から順に、できるところまで挑戦してください。

観点本編(各章末)本付録 H付録 F(発展)
対象範囲その章の内容第 7〜12 章の定着(List<T>・LINQ 含む)第 0〜31 章を走り切った人向け
提出必須(タイマー方式)任意(自習日)任意
完全なコード本文にありなし(仕様・ヒント・骨格のみ)なし
題材社員系が中心業務寄り + 身近 に散らす業務系
難度章ごとに段階的やや難しめ・段階的(3 レベル)上級寄り

「同じ仕組み(クラス・コンストラクター・カプセル化など)を 違う題材 で使う」体験が、この付録のいちばんの狙いです。


本付録には完全なコードを載せません。掲載しているのは:

  • 仕様(何を作るか)
  • 実行結果例(出力イメージ)
  • ヒント(詰まりやすい箇所への指針)

だけです。本編のように「コピペで動く」状態にはしていません。自分で組み立てる練習 のための場だと割り切ってください。

レベル目安時間取り組み方
レベル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 は時間が余った人や、もう一段挑戦したい人向けです。

本編と同じく、課題ごとに別プロジェクト を作成してください。 本付録用のソリューション名は 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> など)は本編と同じです。

詰まったときは、講師に丸投げせず、次の 3 点を整理してから相談してください。

  1. 何をしようとしているか(課題のどの仕様に取り組み中か)
  2. どこまで動いているか(画面ショット or 入力したコード)
  3. 何が起きているか(エラーメッセージ、期待した動きと実際の差)

これは配属後にも効く、現場の質問の型 です。


必要なもの用途
Visual Studio 2022全課題
第 12 章まで履修済み(List<T>・LINQ を含む)全課題
KadaiH ソリューション(新規)全課題

データベースは使いません(本付録はクラス設計と List<T>・LINQ の練習に集中します)。

作業内容参照
プロジェクト作成時の設定最上位レベルのステートメントを使用しない」にチェック第 1 章 1-1
csproj の編集<Nullable>disable</Nullable> に変更第 1 章 1-1
namespace の書き方ファイルスコープ形式(namespace XXX;)第 7 章 冒頭
クラスファイルの追加プロジェクト右クリック → 追加クラス第 7 章 7-7

クラスファイルを自動生成すると、ブロック形式の namespace プロジェクト名 { ... } で作られます。ファイルスコープ形式に書き換えて ください(例:namespace KdH_3_1_ExpenseEntry;)。

自習日なので 厳密なタイマーや必須提出はありません。ただし、終わった分は 任意で講師にレビュー依頼 ができます。次のいずれかで提出してください。

  • Git が使える場合:本編と同じく git addgit commitgit push
    • コミットメッセージ例:AppendixH: H-3-1〜H-4-1完成 / H-4-2は途中
  • Git が使えない場合:KadaiH フォルダをサーバへコピー + 提出メモ.txt

第 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円
  • 全件合計は foreachtotal += expense.Amount;
  • カテゴリ別合計は、出現するカテゴリの種類が決まっているなら 5 つの変数で受ける 方法でも OK
  • もう少し汎用的にやりたい人は、カテゴリ名の配列金額の配列 を並列に持ち、配列内に同じカテゴリがあれば加算、なければ追加、という処理に挑戦してみてください(発展)

H-3-2 じゃんけん判定(目安 30〜40 分)

Section titled “H-3-2 じゃんけん判定(目安 30〜40 分)”

関連章:第 7 章(クラス)、第 8 章(static)、第 11 章(列挙型)

じゃんけんの手を表す 列挙型 と、勝敗を判定する 静的クラス を作り、5 戦分の対戦結果と勝率を表示してください。

列挙型仕様 Hand

Rock
Paper
Scissors

静的クラス仕様 Judge

  • IsPlayer1Win(Hand p1, Hand p2):Player1 が勝つときだけ true(引き分けは false)
  • IsDraw(Hand p1, Hand p2):引き分けのとき true
  • GetResultMessage(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 文 or if の連鎖で。Hand.Rock vs Hand.Scissors のような書き方は使えないので、p1 == Hand.Rock && p2 == Hand.Scissors のように分解する
  • 静的クラスは static class Judge で。new Judge() せずに Judge.IsPlayer1Win(...) で呼べる
  • 勝率の計算で割り算するときは、引き分けを除いた分母が 0 にならないように注意(全戦引き分けなら勝率表示をスキップ)

関連章:第 7 章(クラス)、第 10 章(private set、コンストラクター、メソッド)

図書の在庫数を 外から直接書き換えられない ように private set で守り、Borrow(貸出)/Return(返却)メソッドを通してだけ変更できる Book クラスを作成してください。

クラス仕様 Book

  • プロパティ(すべて public { get; private set; }):
    • Title(string):書籍名
    • Isbn(string):ISBN
    • TotalCount(int):総数
    • AvailableCount(int):貸出可能数
  • コンストラクター:
    • Book(string title, string isbn, int totalCount):AvailableCounttotalCount と同じ値で初期化
  • メソッド:
    • Borrow():貸出。AvailableCount > 0 なら AvailableCount を 1 減らして true を返す。在庫なしなら false
    • Return():返却。AvailableCount < TotalCount なら AvailableCount を 1 増やして true を返す。すでに満数なら false
    • PrintStatus():"[書名] 在庫 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/3
  • AvailableCountprivate 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 メソッド仕様

  1. List<Expense> に同じ月の支出を 6〜8 件 用意する(new List<Expense> { new Expense(...), ... } または Add)
  2. 全件を GetSummary() で一覧表示
  3. 続けて、次を 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)(Averagedouble を返すので、表示時に Math.Round で丸める)
  • 「いちばん高い支出」は expenses.OrderByDescending(e => e.Amount).FirstOrDefault() で取れる(降順に並べて先頭の 1 件)。Max は第 12 章では扱っていないので、この方法で求めてください
  • カテゴリ別の内訳(食費・交通費・…を一気にまとめて集計)は、種類ごとに束ねる GroupBy が必要で 第 12 章の範囲外 です。やりたい場合は H-3-1 と同じく foreach で集計してください(LINQ で無理に書こうとしないこと)
  • 「食費だけ」の抽出結果が 0 件のときの表示(「該当なし」など)も入れる
  • メモ一覧の「/」区切りは string.Join(" / ", メモのリスト)(第 9 章)を使うと簡単

複数の章の内容を組み合わせる課題です。時間が許せば全員 に取り組んでもらいたいレベルです。


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? を返す。EndTimenull なら null、そうでなければ (EndTime - StartTime).TotalHours

クラス仕様 Worker

  • プロパティ:
    • Name(string)
    • Records(TimeRecord[]):勤怠記録の配列(7 日分など)
  • コンストラクター:
    • Worker(string name, TimeRecord[] records)
  • メソッド:
    • GetTotalWorkHours():double を返す。EndTimenull の日は除外して合計
    • GetUnclosedDays():int を返す。EndTimenull の日の数
    • 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 != null
  • DateTime?.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 つ持つ
  • SubtotalTaxTotal は値を持たず、他の値から計算して返す読み取り専用プロパティ(◇ の関係をたどって合計する)

クラス仕様 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円
  • OrderItemSubtotalget だけのプロパティ:get { return Product.UnitPrice * Quantity; }
  • ToString()override する書き方は第 9 章の「ToString のオーバーライド」を復習
  • Receipt.SubtotalforeachItems を合計する 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 メソッド仕様

  1. List<Movie>5〜8 件 用意する(下の例では 5 件)
  2. 全作品を "Title(Genre, Year) Score ★★★★" の形で一覧表示
  3. 評価の高い順 に全件並べ替えて表示(OrderByDescending(m => m.Score))
  4. いちばん評価の高い作品 1 件(OrderByDescending(...).FirstOrDefault())
  5. メソッドチェーン:"アクション" の作品を、評価の高い順に、タイトルだけ 取り出して表示 (Where(...).OrderByDescending(...).Select(m => m.Title).ToList())
  6. 平均評価(AverageMath.Round で小数第 1 位に)
  7. 高評価(4.0 以上)の本数(Count(条件))
  8. タイトルの部分一致検索: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 の順)
  • Starsget { return new string('★', (int)Score); } のように書ける(Score整数部 の本数だけ ★ を作る。(int) で小数を切り捨て=第 2 章のキャスト。★ は表示用の飾りなので、小数は切り捨てで十分)
  • 評価点は $"{m.Score:F1}" のように 小数第 1 位まで 表示すると揃う(そのままだと 4.04 と表示される)。平均評価も同様に :F1 で表示する
  • 「上位 N 件だけ」を取り出す Take は第 12 章では扱っていません。ここでは「評価の高い順に 全件 並べて表示」すれば十分です(上位 3 件などをやりたい人は、Take を自分で調べて使ってみる=発展)
  • 「いちばん評価が高い作品」は Max ではなく OrderByDescending(m => m.Score).FirstOrDefault() で取る
  • 平均は double が返る。表示は $"{平均:F1}" で小数第 1 位まで揃える
  • 検索結果が 0 件のときの表示を必ず入れる
  • ジャンルを入力させ、そのジャンルの平均評価 を表示する(Where で絞ってから Average)
  • Take(3) を調べて「評価トップ 3」を表示する(第 12 章では未習なので、調べて使う練習)

ここからは 目的を持った小さなアプリ を作ります。完成しなくても OK、設計の経験を積む ことを目的としてください。 レベル1・レベル2 を全部終わってから挑戦するのがおすすめです。


H-5-1 数当てゲーム(ステージ付き)(目安 2〜3 時間)

Section titled “H-5-1 数当てゲーム(ステージ付き)(目安 2〜3 時間)”

関連章:第 7 章(クラス)、第 8 章(staticRandom)、第 9 章(File 入出力)、第 10 章(コンストラクター・読み取り専用プロパティ・private set)、第 11 章(列挙型)

1〜100 の範囲の正解を当てる 数当てゲーム を、3 段階の難易度 で遊べるようにします。 ゲーム終了後、結果をファイルに保存 します。

列挙型仕様 Difficulty

Easy
Normal
Hard
難易度範囲最大試行回数ヒント
Easy1〜5010 回「もっと大きい」/「もっと小さい」
Normal1〜1008 回「もっと大きい」/「もっと小さい」
Hard1〜1005 回なし(正解 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 = 0IsWon = 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 メニューに戻る
  • 全終了時、これまでの GameRecordrecords.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)で確保し、何件入っているかを別変数で管理する
  • CSV 読込で過去の記録を一覧表示するメニューを追加
  • 難易度ごとの平均試行回数を表示
  • ヒントに「Hot/Cold」(差の絶対値で温度表現)を追加した第 4 難易度を作る

H-5-2 簡易家計簿アプリ(目安 3 時間以上・任意)

Section titled “H-5-2 簡易家計簿アプリ(目安 3 時間以上・任意)”

関連章:第 7 章(クラス)、第 9 章(FileDateTime)、第 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):カテゴリごとの合計を string 1 つの整形済みテキストで返す("食費: 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.TryParseDateTime.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; のような形
  • 全カテゴリ別の年間集計を表示するメニューを追加
  • 「金額の高い順 Top 3」を表示するメニューを追加
  • 入力時にカテゴリを選択肢から選ばせる(1: 食費 / 2: 交通費 / …)
  • 1 件削除メニューを追加(インデックス指定で削除、配列を 1 つ詰める)

時間が残ったら、書いたコードを次の観点で見直してください。

観点確認内容
クラスの責任1 つのクラスが「あれもこれも」していないか。Repository に分けられないか
カプセル化外から書き換えられたくないプロパティに private set を付けているか
コンストラクター必要な値を必ず受け取っているか。new した直後に「使えない状態」になっていないか
メソッドの名前bool を返すなら Is〇〇 / Has〇〇 / Can〇〇、副作用があるなら動詞始まりになっているか
マジックナンバー60100 のような数字に名前(定数 or 定義済みプロパティ)が付いているか
void と戻り値「結果を返す」なら void ではなく型を返しているか
LINQ の使いどころデータの変換・集計は LINQ、画面表示など副作用や中断は foreach になっているか(第 12 章 12-5)

完璧を目指す必要はありません。「書いた後に自分で読み直す」 習慣そのものが、配属後に効きます。


  • レベル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 章)に進む前の 土台固め として活用してください。