前回はクラスの基本について学びました。
今回は、クラスの「応用的な使い方」について学びます。
クラスの基本(プロパティやメソッド、コンストラクタなど)を理解した方に向けて、より実践的で柔軟なクラス設計に役立つ概念を紹介していきます。
静的メソッドとは?
通常、クラスの中に定義されたメソッドを使うには、まずそのクラスからオブジェクト(インスタンス)を作って呼び出す必要があります。
var article = new Article();
article.Print(); // インスタンスから呼び出す
このように、インスタンスを経由して呼び出すメソッドのことを「インスタンスメソッド」と呼びます。
これは、クラスが持つデータ(プロパティ)にアクセスする処理に適しています。
一方で、インスタンスを作らなくても使えるメソッドもあり、それを「静的メソッド(staticメソッド)」といいます
静的メソッドの例
以下は、2倍の値を返す静的メソッドです。
public static class MathUtil
{
public static int Double(int value)
{
return value * 2;
}
}
静的メソッドは、アクセス修飾子の後ろに static
修飾子を付与します。
メソッドを呼び出す際は new
したインスタンスからではなく、クラス名.静的メソッド名
という形で行います。
var result = MathUtil.Double(5); // 出力: 10
実はこれまでたびたび登場してきた Console.WriteLine()
も、Console
クラスに定義された静的メソッドです。
インスタンスメソッドと静的メソッドの使い分け
どちらを使うかは「特定のデータを使うかどうか」で判断しましょう。
- インスタンスメソッド は、そのオブジェクトに紐づくデータを使った処理に使う
例:記事のタイトルを表示する、ユーザーの状態を変更するなど - 静的メソッド は、特定のデータに依存しない共通処理に使う
例:計算、文字列変換、ログ出力など
実際に使用するアプリケーションでは、
以下では、クラス内のデータ(プロパティ)を使って処理を行うので、インスタンスメソッドを使う必要があります。
public class Article
{
public string Title { get; set; }
// インスタンスメソッド
public void Print()
{
Console.WriteLine(Title);
}
}
一方で、クラス内のデータを使わないメソッドは静的メソッドとして定義するのが適しています。
public static class MathUtil
{
// 静的メソッド
public static int Double(int value)
{
return value * 2;
}
}
このようなメソッドは、データ(TitleやAuthorなど)を持たず、与えられた値だけで処理が完結します。
上の例のMathUtilクラスのように、クラス自体にstaticが付与されているものを静的クラスといいます。
静的クラスはインスタンスを生成できず、中に定義するメンバーもすべて static
にする必要があります。
「インスタンスを生成する必要が無く、共通の機能だけを提供したいクラス」に使うので、実際のアプリケーションで使う機会はそこまで多くありません。
継承とは?
継承とは、あるクラスの機能を引き継いで、新しいクラスを作る仕組みです。
たとえば、「記事」という基本的なクラスがあって、それに「カテゴリ」を加えた「ニュース記事」クラスを作りたいとします。
このとき、Article
クラスを元にして NewsArticle
クラスを作ることができます。
継承の基本構文
class 子クラス名 : 親クラス名
{
// 追加のプロパティやメソッド
}
このように、コロン(:
)を使って「〇〇は△△を継承する」と書きます。
実際の例
以下は記事(親クラス)とニュース記事(子クラス)の例です。
まず、基本となる Article
クラスを定義します。
public class Article
{
public string Title { get; set; }
public void Print()
{
Console.WriteLine(Title);
}
}
次に、これを継承して NewsArticle
クラスを作ります。
public class NewsArticle : Article
{
public string Category { get; set; }
}
このように書くと、NewsArticle
は Article
のプロパティやメソッドをそのまま使えるようになります。
var news = new NewsArticle
{
Title = "C#入門 オブジェクト指向とは?",
Category = "技術"
};
news.Print(); // ArticleクラスのPrintメソッドを呼び出せる
Console.WriteLine(news.Category); // 自分のプロパティも使える
このように、NewsArticle
は Title
や Print()
を自分で定義しなくても、親である Article
から引き継いで使えます。
継承のメリットと注意点
継承が特に役立つのは、複数のクラスが「共通の性質」を持っている場合です。
例えば以下のようなケースです。
- ブログ記事、ニュース記事、マニュアル記事などを扱うシステムで、それぞれに「タイトル」「本文」「投稿日」などの共通要素がある
- 管理者ユーザー、一般ユーザー、ゲストユーザーなどのクラスで「ログイン機能」や「名前」の扱いが共通
このようなとき、共通部分を親クラスにまとめることで、以下のようなメリットがあります。
- コードの重複を減らせる
- 共通の変更が1か所で済む(保守しやすくなる)
このように継承は「似たクラスをまとめて整理したいとき」に便利な仕組みですが、実務では使いすぎるとコードが複雑になりやすいため、必要なときだけ使うようにしましょう。
オーバーライドとポリモーフィズム
オーバーライドとは?
オーバーライドは、親クラスで定義されたメソッドの動きを、子クラスで書き換える(上書きする)機能です。
「基本の動きはあるけど、子クラスでは少し違う動きをさせたい」というときに使います。
例:Printメソッドを上書きする
前回の例では、Article
クラスに Print()
メソッドがありました。
このメソッドを NewsArticle
クラスでオーバーライドして、カテゴリも一緒に表示したいとします。
まず、親クラスのメソッドに virtual
キーワードを付与しておきます。(オーバーライドできるようにするための決まり)
public class Article
{
public string Title { get; set; }
public virtual void Print()
{
Console.WriteLine(Title);
}
}
次に、子クラスのオーバーライドしたいメソッドに override
キーワードを使って、処理内容を上書きします。
public class NewsArticle : Article
{
public string Category { get; set; }
public override void Print()
{
Console.WriteLine($"[{Category}] {Title}");
}
}
このようにすると、実行時に親クラスの Print()
ではなく、子クラスで上書きした Print()
が呼ばれます。
var news = new NewsArticle
{
Title = "C#入門 オブジェクト指向の基本",
Category = "技術"
};
news.Print(); // 出力: [技術] C#入門 オブジェクト指向の基本
ポリモーフィズムとは?
ポリモーフィズム(多態性)とは、「同じメソッド名でも、中身の動きがクラスによって変わる」という考え方です。
たとえば、親クラス型の変数で子クラスのインスタンスを扱っても、Print()
を呼べば実際の中身に応じた動きをしてくれます。
Article article = new NewsArticle
{
Title = "速報",
Category = "ニュース"
};
article.Print(); // 出力: [ニュース] 速報
ここでは、Article
型の変数 article
を使っていますが、実際の中身は NewsArticle
です。
このとき、親クラスに定義された Print()
ではなく、子クラスでオーバーライドされた Print()
が実行されるのがポリモーフィズムです。
ポリモーフィズムのメリット
ポリモーフィズムを使うと、親クラス型の変数やコレクションで、さまざまな種類のオブジェクトをまとめて扱うことができます。
例えば以下のように、Article
を継承した NewsArticle
や BlogArticle
をArticle
型のコレクションとしてまとめて処理できます。
List<Article> articles = new List<Article>
{
new NewsArticle { Title = "速報", Category = "ニュース" },
new BlogArticle { Title = "開発日誌", Tags = new[] { "C#", "日記" } }
};
foreach (var article in articles)
{
article.Print(); // 実行時にそれぞれのクラスに応じたPrintが呼ばれる
}
このように、呼び出し側のコードは「Article型」として統一されていても、実行される中身はクラスごとに変わるため、
- 処理を共通化できる
- 拡張しても既存コードを変えずに済む
- クラスごとの違いを意識せずに使える
といったメリットがあります。
インターフェースとは?
インターフェース(interface)は、「このクラスはこういう機能を持っています」とあらかじめ定義する仕組みです。
どんなメソッドやプロパティがあるか(名前や引数)のみを決めて、具体的な中身(処理)はクラス側に任せます。
クラスはこのインターフェースを「実装する」(継承する)ことで、その機能を実際に使えるようになります。
インターフェースの定義
インターフェースは interface
キーワードを使って定義します。
以下は「印刷できるもの」を表すインターフェースの例です。
public interface IPrintable
{
void Print();
}
IPrintable
という名前には「何かを印刷できるもの」という意味が込められています。
C#では、インターフェース名に「I+動詞+able
」(~できるもの)や「I+名詞+able
」(~する機能を持つもの)といった名前を付けることが多いです。
そのインターフェースが「何ができるのか」をわかりやすく示すためです。
クラスでインターフェースを実装する
このインターフェースを使って、Article
クラスに Print()
機能を持たせてみましょう。
// IPrintableを実装したクラス
public class Article : IPrintable
{
public string Title { get; set; }
// Printメソッドを実装する必要がある
public void Print()
{
Console.WriteLine(Title);
}
}
このように、インターフェースに書かれていた Print()
メソッドを必ず実装する必要があります。
インターフェースを使って呼び出す
インターフェースは「どんな型か分からなくても、PrintできるならOK」といった形で使えるのが強みです。
IPrintable item = new Article { Title = "インターフェース入門" };
item.Print(); // 出力: インターフェース入門
このように、共通の機能を持つクラスを“ひとまとめ”に扱うことができます。
インターフェースは複数実装できる
クラスの継承は1つまでしかできませんが、インターフェースは複数同時に実装できます。
public interface ISavable
{
void Save();
}
public class Document : IPrintable, ISavable
{
public string Title { get; set; }
public void Print()
{
Console.WriteLine(Title);
}
public void Save()
{
Console.WriteLine("保存しました");
}
}
補足:デフォルト実装について(C# 8以降)
C# 8以降では、インターフェースにも「デフォルト実装」を書くことができるようになりました。
これは、インターフェースを実装するクラス側で処理を省略できるようにするための仕組みです。
public interface IPrintable
{
void Print() => Console.WriteLine("印刷しています");
}
このように書くと、クラス側で Print()
を定義しなくても、インターフェース内の処理がそのまま使われます。
抽象クラスとは?
抽象クラス(abstract class)は、「継承されることを前提としたクラス」です。
自分自身ではインスタンス化できず、子クラスで完成させることを目的としたクラスになります。
抽象クラスの書き方
抽象クラスは abstract
キーワードを使って定義します。
public abstract class Document
{
public string Title { get; set; }
// 抽象メソッド(中身は書かず、子クラスに実装させる)
public abstract void Print();
}
子クラスに継承させたいメソッドには abstract
キーワードを付けて抽象メソッドにします。
インターフェースと同じで、中身は子クラスで実装する必要があります。
子クラスの実装
抽象クラスを継承した Report
クラスの例です。
public class Report : Document
{
public override void Print()
{
Console.WriteLine($"レポート: {Title}");
}
}
Print()
を実装しないと、コンパイルエラーになります。
※「このメソッドは絶対に必要」というルールを作ることができるのが抽象クラスの強みです。
インスタンスは作れない
インターフェースと同様に、抽象クラスは new
してインスタンスを生成することはできません。
var doc = new Document(); // エラー!
必ず Report
のような 具象クラス(普通のクラス)で継承してから使う必要があります。
抽象クラスとインターフェースの違い
抽象クラスとインターフェースは、どちらも「共通のルールや機能の枠組みを決める」ために使われますが、設計の目的や使える機能に違いがあります。
項目 | 抽象クラス | インターフェース |
---|---|---|
継承方法 | 単一継承のみ | 多重継承OK |
メソッドの中身(処理) | ✅ 書ける(共通処理を含められる) | ❌ 基本書けない(C# 8以降は一部OK) |
フィールド | ✅ 持てる | ✅ 持てるが、フィールドは不可(C# 8以降でプロパティOK) |
コンストラクタ | ✅ 持てる(初期化処理が可能) | ❌ 持てない |
用途のイメージ | 「共通の土台」「機能の一部を実装済み」 | 「~できるもの」「契約や約束」 |
共通の処理を再利用したいときは抽象クラス、性質や役割を柔軟に付けたいときはインターフェースを使うのが基本的な考え方です。
実際の開発では、インターフェースを使うことが圧倒的に多いです。
詳しい理由はここでは省略しますが、インターフェースの方が柔軟で、DI(依存性注入)やテストといった場面でも扱いやすいためです。
まとめ
この記事では、C#のクラス設計をより柔軟にするための応用的な仕組みについて学びました。
static
を使えば、インスタンスを作らずに共通の処理を実行できる- 継承 によって、共通のプロパティや処理をまとめて再利用できる
- オーバーライド を使えば、親クラスの処理を子クラスで自由に上書きできる
- ポリモーフィズム によって、同じ名前のメソッドでクラスごとの違う動作を実現できる
- インターフェース で「この機能を持つ」という約束を定義でき、複数同時に使える
- 抽象クラス は継承前提の「ひな型」として使え、一部の処理を子クラスに任せられる
これらを使い分けることで、共通性は保ちつつ、柔軟で拡張しやすい設計ができるようになります。
次回の「C#入門⑨」では、record
型を使った「イミュータブル(変更できない)データの扱い方」について解説します。