第14章 インターフェイス
この章の目的
Section titled “この章の目的”この章では、オブジェクト指向プログラミングで重要な インターフェイス(interface)を学習します。
第 13 章では、継承とポリモーフィズムを学習しました。 親クラスに共通部分をまとめ、子クラスがそれを受け継ぐ仕組みです。
インターフェイスも、ポリモーフィズムを支える仕組みの 1 つです。 ただし、親クラスのように 共通のデータを持つ のではなく、そのクラスが持つべき機能の約束 を表します。
印刷できる保存できる検索できる支給額を計算できるC# では、このような「できること」をインターフェイスとして定義します。
インターフェイス → 「このメソッドを持っていてください」というクラスへの約束
クラス → インターフェイスで決められたメソッドを実装するインターフェイスは、現場の C# コード(特に Web フレームワークや DB 接続まわり)で頻繁に登場します。 この章では、まず 「読める・基本的な動きが分かる」 ことを目指します。
この章でできるようになること
Section titled “この章でできるようになること”この章を終えると、次のことができるようになります。
- インターフェイスとは何かを「機能の約束」として説明できる
interfaceキーワードを使ってインターフェイスを定義できるI〇〇の命名習慣を説明できる- クラスでインターフェイスを実装できる
- インターフェイスにメソッドとプロパティを定義できる
- インターフェイス型の変数に実装クラスのオブジェクトを代入できる
List<インターフェイス>で複数のクラスをまとめて扱える- 1 つのクラスが複数のインターフェイスを実装できることを説明できる
- 継承とインターフェイスを組み合わせられる
- 抽象クラスとインターフェイスの違いをおおまかに説明できる
IEmployeeRepositoryのような名前を見て、Service・Repository の役割を想像できる
本章で使用する環境
Section titled “本章で使用する環境”| 項目 | 内容 |
|---|---|
| 開発環境 | Visual Studio 2022 |
| プロジェクト種類 | コンソール アプリ |
| 対象フレームワーク | .NET 8 |
| ソリューション名 | Chapter14 |
| プロジェクト名 | Ch14_Interface |
csproj の Nullable は disable に変更してください
プロジェクト作成後、
Ch14_Interface.csprojを開き、<Nullable>disable</Nullable>に変更してください。詳しい手順は、第 1 章「1-1 プロジェクトを作成する」を参照してください。
作業前チェック
Section titled “作業前チェック”作業を始める前に、次の内容を確認してください。
- クラス・プロパティ・メソッド・コンストラクターを書ける
- 継承(
: 親クラス)とoverrideを使える(第 13 章) - 抽象クラス(
abstract class)の役割を説明できる(第 13 章) -
List<T>とforeach、LINQ のFirstOrDefaultを使える(第 12 章) - 自作クラスを別ファイルに書ける
- 第 13 章の内容を Git に提出済みである
14-1 インターフェイスの基礎
Section titled “14-1 インターフェイスの基礎”身近な例でイメージをつかむ
Section titled “身近な例でイメージをつかむ”コードに入る前に、「機能の約束」というインターフェイスの考え方を、身近な例で見てみます。
例1:コンセントの差込口(プラグの形)
家電は、メーカーも種類もバラバラ(冷蔵庫・テレビ・スマホの充電器…)ですが、プラグの形さえ合えば、どれでもコンセントに挿して電気を使えます。 コンセント側は「この形のプラグなら受け入れる」という 約束 だけを決めていて、中の仕組み(冷やす・映す・充電する)は各家電の責任です。
- 約束(インターフェイス):「この形のプラグを持っていること」
- 実装(クラス):冷蔵庫・テレビ・充電器が、それぞれその形のプラグを備える
例2:リモコンの「再生ボタン」
DVD プレーヤーも、音楽プレーヤーも、動画アプリも、「▶(再生)」を押せば再生が始まります。 「再生できる」という 約束 さえ満たしていれば、利用者は中身の違いを気にせず同じ操作で扱えます。
例3:お店の「支払いできる」
現金・クレジットカード・電子マネーは、仕組みは全然違いますが、どれも「支払いができる」という約束を満たしています。 レジ(利用する側)は「支払いできる手段かどうか」だけ気にすればよく、それぞれの内部処理は各手段に任せます。
これらに共通するのは、
「〇〇できる」という約束だけを先に決めておく → 中身(どうやって実現するか)は、それぞれのモノに任せる → 約束を満たすモノなら、同じ扱い方ができるという考え方です。C# の インターフェイス は、まさにこの「〇〇できる、という約束」をコードで表す仕組みです。
本章では「印刷できる(IPrintable)」「支払い額を計算できる(IPayable)」といった約束を作っていきます。
補足:この章の例について
本文では
Employee(社員)などを題材にしますが、インターフェイスは社員に限った話ではありません。 上のコンセント・リモコン・支払いのように、種類の違うものを「同じ約束」でそろえて扱いたい 場面すべてで使えます。演習では、社員以外の題材(注文・顧客・商品など)でも練習します。
インターフェイスは「機能の約束」
Section titled “インターフェイスは「機能の約束」”先ほどの「〇〇できる」を、実際に C# のコードにしてみます。 まずは「印刷できる」という機能を考えます。 これをインターフェイスで表すと、次のようになります。
IPrintable.cs:
namespace Ch14_Interface;
interface IPrintable{ void Print();}IPrintable は「印刷できるもの」を表す約束です。
このインターフェイスを 実装する クラスは、必ず Print メソッドを持つ必要があります。
メソッド宣言には 中身({ })を書きません。約束だけを書きます。
中身は、実装するクラスの責任です。
補足:インターフェイス名の慣習
C# では、インターフェイス名の先頭に
Iを付けるのが慣習です(IPrintable、IPayable、IEmployeeRepositoryなど)。 「Interface」の頭文字で、現場のコードでも非常によく使われます。
クラスで実装する
Section titled “クラスで実装する”Employee クラスに IPrintable を実装します。
Employee.cs:
namespace Ch14_Interface;
class Employee : IPrintable{ public int EmployeeId { get; set; } public string EmployeeName { get; set; } public string DepartmentName { get; set; }
public void Print() { Console.WriteLine($"社員ID:{EmployeeId}"); Console.WriteLine($"氏名:{EmployeeName}"); Console.WriteLine($"部署:{DepartmentName}"); }}Program.cs:
namespace Ch14_Interface;
internal class Program{ static void Main(string[] args) { Employee employee = new Employee { EmployeeId = 1001, EmployeeName = "山田二郎", DepartmentName = "総務" };
employee.Print(); }}実行結果:
社員ID:1001氏名:山田二郎部署:総務インターフェイスを実装する書き方
Section titled “インターフェイスを実装する書き方”class Employee : IPrintable:の後ろに インターフェイス名 を書く(継承と同じ書き方)- インターフェイスで約束されたメンバーをすべて実装する必要がある
- 実装メソッドは
publicで書く(C# のインターフェイスメンバーはpublic扱い)
Print メソッドを書き忘れると、コンパイルエラーになります。
'Employee' は インターフェイス メンバー 'IPrintable.Print()' を実装しませんこのエラーが出たら「インターフェイスで約束されたメソッドが足りない」と読み解いて、メソッドを追加します。
継承と何が違う?
Section titled “継承と何が違う?”: の書き方は継承(第 13 章)と同じですが、意味が違います。
| 観点 | 継承(: 親クラス) | インターフェイス実装(: I〇〇) |
|---|---|---|
| 受け取るもの | 親のプロパティ・メソッドの 実装 | メソッドの 約束(中身は自前) |
| 数 | 1 つだけ | 複数可能 |
| 親側で値を持つ | 持てる | 持てない(本研修の範囲では) |
C# では : 親クラス, インターフェイス1, インターフェイス2 のように 継承 1 つ + インターフェイス複数 を組み合わせられます(後述)。
14-2 インターフェイス型として扱う
Section titled “14-2 インターフェイス型として扱う”インターフェイス型の変数
Section titled “インターフェイス型の変数”インターフェイスを実装したクラスのオブジェクトは、インターフェイス型の変数 に代入できます。
namespace Ch14_Interface;
internal class Program{ static void Main(string[] args) { IPrintable printable = new Employee { EmployeeId = 1001, EmployeeName = "山田二郎", DepartmentName = "総務" };
printable.Print(); }}実行結果:
社員ID:1001氏名:山田二郎部署:総務printable の 型は IPrintable ですが、中身は Employee オブジェクトです。
Print メソッドを呼び出すと、Employee 側に書いた処理が動きます。
インターフェイス型では「約束された機能」だけ使える
Section titled “インターフェイス型では「約束された機能」だけ使える”次のコードは エラー になります。
IPrintable printable = new Employee { EmployeeId = 1001, EmployeeName = "山田二郎", ... };
Console.WriteLine(printable.EmployeeName); // ← コンパイルエラーEmployee クラスには EmployeeName がありますが、IPrintable インターフェイスには定義されていない ためです。
変数の型 → IPrintable使えるもの → IPrintable に定義されたメンバー(Print() のみ)使えないもの → Employee 固有のメンバー(EmployeeName、EmployeeId など)これは制限のように見えますが、メリットもあります:呼び出し側がインターフェイスにだけ依存していれば、後から実装クラスを差し替えやすくなる からです(後述)。
インターフェイスにプロパティも定義できる
Section titled “インターフェイスにプロパティも定義できる”メソッドだけでなく、プロパティも約束できます。
IPrintable.cs(改造版):
namespace Ch14_Interface;
interface IPrintable{ string Title { get; } void Print();}Title { get; } は「読み取り可能な string 型プロパティを持っていてください」という約束です。
実装クラス側で値の入れ方を決めます。
SalesReport.cs:
namespace Ch14_Interface;
class SalesReport : IPrintable{ public string Title => "売上レポート";
public void Print() { Console.WriteLine("売上レポートを出力します。"); }}補足:
=>を使った省略記法
public string Title => "売上レポート";は、次の書き方の省略形です。public string Title { get { return "売上レポート"; } }1 行で値を返すだけのプロパティ・メソッドでよく使います。
Program.cs:
namespace Ch14_Interface;
internal class Program{ static void Main(string[] args) { IPrintable report = new SalesReport();
Console.WriteLine(report.Title); report.Print(); }}実行結果:
売上レポート売上レポートを出力します。14-3 複数のクラスを同じインターフェイスでまとめる
Section titled “14-3 複数のクラスを同じインターフェイスでまとめる”別々のクラスを「同じ機能で」まとめる
Section titled “別々のクラスを「同じ機能で」まとめる”インターフェイスの大きなメリットは、まったく違う種類のクラスを同じ機能でまとめられる ことです。
たとえば、次の 2 つのクラスは継承関係にありませんが、どちらも「印刷できる」機能を持ちます。
| クラス | 印刷する内容 |
|---|---|
Employee | 社員情報 |
SalesReport | 売上レポート |
両方に IPrintable を実装すると、同じ IPrintable 型として扱えます。
EmployeeとSalesReportを同じリストに入れる
Section titled “EmployeeとSalesReportを同じリストに入れる”Employee.cs(Title プロパティを追加):
namespace Ch14_Interface;
class Employee : IPrintable{ public int EmployeeId { get; set; } public string EmployeeName { get; set; } public string DepartmentName { get; set; }
public string Title => "社員情報";
public void Print() { Console.WriteLine($"社員ID:{EmployeeId}"); Console.WriteLine($"氏名:{EmployeeName}"); Console.WriteLine($"部署:{DepartmentName}"); }}SalesReport.cs:
namespace Ch14_Interface;
class SalesReport : IPrintable{ public DateTime ReportDate { get; set; } public int TotalAmount { get; set; }
public string Title => "売上レポート";
public void Print() { Console.WriteLine($"売上日:{ReportDate.ToString("yyyy/MM/dd")}"); Console.WriteLine($"売上合計:{TotalAmount}円"); }}Program.cs:
namespace Ch14_Interface;
internal class Program{ static void Main(string[] args) { List<IPrintable> printTargets = new List<IPrintable> { new Employee { EmployeeId = 1001, EmployeeName = "山田二郎", DepartmentName = "総務" }, new SalesReport { ReportDate = new DateTime(2026, 5, 18), TotalAmount = 1200000 } };
foreach (IPrintable target in printTargets) { Console.WriteLine($"=== {target.Title} ==="); target.Print(); } }}実行結果:
=== 社員情報 ===社員ID:1001氏名:山田二郎部署:総務=== 売上レポート ===売上日:2026/05/18売上合計:1200000円foreach の中では、target は IPrintable 型として扱われています。
それでも、実際のオブジェクトに応じた Print 処理が動きます。これも ポリモーフィズム です。
継承との比較
Section titled “継承との比較”第 13 章では List<EmployeeBase> で正社員・契約社員をまとめました(継承)。
第 14 章では List<IPrintable> で社員・売上レポートをまとめました(インターフェイス)。
| 仕組み | 共通の型 | 共通点 |
|---|---|---|
| 継承(第 13 章) | EmployeeBase | 「同じ親」を持つ |
| インターフェイス(第 14 章) | IPrintable | 「同じ機能」を持つ |
継承は「同じ家系」、インターフェイスは「同じ資格」と考えると分かりやすいです。
14-4 IPayable で支給額を計算する
Section titled “14-4 IPayable で支給額を計算する”IPayable インターフェイス
Section titled “IPayable インターフェイス”支給額を計算できることを表す IPayable を定義します。
IPayable.cs:
namespace Ch14_Interface;
interface IPayable{ int GetPaymentAmount();}正社員と契約社員に実装する
Section titled “正社員と契約社員に実装する”RegularEmployee.cs:
namespace Ch14_Interface;
class RegularEmployee : IPayable{ public string EmployeeName { get; set; } public int MonthlySalary { get; set; }
public int GetPaymentAmount() { return MonthlySalary; }}ContractEmployee.cs:
namespace Ch14_Interface;
class ContractEmployee : IPayable{ public string EmployeeName { get; set; } public int HourlyWage { get; set; } public int WorkHours { get; set; }
public int GetPaymentAmount() { return HourlyWage * WorkHours; }}List<IPayable> で支給額を集計する
Section titled “List<IPayable> で支給額を集計する”Program.cs:
namespace Ch14_Interface;
internal class Program{ static void Main(string[] args) { List<IPayable> payTargets = new List<IPayable> { new RegularEmployee { EmployeeName = "山田二郎", MonthlySalary = 500000 }, new ContractEmployee { EmployeeName = "田中浩介", HourlyWage = 1800, WorkHours = 120 } };
int totalPayment = 0; foreach (IPayable target in payTargets) { int amount = target.GetPaymentAmount(); Console.WriteLine($"支給額:{amount}円"); totalPayment += amount; }
Console.WriteLine($"支給合計:{totalPayment}円"); }}実行結果:
支給額:500000円支給額:216000円支給合計:716000円第 13 章との比較
第 13 章では
EmployeeBaseを継承してGetPaymentAmountをoverrideしました。 第 14 章ではIPayableを実装する形で同じことができました。 両方の書き方ができるのが C# の特徴で、「共通の親が必要なら継承、機能だけまとめたいならインターフェイス」 が一つの目安です。
14-5 複数のインターフェイス + 継承
Section titled “14-5 複数のインターフェイス + 継承”1 クラスで複数インターフェイス
Section titled “1 クラスで複数インターフェイス”C# では、1 つのクラスが 複数のインターフェイスを実装 できます。
, で区切って並べます。
class RegularEmployee : IPrintable, IPayable{ // IPrintable.Print と IPayable.GetPaymentAmount の両方を実装}例:
RegularEmployee.cs(両方を実装):
namespace Ch14_Interface;
class RegularEmployee : IPrintable, IPayable{ public int EmployeeId { get; set; } public string EmployeeName { get; set; } public string DepartmentName { get; set; } public int MonthlySalary { get; set; }
public string Title => "正社員情報";
public void Print() { Console.WriteLine($"社員ID:{EmployeeId}"); Console.WriteLine($"氏名:{EmployeeName}"); Console.WriteLine($"部署:{DepartmentName}"); }
public int GetPaymentAmount() { return MonthlySalary; }}両方のインターフェイス型として扱えます。
RegularEmployee employee = new RegularEmployee{ EmployeeId = 1001, EmployeeName = "山田二郎", DepartmentName = "総務", MonthlySalary = 500000};
IPrintable asPrintable = employee; // OKIPayable asPayable = employee; // OK
asPrintable.Print();Console.WriteLine($"支給額:{asPayable.GetPaymentAmount()}円");継承とインターフェイスの組み合わせ
Section titled “継承とインターフェイスの組み合わせ”C# では 継承は 1 つだけ ですが、インターフェイスは複数実装 できます。 両方を組み合わせる場合は、最初に親クラス、その後にインターフェイス を書きます。
class RegularEmployee : EmployeeBase, IPrintable, IPayable{ // EmployeeBase の継承 + 2 つのインターフェイスを実装}| ルール | 上限 |
|---|---|
| クラスの継承 | 1 つだけ |
| インターフェイスの実装 | 複数 OK |
14-6 抽象クラスとインターフェイスの違い
Section titled “14-6 抽象クラスとインターフェイスの違い”どちらもポリモーフィズムに使える
Section titled “どちらもポリモーフィズムに使える”abstract class(第 13 章)と interface(本章)は、どちらも共通の型として複数のクラスをまとめるのに使えます。
abstract class EmployeeBase{ public abstract int GetPaymentAmount();}interface IPayable{ int GetPaymentAmount();}似ていますが、向いている使い方が違います。
| 観点 | 抽象クラス | インターフェイス |
|---|---|---|
| キーワード | abstract class | interface |
| 主な役割 | 共通の 土台(共通データ + 共通処理) | 機能の約束 だけ |
| 共通プロパティを持たせる | できる(値も持てる) | できない(本研修の範囲では) |
| 共通処理を書く | できる | 書かない(本研修では) |
| クラスの利用数 | 1 クラスにつき 1 つだけ継承可 | 複数実装可 |
| 向いている例 | 社員の共通情報(ID・氏名) | 印刷できる、保存できる、検索できる |
使い分けの目安
Section titled “使い分けの目安”共通のデータや処理をまとめたい → 抽象クラスが候補
「この機能を持っていることを約束したい」だけ → インターフェイスが候補
両方を組み合わせたい → 抽象クラスを継承しつつ、インターフェイスも複数実装初学者の段階では、無理に完璧に使い分ける必要はありません。 現場のコードでは、Service・Repository のような層でインターフェイスがよく使われます(次節)。
14-7 ServiceとRepositoryのイメージ
Section titled “14-7 ServiceとRepositoryのイメージ”DB 接続編・Web アプリへの布石
Section titled “DB 接続編・Web アプリへの布石”後の章では、データベース(SQLServer)から社員情報を取得します。 そのとき、現場のコードでは次のような名前のクラス・インターフェイスがよく登場します。
| 名前 | 役割 |
|---|---|
IEmployeeRepository | 社員データの取得・保存などの 約束 |
SqlServerEmployeeRepository | SQLServer から取得する 実装 |
SampleEmployeeRepository | テスト用にサンプルデータを返す 実装 |
「Repository」は データの保管庫 を表す名前で、DB アクセスを担当するクラスによく使われます。
IEmployeeRepository を作る
Section titled “IEmployeeRepository を作る”Employee.cs:
namespace Ch14_Interface;
class Employee{ public int EmployeeId { get; set; } public string EmployeeName { get; set; }}IEmployeeRepository.cs:
namespace Ch14_Interface;
interface IEmployeeRepository{ List<Employee> GetAll(); Employee FindById(int employeeId);}FindById は、見つからなかった場合 null を返す可能性があります。
呼び出し側で null チェック を行う必要があります(第 11 章で学んだとおり)。
サンプル用の実装クラス
Section titled “サンプル用の実装クラス”DB に接続せず、固定データを返す実装を作ります。
SampleEmployeeRepository.cs:
namespace Ch14_Interface;
class SampleEmployeeRepository : IEmployeeRepository{ private List<Employee> _employees = new List<Employee> { new Employee { EmployeeId = 1001, EmployeeName = "山田二郎" }, new Employee { EmployeeId = 1002, EmployeeName = "佐藤昭夫" }, new Employee { EmployeeId = 1003, EmployeeName = "山口洋子" } };
public List<Employee> GetAll() { return _employees; }
public Employee FindById(int employeeId) { return _employees.FirstOrDefault(e => e.EmployeeId == employeeId); }}Program.cs:
namespace Ch14_Interface;
internal class Program{ static void Main(string[] args) { IEmployeeRepository repository = new SampleEmployeeRepository();
List<Employee> all = repository.GetAll(); foreach (Employee e in all) { Console.WriteLine($"{e.EmployeeId}:{e.EmployeeName}"); }
Employee found = repository.FindById(1002); if (found != null) { Console.WriteLine($"検索結果:{found.EmployeeName}"); } else { Console.WriteLine("該当する社員が見つかりませんでした。"); } }}実行結果:
1001:山田二郎1002:佐藤昭夫1003:山口洋子検索結果:佐藤昭夫実装を差し替えるイメージ
Section titled “実装を差し替えるイメージ”呼び出し側のコードは IEmployeeRepository 型 で書いてあります。
IEmployeeRepository repository = new SampleEmployeeRepository();第 17 章以降の DB 接続編では、ここを SqlServerEmployeeRepository に 差し替えるだけ で、SQLServer 版に切り替えられます。
// 後の DB 接続編で実装を差し替えるイメージ// IEmployeeRepository repository = new SqlServerEmployeeRepository();repository.GetAll() や repository.FindById(...) を呼び出す側のコードは、一切変える必要がない のがインターフェイスの強みです。
これは、テスト や 環境ごとの切り替え(本番 / 開発 / テスト)で大きな威力を発揮します。
補足:DI(依存性注入)について
Web アプリ・業務システムでは、
IEmployeeRepositoryのような型を、外部から渡す 仕組み(Dependency Injection, DI)がよく使われます。 本研修では深く扱いませんが、IEmployeeRepositoryのような名前を見たら「外から実装を差し替えられる作りだな」と読めることを目指します。
よくあるつまずき
Section titled “よくあるつまずき”| つまずき | 原因 | 対応 |
|---|---|---|
| インターフェイスの意味が分からない | 中身がなく抽象的に見える | 「機能の約束」と覚える |
interface と class の違いが分からない | 両方とも型として使える | クラスは設計図、インターフェイスは約束だけ |
I〇〇 の I が分からない | 命名規則を知らない | Interface の頭文字 |
| インターフェイスのメソッドに処理を書こうとする | クラスメソッドと混同 | 本研修ではインターフェイス側に中身を書かない |
| 実装クラスでメソッドを書き忘れる | 約束されたメソッドが不足 | エラーメッセージから不足メソッドを追加 |
| インターフェイス型でクラス固有のメンバーが使えない | インターフェイス型では「約束されたもの」だけ | 必要なら実装クラス型で受ける |
| 抽象クラスとの違いが分からない | どちらもポリモーフィズムに使える | 抽象クラス=共通の土台、インターフェイス=機能の約束 |
| 複数インターフェイスの書き方が分からない | カンマで区切る感覚がない | : I1, I2, I3 のようにカンマ区切り |
| 継承とインターフェイスの順序を間違える | どちらが先か分からない | 親クラスを最初に、続けてインターフェイス |
IEmployeeRepository の意味が見えてこない | DI や差し替えの感覚がない | 「データ取得の約束」と読み、実装は後から差し替え可 |
学んだことチェック
Section titled “学んだことチェック”- インターフェイスとは「機能の約束」であることを説明できる
-
interfaceキーワードでインターフェイスを定義できる -
I〇〇の命名習慣を説明できる - クラスでインターフェイスを実装できる
- インターフェイスにメソッドとプロパティを定義できる
- インターフェイス型の変数に実装クラスのオブジェクトを代入できる
- インターフェイス型では約束されたメンバーだけ使えることを説明できる
-
List<インターフェイス>で複数のクラスをまとめて扱える - 1 つのクラスに複数のインターフェイスを実装できる
- 継承とインターフェイスを組み合わせられる(順序:親クラスが先)
- 抽象クラスとインターフェイスの違いを説明できる
-
IEmployeeRepositoryのような名前を見て、Repository の役割を想像できる
研修の進め方によっては、隣の人またはチーム内で説明確認を行います。
次の内容を、自分の言葉で説明してください。
- インターフェイスとは何ですか。
IPrintableはどのような役割を持ちますか。class Employee : IPrintableは何を意味しますか。- インターフェイス型の変数では、どのメンバーを使えますか。
List<IPrintable>に異なるクラスのオブジェクトを入れられるのはなぜですか。- クラスの継承とインターフェイスの実装は、それぞれいくつまでできますか。
- 抽象クラスとインターフェイスの使い分けは、どのように考えますか。
IEmployeeRepositoryを使うと、何が嬉しいですか。
説明するときは、完全な答えでなくても構いません。 自分の言葉で説明しようとすることが大切です。
この章の演習課題に取り組みます。複数のインターフェイス・クラスファイルを作る、歯ごたえのある演習です。
本章の進め方
Section titled “本章の進め方”| 段階 | 目安時間 | 内容 |
|---|---|---|
| ① 準備 | 10 分 | ペア確認 + 課題確認(評価対象外) |
| ② ソロ作業 | 35 分 | タイマーで計測。タイマー時点の commit が唯一の評価対象(別ファイルでインターフェイス・クラスを複数作るため長めに設定) |
| ③ チーム時間 | 講師指定の発表開始時刻まで | レビュー + 発表者選出 + 実装続行(任意)。発表開始時刻は厳守 |
提出ルール(タイマー方式)
タイマー時点の commit が唯一の評価対象です。タイマー後の書き足しは評価されません。 コミットメッセージ形式:
Chapter14 タイマー提出: <どこまで完成> / <詰まったポイント>(なければ「特になし」) 例:Chapter14 タイマー提出: 必須14-1・14-2完成、発展14-3途中 / List<IExportable> でまとめる型でつまずいた
提出方法:Git が使えないときはサーバへコピー
講師の指示があったときは、
pushの代わりにKadai14フォルダを提出先サーバへコピーし、コピー先に提出メモ.txt(「どこまで完成」「詰まったポイント」を記載)を作成してください。
タイマー後のチーム時間の使い方
レビュー・発表者選出・実装続行(任意)を自由配分してください。発表開始時刻は厳守です。
この章の演習の進め方
Section titled “この章の演習の進め方”この章では、課題ごとにコンソールアプリのプロジェクトを作成 します。 各課題では、指定されたプロジェクト名を使ってください。
| 課題 | 必須/発展 | プロジェクト名 | 作成する主なファイル |
|---|---|---|---|
| 課題 14-1 | 必須 | Kd14_01_Exportable | Program.cs、IExportable.cs、Order.cs、Customer.cs |
| 課題 14-2 | 必須 | Kd14_02_Taxable | Program.cs、IExportable.cs、ITaxable.cs、Order.cs |
| 課題 14-3 | 発展 | Kd14_03_ProductRepository | Program.cs、Product.cs、IProductRepository.cs、SampleProductRepository.cs |
| 課題 14-4 | 発展 | Kd14_04_RepositorySwap | Program.cs、課題 14-3 の 4 ファイル + EmptyProductRepository.cs |
課題用プロジェクトは、課題用ソリューション Kadai14 の中にまとめます(講義用の Chapter14 ソリューションとは分けます)。
Kadai14/ Kadai14.sln Kd14_01_Exportable/ Kd14_02_Taxable/ Kd14_03_ProductRepository/ Kd14_04_RepositorySwap/最初の課題で
Kadai14ソリューションとKd14_01_Exportableプロジェクトを同時に作成し、2 つ目以降はそのソリューションに プロジェクトを追加 していきます。各プロジェクトの csproj は忘れず<Nullable>disable</Nullable>に変更してください。クラス・インターフェイスのファイルを自動生成すると
namespace プロジェクト名 { ... }(ブロック形式)で作られます。本研修はファイルスコープ形式に統一するため、namespace Kd14_01_Exportable;のように;形式へ書き換え、同じプロジェクト内の各ファイルの namespace を揃えてください。
まずは、全員が必須課題に取り組んでください。
この章の演習は「データの出力・税計算・商品リポジトリ」を題材にします
本文では社員(
Employee)やIPrintable/IPayable/IEmployeeRepositoryを題材にインターフェイスを学びました。演習では 別の題材 で、同じ仕組みを自分で組み立てます。 本文のコードをそのまま写しても解けません。本文で学んだ「インターフェイス=機能の約束」「インターフェイス型でまとめる」「実装を差し替える」考え方を、別の仕様に当てはめて 実装してください。
課題 14-1 IExportable でまとめて書き出す
Section titled “課題 14-1 IExportable でまとめて書き出す”「1 行のテキスト(カンマ区切り)に書き出せる」という機能を表す IExportable インターフェイスを作成し、まったく種類の違う 2 つのクラス(注文 Order と 顧客 Customer)に実装してください。
両方を List<IExportable> にまとめ、同じ呼び出しで書き出します。
IExportable 仕様
- ファイル名:
IExportable.cs - メソッド:
string ToCsvLine();(カンマ区切りの 1 行文字列を返す)
Order(注文)クラス仕様
- ファイル名:
Order.cs - 実装:
IExportable - プロパティ:
OrderId(int)、ProductName(string)、Quantity(int) ToCsvLine():"1,ノート,3"の形式(OrderId,ProductName,Quantityをカンマで連結)
Customer(顧客)クラス仕様
- ファイル名:
Customer.cs - 実装:
IExportable - プロパティ:
CustomerId(int)、CustomerName(string)、Prefecture(string) ToCsvLine():"101,田中商店,東京都"の形式(CustomerId,CustomerName,Prefectureをカンマで連結)
Main メソッド
List<IExportable>にOrder2 件とCustomer1 件を入れるOrder:(1, “ノート”, 3) / (2, “マグカップ”, 2)Customer:(101, “田中商店”, “東京都”)
foreachで各要素のToCsvLine()を呼び出して 1 行ずつ表示
実行結果例:
1,ノート,32,マグカップ,2101,田中商店,東京都条件:
List<IExportable>を使う(OrderとCustomerという 無関係なクラス を同じリストに入れる)- カンマ区切りの文字列は
string.Join(",", ...)または文字列補間$"{...},{...}"で作る(引用符での囲みなどは不要。第 22 章で扱う本格的な CSV とは別の、簡単な連結でよい) foreachの中で型判定のifは書かない(実体ごとのToCsvLine()が動く)
課題 14-2 複数インターフェイス + ITaxable で税込合計
Section titled “課題 14-2 複数インターフェイス + ITaxable で税込合計”注文に「税込金額を計算できる」機能(ITaxable)を追加します。Order に IExportable と ITaxable の 両方 を実装し、税込金額の合計を求めてください。
IExportable 仕様(課題 14-1 と同じ)
- ファイル名:
IExportable.cs - メソッド:
string ToCsvLine();
ITaxable 仕様
- ファイル名:
ITaxable.cs - メソッド:
int GetTaxIncludedPrice();(税込価格を返す)
Order クラス仕様
- ファイル名:
Order.cs - 実装:
IExportable, ITaxable(2 つのインターフェイスを実装) - プロパティ:
OrderId(int)、ProductName(string)、Quantity(int)、UnitPrice(int) ToCsvLine():"1,ノート,3,180"の形式(OrderId,ProductName,Quantity,UnitPrice)GetTaxIncludedPrice():税抜金額(Quantity * UnitPrice)に消費税 10% を加えた 税込金額(整数)を返す- 計算式:
Quantity * UnitPrice * 110 / 100(整数の掛け算 → 割り算の順で、小数点以下は切り捨て)
- 計算式:
Main メソッド
List<Order>に次の 2 件を入れる- (1, “ノート”, 3, 180)
- (2, “マグカップ”, 2, 800)
foreachで各注文のToCsvLine()とGetTaxIncludedPrice()を表示- 最後に 税込金額の合計 を LINQ の
Sumで求めて表示
実行結果例:
1,ノート,3,180 → 税込594円2,マグカップ,2,800 → 税込1760円税込合計:2354円注文1:3 × 180 = 540 → 540 × 110 / 100 = 594 円。注文2:2 × 800 = 1600 → 1600 × 110 / 100 = 1760 円。合計 2354 円。
条件:
class Order : IExportable, ITaxableのように 複数インターフェイスを実装- 税込計算は 整数の掛け算を先に、割り算を後に(
* 110 / 100)。* 1.1のような小数を使うと型がずれるので使わない - 合計は LINQ の
Sumを使う(orders.Sum(o => o.GetTaxIncludedPrice()))
必須課題が終わった人は、発展課題に取り組んでください。 発展課題からは、仕様だけが提示されます。実装方法は自分で考えてください。
課題 14-3 IProductRepository を作成する
Section titled “課題 14-3 IProductRepository を作成する”商品データの取得を担当する IProductRepository インターフェイスと、その実装クラスを作成してください。
取得メソッドには、全件取得・ID 検索 に加えて、カテゴリでの絞り込み(自分で Where を書く)を含めます。
Product クラス仕様
- ファイル名:
Product.cs - プロパティ:
ProductId(int)、ProductName(string)、Category(string)、Price(int)
IProductRepository 仕様
- ファイル名:
IProductRepository.cs - メソッド:
List<Product> GetAll():全商品を返すProduct FindById(int productId):該当商品を 1 件返す。見つからなければnullを返すList<Product> FindByCategory(string category):指定カテゴリの商品だけ を返す(0 件なら空のリスト)
SampleProductRepository 仕様
- ファイル名:
SampleProductRepository.cs - 実装:
IProductRepository - 内部に固定の
List<Product>を持つ(次の 4 件)
| ID | 商品名 | カテゴリ | 価格 |
|---|---|---|---|
| 1 | ノート | 文房具 | 180 |
| 2 | ボールペン | 文房具 | 120 |
| 3 | マグカップ | 食器 | 800 |
| 4 | タンブラー | 食器 | 1700 |
FindByIdは LINQ のFirstOrDefaultを使うFindByCategoryは LINQ のWhere(...).ToList()で絞り込む
Main メソッド
IProductRepository型の変数にSampleProductRepositoryを代入GetAll()で全商品を表示FindByCategory("食器")で絞り込んで表示FindById(2)で検索して表示、FindById(999)も試す(null なら「見つかりませんでした」)
実行結果例:
[全商品]1:ノート(文房具)180円2:ボールペン(文房具)120円3:マグカップ(食器)800円4:タンブラー(食器)1700円
[食器]3:マグカップ(食器)800円4:タンブラー(食器)1700円
[検索 2]ボールペン(文房具)120円
[検索 999]商品が見つかりませんでした。条件:
- メソッドの戻り値の型(
List<Product>/Product)を仕様どおりに書く FindByCategoryは自分でWhereを書いて絞り込む(本文のGetAll/FindByIdには無いメソッド)- 見つからない場合(
null/ 空リスト)の処理を入れる
課題 14-4 Repository を差し替えるイメージ
Section titled “課題 14-4 Repository を差し替えるイメージ”課題 14-3 を発展させ、実装クラスを差し替える ことのメリットをコードで示してください。
追加クラス仕様
EmptyProductRepository:IProductRepositoryを実装。GetAll()とFindByCategory(...)は 空のリスト、FindById(...)は常にnullを返す- データがまだない、DB に接続できない、テスト用といった状況を模擬する
この課題のポイント
IProductRepository 型を引数に受け取る 共通の表示メソッド を 1 つ用意します。
このメソッドは「どの実装クラスが渡されたか」を気にせず、IProductRepository の約束(GetAll())だけを使って動きます。
そこに 中身の違う 2 つの実装(SampleProductRepository と EmptyProductRepository)を渡すと、同じメソッドが渡された実装に応じて違う結果を出す ことを確認できます。これが「実装を差し替えられる」インターフェイスの強みです。
手順
- 次の
PrintAllProductsメソッド を、Programクラスの中(Mainと並べて)に作る。引数の型がIProductRepository(具体クラスではない) であることがポイント。
static void PrintAllProducts(IProductRepository repository){ List<Product> all = repository.GetAll(); if (all.Count == 0) { Console.WriteLine("商品データはありません。"); return; } foreach (Product p in all) { Console.WriteLine($"{p.ProductId}:{p.ProductName}({p.Category}){p.Price}円"); }}Mainの中で、このPrintAllProductsを 異なる実装クラスを渡して 2 回 呼び出す。
static void Main(string[] args){ Console.WriteLine("[SampleProductRepository]"); PrintAllProducts(new SampleProductRepository());
Console.WriteLine(); Console.WriteLine("[EmptyProductRepository]"); PrintAllProducts(new EmptyProductRepository());
// 後の DB 接続編では、ここを差し替えるだけで本物の DB から取れる // PrintAllProducts(new SqlServerProductRepository());}- 最後の行のように、後の DB 接続編で
SqlServerProductRepositoryに差し替えるイメージ をコメントで 1 行残す(SqlServerProductRepository自体は実装しなくてよい)。
実行結果例:
[SampleProductRepository]1:ノート(文房具)180円2:ボールペン(文房具)120円3:マグカップ(食器)800円4:タンブラー(食器)1700円
[EmptyProductRepository]商品データはありません。条件:
PrintAllProductsの引数の型はIProductRepository(具体クラスではない)- 同じメソッドが、異なる実装クラスで動作することを確認
IProductRepository repository = new SqlServerProductRepository();のような差し替えコメントを残す(SqlServerProductRepositoryは実装しなくてよい)
提出前チェックリスト
Section titled “提出前チェックリスト”- プログラムを Visual Studio から実行できる
-
interfaceでインターフェイスを定義できている - インターフェイス名の先頭に
Iを付けている - クラスでインターフェイスを実装できている
- インターフェイス型の変数を使えている
-
List<インターフェイス>で異なるクラスをまとめて扱えている - 複数のインターフェイス(
IExportable+ITaxable)を実装できている -
IProductRepositoryのような Repository インターフェイスを書けている(発展) - 実装クラスを差し替えるイメージを理解している(発展)
- クラス・インターフェイスを別ファイルに分けて書けている
- インデントが整っている
- タイマー時点で commit 済み(または
提出メモ.txtを書いた)
Git への提出
Section titled “Git への提出”タイマーが鳴ったら、その時点の状態を Git に提出します。
git statusgit add .git commit -m "Chapter14 タイマー提出: 必須14-1・14-2完成、発展14-3途中 / 特になし"git push origin mainGit が使えないときは、上記コミットの代わりに Kadai14 フォルダを提出先サーバへコピーし、コピー先に 提出メモ.txt を作成してください(演習課題の「提出方法:Git が使えないときはサーバへコピー」参照)。
Git の詳しい操作は、付録 C「Git のインストールと提出ルール」 を参照してください。
この章のまとめ
Section titled “この章のまとめ”この章では、インターフェイスを学習しました。
- インターフェイスは、クラスが持つべき 機能の約束 を表す
interfaceキーワードで定義し、慣習として名前の先頭にIを付ける- インターフェイス自体には処理を書かず、約束だけを書く
- クラスは
: I〇〇の形でインターフェイスを実装する - 実装クラスは、約束されたメンバーをすべて持つ必要がある
- インターフェイス型の変数では、約束されたメンバーだけが使える
List<インターフェイス>で異なるクラスをまとめて扱える(ポリモーフィズム)- 1 つのクラスは 複数のインターフェイスを実装 できる
- 継承(1 つだけ)とインターフェイス(複数可)を組み合わせられる
- 抽象クラスは「共通の土台」、インターフェイスは「機能の約束」と考えると分かりやすい
IEmployeeRepositoryのような Repository インターフェイスを使うと、実装(サンプル / SQLServer / テスト用)を差し替えやすくなる- 後の DB 接続編・Web アプリ編で、この考え方が活きてくる
次章では、例外処理 を学習します。
プログラムでは、入力ミス、ファイルが存在しない、DB 接続失敗など、さまざまなエラーが発生します。 例外処理を学ぶことで、エラーが発生したときにプログラムを安全に止めたり、回復したりする方法を身に付けます。 特に、DB 接続を扱う第 17 章以降では、例外処理の考え方が必須になります。