Skip to content

第14章 インターフェイス

この章では、オブジェクト指向プログラミングで重要な インターフェイス(interface)を学習します。

第 13 章では、継承とポリモーフィズムを学習しました。 親クラスに共通部分をまとめ、子クラスがそれを受け継ぐ仕組みです。

インターフェイスも、ポリモーフィズムを支える仕組みの 1 つです。 ただし、親クラスのように 共通のデータを持つ のではなく、そのクラスが持つべき機能の約束 を表します。

印刷できる
保存できる
検索できる
支給額を計算できる

C# では、このような「できること」をインターフェイスとして定義します。

インターフェイス
→ 「このメソッドを持っていてください」というクラスへの約束
クラス
→ インターフェイスで決められたメソッドを実装する

インターフェイスは、現場の C# コード(特に Web フレームワークや DB 接続まわり)で頻繁に登場します。 この章では、まず 「読める・基本的な動きが分かる」 ことを目指します。


この章でできるようになること

Section titled “この章でできるようになること”

この章を終えると、次のことができるようになります。

  • インターフェイスとは何かを「機能の約束」として説明できる
  • interface キーワードを使ってインターフェイスを定義できる
  • I〇〇 の命名習慣を説明できる
  • クラスでインターフェイスを実装できる
  • インターフェイスにメソッドとプロパティを定義できる
  • インターフェイス型の変数に実装クラスのオブジェクトを代入できる
  • List<インターフェイス> で複数のクラスをまとめて扱える
  • 1 つのクラスが複数のインターフェイスを実装できることを説明できる
  • 継承とインターフェイスを組み合わせられる
  • 抽象クラスとインターフェイスの違いをおおまかに説明できる
  • IEmployeeRepository のような名前を見て、Service・Repository の役割を想像できる

項目内容
開発環境Visual Studio 2022
プロジェクト種類コンソール アプリ
対象フレームワーク.NET 8
ソリューション名Chapter14
プロジェクト名Ch14_Interface

csproj の Nullable は disable に変更してください

プロジェクト作成後、Ch14_Interface.csproj を開き、<Nullable>disable</Nullable> に変更してください。

詳しい手順は、第 1 章「1-1 プロジェクトを作成する」を参照してください。


作業を始める前に、次の内容を確認してください。

  • クラス・プロパティ・メソッド・コンストラクターを書ける
  • 継承(: 親クラス)と override を使える(第 13 章)
  • 抽象クラス(abstract class)の役割を説明できる(第 13 章)
  • List<T>foreach、LINQ の FirstOrDefault を使える(第 12 章)
  • 自作クラスを別ファイルに書ける
  • 第 13 章の内容を Git に提出済みである

コードに入る前に、「機能の約束」というインターフェイスの考え方を、身近な例で見てみます。

例1:コンセントの差込口(プラグの形)

家電は、メーカーも種類もバラバラ(冷蔵庫・テレビ・スマホの充電器…)ですが、プラグの形さえ合えば、どれでもコンセントに挿して電気を使えます。 コンセント側は「この形のプラグなら受け入れる」という 約束 だけを決めていて、中の仕組み(冷やす・映す・充電する)は各家電の責任です。

  • 約束(インターフェイス):「この形のプラグを持っていること」
  • 実装(クラス):冷蔵庫・テレビ・充電器が、それぞれその形のプラグを備える

例2:リモコンの「再生ボタン」

DVD プレーヤーも、音楽プレーヤーも、動画アプリも、「▶(再生)」を押せば再生が始まります。 「再生できる」という 約束 さえ満たしていれば、利用者は中身の違いを気にせず同じ操作で扱えます。

例3:お店の「支払いできる」

現金・クレジットカード・電子マネーは、仕組みは全然違いますが、どれも「支払いができる」という約束を満たしています。 レジ(利用する側)は「支払いできる手段かどうか」だけ気にすればよく、それぞれの内部処理は各手段に任せます。

これらに共通するのは、

「〇〇できる」という約束だけを先に決めておく
→ 中身(どうやって実現するか)は、それぞれのモノに任せる
→ 約束を満たすモノなら、同じ扱い方ができる

という考え方です。C# の インターフェイス は、まさにこの「〇〇できる、という約束」をコードで表す仕組みです。 本章では「印刷できる(IPrintable)」「支払い額を計算できる(IPayable)」といった約束を作っていきます。

補足:この章の例について

本文では Employee(社員)などを題材にしますが、インターフェイスは社員に限った話ではありません。 上のコンセント・リモコン・支払いのように、種類の違うものを「同じ約束」でそろえて扱いたい 場面すべてで使えます。演習では、社員以外の題材(注文・顧客・商品など)でも練習します。


インターフェイスは「機能の約束」

Section titled “インターフェイスは「機能の約束」”

先ほどの「〇〇できる」を、実際に C# のコードにしてみます。 まずは「印刷できる」という機能を考えます。 これをインターフェイスで表すと、次のようになります。

IPrintable.cs:

IPrintable.cs
namespace Ch14_Interface;
interface IPrintable
{
void Print();
}

IPrintable は「印刷できるもの」を表す約束です。 このインターフェイスを 実装する クラスは、必ず Print メソッドを持つ必要があります。

メソッド宣言には 中身({ })を書きません。約束だけを書きます。 中身は、実装するクラスの責任です。

補足:インターフェイス名の慣習

C# では、インターフェイス名の先頭に I を付けるのが慣習です(IPrintableIPayableIEmployeeRepository など)。 「Interface」の頭文字で、現場のコードでも非常によく使われます。


Employee クラスに IPrintable を実装します。

Employee.cs:

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:

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()' を実装しません

このエラーが出たら「インターフェイスで約束されたメソッドが足りない」と読み解いて、メソッドを追加します。


: の書き方は継承(第 13 章)と同じですが、意味が違います

観点継承(: 親クラス)インターフェイス実装(: I〇〇)
受け取るもの親のプロパティ・メソッドの 実装メソッドの 約束(中身は自前)
1 つだけ複数可能
親側で値を持つ持てる持てない(本研修の範囲では)

C# では : 親クラス, インターフェイス1, インターフェイス2 のように 継承 1 つ + インターフェイス複数 を組み合わせられます(後述)。


14-2 インターフェイス型として扱う

Section titled “14-2 インターフェイス型として扱う”

インターフェイスを実装したクラスのオブジェクトは、インターフェイス型の変数 に代入できます。

Program.cs
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(改造版):

IPrintable.cs
namespace Ch14_Interface;
interface IPrintable
{
string Title { get; }
void Print();
}

Title { get; } は「読み取り可能な string 型プロパティを持っていてください」という約束です。 実装クラス側で値の入れ方を決めます。

SalesReport.cs:

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:

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 プロパティを追加):

Employee.cs
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:

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:

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 の中では、targetIPrintable 型として扱われています。 それでも、実際のオブジェクトに応じた Print 処理が動きます。これも ポリモーフィズム です。


第 13 章では List<EmployeeBase> で正社員・契約社員をまとめました(継承)。 第 14 章では List<IPrintable> で社員・売上レポートをまとめました(インターフェイス)。

仕組み共通の型共通点
継承(第 13 章)EmployeeBase「同じ親」を持つ
インターフェイス(第 14 章)IPrintable「同じ機能」を持つ

継承は「同じ家系」、インターフェイスは「同じ資格」と考えると分かりやすいです。


14-4 IPayable で支給額を計算する

Section titled “14-4 IPayable で支給額を計算する”

支給額を計算できることを表す IPayable を定義します。

IPayable.cs:

IPayable.cs
namespace Ch14_Interface;
interface IPayable
{
int GetPaymentAmount();
}

RegularEmployee.cs:

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:

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:

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 を継承して GetPaymentAmountoverride しました。 第 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(両方を実装):

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; // OK
IPayable 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 classinterface
主な役割共通の 土台(共通データ + 共通処理)機能の約束 だけ
共通プロパティを持たせるできる(値も持てる)できない(本研修の範囲では)
共通処理を書くできる書かない(本研修では)
クラスの利用数1 クラスにつき 1 つだけ継承可複数実装可
向いている例社員の共通情報(ID・氏名)印刷できる、保存できる、検索できる

共通のデータや処理をまとめたい
→ 抽象クラスが候補
「この機能を持っていることを約束したい」だけ
→ インターフェイスが候補
両方を組み合わせたい
→ 抽象クラスを継承しつつ、インターフェイスも複数実装

初学者の段階では、無理に完璧に使い分ける必要はありません。 現場のコードでは、Service・Repository のような層でインターフェイスがよく使われます(次節)。


14-7 ServiceとRepositoryのイメージ

Section titled “14-7 ServiceとRepositoryのイメージ”

後の章では、データベース(SQLServer)から社員情報を取得します。 そのとき、現場のコードでは次のような名前のクラス・インターフェイスがよく登場します。

名前役割
IEmployeeRepository社員データの取得・保存などの 約束
SqlServerEmployeeRepositorySQLServer から取得する 実装
SampleEmployeeRepositoryテスト用にサンプルデータを返す 実装

「Repository」は データの保管庫 を表す名前で、DB アクセスを担当するクラスによく使われます。


Employee.cs:

Employee.cs
namespace Ch14_Interface;
class Employee
{
public int EmployeeId { get; set; }
public string EmployeeName { get; set; }
}

IEmployeeRepository.cs:

IEmployeeRepository.cs
namespace Ch14_Interface;
interface IEmployeeRepository
{
List<Employee> GetAll();
Employee FindById(int employeeId);
}

FindById は、見つからなかった場合 null を返す可能性があります。 呼び出し側で null チェック を行う必要があります(第 11 章で学んだとおり)。


DB に接続せず、固定データを返す実装を作ります。

SampleEmployeeRepository.cs:

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:

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:山口洋子
検索結果:佐藤昭夫

呼び出し側のコードは 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 のような名前を見たら「外から実装を差し替えられる作りだな」と読めることを目指します。


つまずき原因対応
インターフェイスの意味が分からない中身がなく抽象的に見える「機能の約束」と覚える
interfaceclass の違いが分からない両方とも型として使えるクラスは設計図、インターフェイスは約束だけ
I〇〇I が分からない命名規則を知らないInterface の頭文字
インターフェイスのメソッドに処理を書こうとするクラスメソッドと混同本研修ではインターフェイス側に中身を書かない
実装クラスでメソッドを書き忘れる約束されたメソッドが不足エラーメッセージから不足メソッドを追加
インターフェイス型でクラス固有のメンバーが使えないインターフェイス型では「約束されたもの」だけ必要なら実装クラス型で受ける
抽象クラスとの違いが分からないどちらもポリモーフィズムに使える抽象クラス=共通の土台、インターフェイス=機能の約束
複数インターフェイスの書き方が分からないカンマで区切る感覚がない: I1, I2, I3 のようにカンマ区切り
継承とインターフェイスの順序を間違えるどちらが先か分からない親クラスを最初に、続けてインターフェイス
IEmployeeRepository の意味が見えてこないDI や差し替えの感覚がない「データ取得の約束」と読み、実装は後から差し替え可

  • インターフェイスとは「機能の約束」であることを説明できる
  • interface キーワードでインターフェイスを定義できる
  • I〇〇 の命名習慣を説明できる
  • クラスでインターフェイスを実装できる
  • インターフェイスにメソッドとプロパティを定義できる
  • インターフェイス型の変数に実装クラスのオブジェクトを代入できる
  • インターフェイス型では約束されたメンバーだけ使えることを説明できる
  • List<インターフェイス> で複数のクラスをまとめて扱える
  • 1 つのクラスに複数のインターフェイスを実装できる
  • 継承とインターフェイスを組み合わせられる(順序:親クラスが先)
  • 抽象クラスとインターフェイスの違いを説明できる
  • IEmployeeRepository のような名前を見て、Repository の役割を想像できる

研修の進め方によっては、隣の人またはチーム内で説明確認を行います。

次の内容を、自分の言葉で説明してください。

  1. インターフェイスとは何ですか。
  2. IPrintable はどのような役割を持ちますか。
  3. class Employee : IPrintable は何を意味しますか。
  4. インターフェイス型の変数では、どのメンバーを使えますか。
  5. List<IPrintable> に異なるクラスのオブジェクトを入れられるのはなぜですか。
  6. クラスの継承とインターフェイスの実装は、それぞれいくつまでできますか。
  7. 抽象クラスとインターフェイスの使い分けは、どのように考えますか。
  8. IEmployeeRepository を使うと、何が嬉しいですか。

説明するときは、完全な答えでなくても構いません。 自分の言葉で説明しようとすることが大切です。


この章の演習課題に取り組みます。複数のインターフェイス・クラスファイルを作る、歯ごたえのある演習です。

段階目安時間内容
① 準備10 分ペア確認 + 課題確認(評価対象外)
② ソロ作業35 分タイマーで計測。タイマー時点の commit が唯一の評価対象(別ファイルでインターフェイス・クラスを複数作るため長めに設定)
③ チーム時間講師指定の発表開始時刻までレビュー + 発表者選出 + 実装続行(任意)。発表開始時刻は厳守

提出ルール(タイマー方式)

タイマー時点の commit が唯一の評価対象です。タイマー後の書き足しは評価されません。 コミットメッセージ形式:Chapter14 タイマー提出: <どこまで完成> / <詰まったポイント>(なければ「特になし」) 例:Chapter14 タイマー提出: 必須14-1・14-2完成、発展14-3途中 / List<IExportable> でまとめる型でつまずいた

提出方法:Git が使えないときはサーバへコピー

講師の指示があったときは、push の代わりに Kadai14 フォルダを提出先サーバへコピーし、コピー先に 提出メモ.txt(「どこまで完成」「詰まったポイント」を記載)を作成してください。

タイマー後のチーム時間の使い方

レビュー・発表者選出・実装続行(任意)を自由配分してください。発表開始時刻は厳守です。


この章では、課題ごとにコンソールアプリのプロジェクトを作成 します。 各課題では、指定されたプロジェクト名を使ってください。

課題必須/発展プロジェクト名作成する主なファイル
課題 14-1必須Kd14_01_ExportableProgram.csIExportable.csOrder.csCustomer.cs
課題 14-2必須Kd14_02_TaxableProgram.csIExportable.csITaxable.csOrder.cs
課題 14-3発展Kd14_03_ProductRepositoryProgram.csProduct.csIProductRepository.csSampleProductRepository.cs
課題 14-4発展Kd14_04_RepositorySwapProgram.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>Order 2 件と Customer 1 件を入れる
    • Order:(1, “ノート”, 3) / (2, “マグカップ”, 2)
    • Customer:(101, “田中商店”, “東京都”)
  • foreach で各要素の ToCsvLine() を呼び出して 1 行ずつ表示

実行結果例:

1,ノート,3
2,マグカップ,2
101,田中商店,東京都

条件:

  • List<IExportable> を使う(OrderCustomer という 無関係なクラス を同じリストに入れる)
  • カンマ区切りの文字列は string.Join(",", ...) または文字列補間 $"{...},{...}" で作る(引用符での囲みなどは不要。第 22 章で扱う本格的な CSV とは別の、簡単な連結でよい)
  • foreach の中で型判定の if は書かない(実体ごとの ToCsvLine() が動く)

課題 14-2 複数インターフェイス + ITaxable で税込合計

Section titled “課題 14-2 複数インターフェイス + ITaxable で税込合計”

注文に「税込金額を計算できる」機能(ITaxable)を追加します。OrderIExportableITaxable両方 を実装し、税込金額の合計を求めてください。

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 つの実装(SampleProductRepositoryEmptyProductRepository)を渡すと、同じメソッドが渡された実装に応じて違う結果を出す ことを確認できます。これが「実装を差し替えられる」インターフェイスの強みです。

手順

  1. 次の 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}");
}
}
  1. 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());
}
  1. 最後の行のように、後の DB 接続編で SqlServerProductRepository に差し替えるイメージ をコメントで 1 行残す(SqlServerProductRepository 自体は実装しなくてよい)。

実行結果例:

[SampleProductRepository]
1:ノート(文房具)180円
2:ボールペン(文房具)120円
3:マグカップ(食器)800円
4:タンブラー(食器)1700円
[EmptyProductRepository]
商品データはありません。

条件:

  • PrintAllProducts の引数の型は IProductRepository(具体クラスではない)
  • 同じメソッドが、異なる実装クラスで動作することを確認
  • IProductRepository repository = new SqlServerProductRepository(); のような差し替えコメントを残す(SqlServerProductRepository は実装しなくてよい)

  • プログラムを Visual Studio から実行できる
  • interface でインターフェイスを定義できている
  • インターフェイス名の先頭に I を付けている
  • クラスでインターフェイスを実装できている
  • インターフェイス型の変数を使えている
  • List<インターフェイス> で異なるクラスをまとめて扱えている
  • 複数のインターフェイス(IExportable+ITaxable)を実装できている
  • IProductRepository のような Repository インターフェイスを書けている(発展)
  • 実装クラスを差し替えるイメージを理解している(発展)
  • クラス・インターフェイスを別ファイルに分けて書けている
  • インデントが整っている
  • タイマー時点で commit 済み(または 提出メモ.txt を書いた)

タイマーが鳴ったら、その時点の状態を Git に提出します。

Terminal window
git status
git add .
git commit -m "Chapter14 タイマー提出: 必須14-1・14-2完成、発展14-3途中 / 特になし"
git push origin main

Git が使えないときは、上記コミットの代わりに Kadai14 フォルダを提出先サーバへコピーし、コピー先に 提出メモ.txt を作成してください(演習課題の「提出方法:Git が使えないときはサーバへコピー」参照)。

Git の詳しい操作は、付録 C「Git のインストールと提出ルール」 を参照してください。


この章では、インターフェイスを学習しました。

  • インターフェイスは、クラスが持つべき 機能の約束 を表す
  • interface キーワードで定義し、慣習として名前の先頭に I を付ける
  • インターフェイス自体には処理を書かず、約束だけを書く
  • クラスは : I〇〇 の形でインターフェイスを実装する
  • 実装クラスは、約束されたメンバーをすべて持つ必要がある
  • インターフェイス型の変数では、約束されたメンバーだけが使える
  • List<インターフェイス> で異なるクラスをまとめて扱える(ポリモーフィズム)
  • 1 つのクラスは 複数のインターフェイスを実装 できる
  • 継承(1 つだけ)とインターフェイス(複数可)を組み合わせられる
  • 抽象クラスは「共通の土台」、インターフェイスは「機能の約束」と考えると分かりやすい
  • IEmployeeRepository のような Repository インターフェイスを使うと、実装(サンプル / SQLServer / テスト用)を差し替えやすくなる
  • 後の DB 接続編・Web アプリ編で、この考え方が活きてくる

次章では、例外処理 を学習します。

プログラムでは、入力ミス、ファイルが存在しない、DB 接続失敗など、さまざまなエラーが発生します。 例外処理を学ぶことで、エラーが発生したときにプログラムを安全に止めたり、回復したりする方法を身に付けます。 特に、DB 接続を扱う第 17 章以降では、例外処理の考え方が必須になります。