Day 72:設計 タスク管理アプリ制作
Day 72では、タスク1件分のデータをどのような形で表すか設計します。前日の要件定義で決めた機能を、C#の型やクラスに置き換える段階です。
今回のタスクは、次の情報を持ちます。
TaskItem
├─ Id
├─ Title
├─ Description
├─ Priority
├─ DueDate
├─ IsCompleted
└─ CreatedAt
クラスの設計では、項目の名前だけでなく「どの型にするか」「省略できるか」「初期値は何か」も決めます。今回はメンテナンスしやすいように、タスクのデータと、追加・検索・削除などの操作を担当する処理を分けます。
ステップ1:クラス名を決める
図では Task としていますが、C#では非同期処理で使う System.Threading.Tasks.Task という型がよく登場します。自作クラスも Task と名付けると、コードによってはどちらの型なのか分かりにくくなります。
そのため、このアプリでは TaskItem という名前を使います。「タスク1件を表すデータ」という意味で、用途も伝わりやすい名前です。
ステップ2:各項目の型とルールを決める
| 項目 | 型 | 意味とルール |
|---|---|---|
Id | int | タスクを識別する番号です。タスクごとに重複しない値を割り当てます |
Title | string | タスクの短い名前です。必須にします |
Description | string | 詳細説明です。省略できるようにします |
Priority | Priority | 優先度です。「低・中・高」から選びます |
DueDate | DateTime? | 期限です。未設定の場合を表せるようにします |
IsCompleted | bool | 完了していれば true、未完了なら false です |
CreatedAt | DateTime | タスクを作成した日時です |
この設計で特に重要なのは、DueDate を DateTime? にすることです。期限は任意なので、通常の DateTime ではなく、値がない状態も表せる DateTime? を使います。
ステップ3:優先度を enum で定義する
優先度を文字列で扱うと、「高」「こう」「High」など、入力の揺れが起きる可能性があります。enum を使うと、用意した選択肢から優先度を選べます。
Models/Priority.cs を作成します。
namespace TaskManager.Models
{
// タスクに設定できる優先度を定義します
public enum Priority
{
Low, // 低
Medium, // 中
High // 高
}
}C#Priority は TaskItem の中に直接文字列として書くのではなく、独立した型として定義しています。こうすると、アプリのほかの部分でも同じ選択肢を使えます。
ステップ4:TaskItem クラスを作る
Models/TaskItem.cs を作成し、タスク1件分の情報をプロパティとして定義します。
using System;
namespace TaskManager.Models
{
// タスク1件分のデータを表します
public class TaskItem
{
// タスクを識別する番号です
public int Id { get; set; }
// タスクのタイトルです。未設定の状態を避けるため空文字列を初期値にします
public string Title { get; set; } = "";
// 詳細説明です。入力がない場合は空文字列にします
public string Description { get; set; } = "";
// 優先度です。指定がなければ「中」にします
public Priority Priority { get; set; } = Priority.Medium;
// 期限です。nullの場合は期限が設定されていません
public DateTime? DueDate { get; set; }
// 完了状態です。初期値はfalseなので未完了です
public bool IsCompleted { get; set; }
// タスクを作成した日時です
public DateTime CreatedAt { get; set; } = DateTime.Now;
}
}C#各プロパティに初期値を設定しているのは、タスクを作るときに値の入れ忘れがあっても、不自然な状態になりにくくするためです。たとえば、優先度を指定しなければ Medium、完了状態を指定しなければ false になります。
Title は初期値を空文字列にしていますが、これだけで必須入力が保証されるわけではありません。タスクを追加する処理で、空白だけのタイトルを登録できないように確認する必要があります。
ステップ5:タスクを作成して表示する
プロパティを用意したら、オブジェクト初期化子でタスクを作成できます。
using System;
using TaskManager.Models;
class Program
{
static void Main()
{
// 新しいタスクを作成します
TaskItem task = new TaskItem
{
Id = 1,
Title = "報告書を作る",
Description = "月次の売上データをまとめます",
Priority = Priority.High,
DueDate = new DateTime(2026, 11, 20),
IsCompleted = false
};
// タスクの情報を表示します
Console.WriteLine($"ID:{task.Id}");
Console.WriteLine($"タイトル:{task.Title}");
Console.WriteLine($"説明:{task.Description}");
Console.WriteLine($"優先度:{task.Priority}");
Console.WriteLine($"完了:{task.IsCompleted}");
Console.WriteLine($"作成日時:{task.CreatedAt}");
// 期限が設定されている場合だけ、期限を表示します
if (task.DueDate.HasValue)
{
Console.WriteLine($"期限:{task.DueDate.Value:yyyy-MM-dd}");
}
else
{
Console.WriteLine("期限:なし");
}
}
}C#DueDate は DateTime? なので、値があるかどうかを HasValue で確認しています。値がない場合に .Value を使うと例外になるため、先に確認してから取り出すのが大切です。
ステップ6:作成日時と期限の違いを理解する
CreatedAt と DueDate は、どちらも日付や時刻を表しますが、役割が異なります。
- `CreatedAt` は、タスクを登録した日時です。通常はアプリ側で自動設定します
- `DueDate` は、タスクを終える期限です。利用者が設定し、省略できるようにします
コンソールアプリの最初の版では DateTime.Now を使い、実行環境の現在日時を記録します。複数の地域やサーバーで日時を扱うアプリでは、タイムゾーンの影響を避けるために DateTimeOffset やUTCでの保存を検討します。まずはこのアプリの利用範囲に合った方法を選び、日時の扱いをアプリ内で統一することが大切です。
ステップ7:データと操作を分ける
TaskItem は、タスクの情報を表すクラスです。タスクの追加、検索、編集、削除といった処理まで詰め込まず、別のクラスに担当させます。
たとえば、今後は次のように分けられます。
TaskItem
タスク1件分のデータを持つ
TaskManager
タスクの追加、検索、編集、削除、完了変更を担当する
Program
メニューの表示や利用者の入力を受け付ける
このように責務を分けると、画面の入力方法を変更しても、タスクのデータ構造や管理処理に影響しにくくなります。Day 72では、まず TaskItem と Priority を作り、次の実装段階でタスクの管理処理を加える準備をします。
設計で大切な注意点
- `Id` はタスクを特定するための値なので、同じIDを複数のタスクに割り当てないようにします
- `Title` は空白だけの文字列を許さないよう、追加処理で検証します
- `DueDate` は任意なので、期限なしを `null` で表します
- 優先度は自由入力の文字列ではなく、`enum` で選択肢を限定します
- `CreatedAt` は登録時に設定し、編集で意図せず書き換えないように設計します
- データを表すクラスに、画面表示や保存処理を集めすぎないようにします
特に Id と CreatedAt は、タスクの作成後にむやみに変える項目ではありません。学習用の最初のコードでは簡潔さを優先して set を公開していますが、実装が進んだら、生成時に一度だけ設定できる設計にすることも検討できます。
Day 72の基本テンプレート
タスクの情報を追加・変更するときは、次の項目を確認します。
クラス名:
このクラスが表すもの:
プロパティ:
・名前:
・型:
・必須か:
・初期値:
・変更できるか:
入力ルール:
・空欄を許可するか:
・範囲や選択肢があるか:
・値がない状態をどう表すか:
今回の設計に当てはめると、DueDate は「任意」「未設定は null」、Priority は「Low・Medium・Highのいずれか」と整理できます。
Day 72のゴール
- タスクの各項目に適切なC#の型を選べます
- 優先度を `enum` で表せます
- 期限のような任意項目を `DateTime?` で扱えます
- 初期値が必要な項目と、その理由を説明できます
- タスクのデータと、タスクを管理する処理を分ける設計を理解できます
まとめ
Day 72では、タスク1件分の情報を TaskItem クラスとして設計しました。必須のタイトル、選択肢を限定する優先度、任意の期限、初期状態としての未完了、作成日時など、それぞれの項目の意味に合った型と初期値を決めることが重要です。
- データの項目ごとに、型と入力ルールを決めます
- 優先度は `enum`、期限は `DateTime?` で表します
- 初期値を設け、タスク作成直後の状態を明確にします
- `TaskItem` はデータを表し、タスク管理の処理は別のクラスに分けます
まずは Priority.cs と TaskItem.cs を作り、サンプルのタスクを一件作成して表示してください。データの形が決まると、次の段階でタスク追加や一覧表示を実装しやすくなります。
