C#のオーバーライドとは?virtual・overrideの使い方と継承の基本をサンプルで解説

はじめに

C#で継承を学び始めると、よく出てくるのが「オーバーライド」という考え方です。

オーバーライドは、親クラスで定義されたメソッドやプロパティの処理を、子クラス側で上書きして独自の動作に変える仕組みです。C#では主にvirtualoverrideを使って実装します。

ただし、初心者にとっては「継承」「virtual」「override」「overload」「abstract」「interface」など似た用語が多く、どこで何を使えばよいのか分かりにくいポイントでもあります。

この記事では、C#のオーバーライドについて、基本概念からサンプルコード、よくあるエラー、実務での使いどころまで順番に解説します。

1. C#のオーバーライドとは何か?検索意図と全体像

1-1. 「C# オーバーライド」で検索するユーザーの目的

「C# オーバーライド」で検索する人の多くは、次のような疑問を持っています。

「親クラスのメソッドを子クラスで書き換えるにはどうすればよいのか」
virtualoverrideの違いが分からない」
「overrideを書いたらエラーになった」
「overloadやabstractとの違いを整理したい」
「ポリモーフィズムとどう関係しているのか知りたい」

C#のオーバーライドは、オブジェクト指向プログラミングの重要な機能です。単にメソッドを上書きするだけでなく、「共通の型として扱いながら、実際の処理は子クラスごとに変える」という設計を実現できます。

1-2. 初心者がつまずきやすいポイント

C#のオーバーライドで初心者がつまずきやすいのは、次のような点です。

親クラスのメソッドにvirtualが付いていないとoverrideできません。また、子クラス側でメソッド名や引数、戻り値の型が一致していない場合もエラーになります。

さらに、overrideoverloadは名前が似ていますが意味はまったく違います。overrideは継承元の処理を上書きすること、overloadは同じ名前で引数の異なるメソッドを複数定義することです。

また、abstractinterfaceも「子クラスで実装する」という点では似ていますが、設計上の目的が異なります。

1-3. この記事で解決できること

この記事を読むことで、次の内容を理解できます。

C#のオーバーライドとは何か、virtualoverrideをどう使うのか、継承やポリモーフィズムとどう関係するのか、そして実務ではどのような場面で使うべきなのかを、サンプルコードを通して整理できます。

まずは、オーバーライドの前提となる「継承」から確認していきましょう。

2. C#の継承とオーバーライドの基本概念

2-1. クラス継承とは何か

継承とは、あるクラスの機能を別のクラスが引き継ぐ仕組みです。

C#では、親となるクラスを「基底クラス」または「親クラス」、継承する側のクラスを「派生クラス」または「子クラス」と呼びます。

C#
class Animal
{
public void Eat()
{
Console.WriteLine("食べます");
}
}

class Dog : Animal
{
}

この例では、DogクラスがAnimalクラスを継承しています。DogクラスにはEatメソッドを書いていませんが、Animalクラスの機能を引き継いでいるため、次のように呼び出せます。

C#
Dog dog = new Dog();
dog.Eat();

継承を使うと、共通する処理を親クラスにまとめ、子クラスでは差分だけを実装できます。

ただし、単に継承するだけでは、親クラスのメソッドを自由に上書きできるわけではありません。そこで必要になるのがvirtualoverrideです。

2-2. virtualメソッドの役割

virtualは、「このメソッドは子クラスで上書きしてもよい」という意味を持つキーワードです。

親クラス側でvirtualを付けたメソッドは、子クラス側でoverrideを使って処理を変更できます。

C#
class Animal
{
public virtual void Speak()
{
Console.WriteLine("動物が鳴きます");
}
}

このSpeakメソッドはvirtualとして定義されているため、派生クラスでオーバーライドできます。

virtualが付いていない通常のメソッドは、基本的にoverrideできません。C#では、親クラスの設計者が「上書きしてよい」と明示したメンバーだけをオーバーライドできる仕組みになっています。

2-3. overrideの基本構文とルール

overrideは、親クラスのvirtualabstract、またはoverrideされたメンバーを、子クラス側で上書きするときに使います。

基本構文は次の通りです。

C#
class 子クラス : 親クラス
{
public override 戻り値の型 メソッド名(引数)
{
// 子クラス独自の処理
}
}

実際の例を見てみましょう。

C#
class Animal
{
public virtual void Speak()
{
Console.WriteLine("動物が鳴きます");
}
}

class Dog : Animal
{
public override void Speak()
{
Console.WriteLine("ワンワン");
}
}

このコードでは、AnimalクラスのSpeakメソッドを、Dogクラスでオーバーライドしています。

オーバーライドするには、主に次のルールがあります。

親クラスのメソッドにvirtualabstract、またはoverrideが付いている必要があります。子クラス側ではoverrideを付けます。メソッド名、戻り値、引数の形は基本的に一致させる必要があります。アクセス修飾子も原則として親クラスの定義と合わせます。

3. virtual・overrideの使い方をサンプルコードで理解する

3-1. シンプルなオーバーライド例

まずは最もシンプルなC#のオーバーライド例です。

C#
using System;

class Animal
{
public virtual void Speak()
{
Console.WriteLine("動物が鳴きます");
}
}

class Cat : Animal
{
public override void Speak()
{
Console.WriteLine("ニャー");
}
}

class Program
{
static void Main()
{
Animal animal = new Animal();
animal.Speak();

Cat cat = new Cat();
cat.Speak();
}
}

実行結果は次のようになります。

C#
動物が鳴きます
ニャー

AnimalクラスのSpeakは標準の処理です。一方、CatクラスではSpeakをオーバーライドして、猫らしい鳴き声に変更しています。

このように、親クラスに共通の処理を用意しつつ、子クラスごとに振る舞いを変えられるのがオーバーライドの基本です。

3-2. baseキーワードの使い方

オーバーライドしたメソッドの中で、親クラスの処理も呼び出したい場合があります。そのときに使うのがbaseキーワードです。

C#
using System;

class Animal
{
public virtual void Speak()
{
Console.WriteLine("動物が鳴きます");
}
}

class Dog : Animal
{
public override void Speak()
{
base.Speak();
Console.WriteLine("ワンワン");
}
}

class Program
{
static void Main()
{
Dog dog = new Dog();
dog.Speak();
}
}

実行結果は次のようになります。

C#
動物が鳴きます
ワンワン

base.Speak()によって、親クラスであるAnimalSpeakメソッドを呼び出しています。その後、Dogクラス独自の処理を追加しています。

baseは、親クラスの処理を完全に置き換えるのではなく、「親の処理を活かしながら一部だけ拡張したい」場合に便利です。

たとえば、ログ出力、初期化処理、共通チェック処理などを親クラスに置き、子クラスで追加処理を行うような設計でよく使われます。

3-3. 実行時の動作(ポリモーフィズムの確認)

オーバーライドの重要な特徴は、変数の型が親クラスでも、実際に入っているオブジェクトの型に応じて子クラスのメソッドが呼ばれることです。

C#
using System;

class Animal
{
public virtual void Speak()
{
Console.WriteLine("動物が鳴きます");
}
}

class Dog : Animal
{
public override void Speak()
{
Console.WriteLine("ワンワン");
}
}

class Cat : Animal
{
public override void Speak()
{
Console.WriteLine("ニャー");
}
}

class Program
{
static void Main()
{
Animal[] animals =
{
new Dog(),
new Cat(),
new Animal()
};

foreach (Animal animal in animals)
{
animal.Speak();
}
}
}

実行結果は次のようになります。

C#
ワンワン
ニャー
動物が鳴きます

配列の型はAnimal[]ですが、実際にはDogCatのインスタンスが入っています。そしてSpeakを呼び出すと、それぞれのクラスでオーバーライドされた処理が実行されます。

これがポリモーフィズム、つまり多態性です。

4. overrideと関連用語の違いを整理する

4-1. overrideとoverloadの違い

overrideoverloadは名前が似ていますが、意味は異なります。

overrideは、親クラスで定義されたメソッドを子クラスで上書きすることです。

C#
class Animal
{
public virtual void Speak()
{
Console.WriteLine("動物が鳴きます");
}
}

class Dog : Animal
{
public override void Speak()
{
Console.WriteLine("ワンワン");
}
}

一方、overloadは、同じクラス内で同じ名前のメソッドを、引数の数や型を変えて複数定義することです。

C#
class Calculator
{
public int Add(int a, int b)
{
return a + b;
}

public int Add(int a, int b, int c)
{
return a + b + c;
}

public double Add(double a, double b)
{
return a + b;
}
}

overrideは継承が前提です。overloadは継承がなくても使えます。

つまり、overrideは「親の処理を子で上書き」、overloadは「同じ名前で別パターンのメソッドを用意」と覚えると分かりやすいです。

4-2. virtualとabstractの違い

virtualabstractも混同しやすいキーワードです。

virtualメソッドは、親クラス側に処理の中身を持つことができます。子クラスは必要に応じてオーバーライドできます。

C#
class Animal
{
public virtual void Speak()
{
Console.WriteLine("動物が鳴きます");
}
}

一方、abstractメソッドは処理の中身を持ちません。抽象メソッドを含むクラスはabstract classとして定義し、継承した子クラス側で必ず実装する必要があります。

C#
abstract class Animal
{
public abstract void Speak();
}

class Dog : Animal
{
public override void Speak()
{
Console.WriteLine("ワンワン");
}
}

virtualは「必要なら上書きしてよい」、abstractは「必ず子クラスで実装しなければならない」という違いがあります。

共通の標準動作がある場合はvirtual、親クラスだけでは処理を決められない場合はabstractを使うとよいでしょう。

4-3. sealed overrideの意味と使いどころ

sealed overrideは、「このクラスではオーバーライドするが、これ以上派生クラスではオーバーライドさせない」という意味です。

C#
class Animal
{
public virtual void Speak()
{
Console.WriteLine("動物が鳴きます");
}
}

class Dog : Animal
{
public sealed override void Speak()
{
Console.WriteLine("ワンワン");
}
}

class Poodle : Dog
{
// Dog.Speak() は sealed override されているため、
// ここで override することはできない
}

sealed overrideを使うと、継承階層の途中でオーバーライドを打ち止めにできます。

実務では、これ以上変更されるとクラスの整合性が崩れる処理や、セキュリティ上・設計上変更させたくない処理に使われることがあります。

ただし、むやみにsealed overrideを使うと拡張性が下がるため、「本当にこれ以上上書きさせたくないのか」を考えて使うことが重要です。

5. ポリモーフィズム(多態性)とオーバーライドの関係

5-1. ポリモーフィズムの基本概念

ポリモーフィズムとは、同じ呼び出し方で、実際のオブジェクトに応じて異なる動作をさせる仕組みです。

C#では、継承とオーバーライドを使うことでポリモーフィズムを実現できます。

C#
class Payment
{
public virtual void Pay()
{
Console.WriteLine("支払いを行います");
}
}

class CreditCardPayment : Payment
{
public override void Pay()
{
Console.WriteLine("クレジットカードで支払います");
}
}

class CashPayment : Payment
{
public override void Pay()
{
Console.WriteLine("現金で支払います");
}
}

呼び出し側は、具体的な支払い方法を細かく意識せず、共通のPayment型として扱えます。

C#
Payment payment = new CreditCardPayment();
payment.Pay();

実行されるのはCreditCardPaymentPayメソッドです。

この仕組みによって、コードの分岐を減らし、拡張しやすい設計にできます。

5-2. 実務での活用シーン

オーバーライドは、実務でもさまざまな場面で使われます。

たとえば、ゲーム開発ではCharacterクラスを親にして、PlayerEnemyAttackメソッドをオーバーライドできます。

C#
class Character
{
public virtual void Attack()
{
Console.WriteLine("攻撃します");
}
}

class Player : Character
{
public override void Attack()
{
Console.WriteLine("プレイヤーが剣で攻撃します");
}
}

class Enemy : Character
{
public override void Attack()
{
Console.WriteLine("敵が体当たりします");
}
}

業務システムでは、帳票出力、通知処理、決済処理、バリデーション処理などで使われます。

たとえば、通知処理であれば、親クラスにSendメソッドを定義し、メール通知、SMS通知、チャット通知などで処理を変える設計ができます。

C#
class Notification
{
public virtual void Send(string message)
{
Console.WriteLine($"通知: {message}");
}
}

class EmailNotification : Notification
{
public override void Send(string message)
{
Console.WriteLine($"メール送信: {message}");
}
}

class ChatNotification : Notification
{
public override void Send(string message)
{
Console.WriteLine($"チャット送信: {message}");
}
}

呼び出し側はNotification型として扱えるため、新しい通知方法を追加しても既存コードへの影響を小さくできます。

5-3. インターフェースとの違いと使い分け

オーバーライドとインターフェースは、どちらもポリモーフィズムを実現するために使われますが、目的が異なります。

継承とオーバーライドは、「共通の実装を持つ親クラスがあり、それを子クラスで拡張・変更したい場合」に向いています。

一方、インターフェースは、「どのような機能を持つべきか」という契約を定義したい場合に向いています。

C#
interface INotification
{
void Send(string message);
}

class EmailNotification : INotification
{
public void Send(string message)
{
Console.WriteLine($"メール送信: {message}");
}
}

インターフェースは、クラス継承とは違い、複数実装できます。そのため、共通の親クラスを持たないクラスにも同じ機能を持たせたい場合に便利です。

使い分けの目安としては、共通処理を親クラスに持たせたいなら継承とオーバーライド、機能のルールだけを定義したいならインターフェースを選ぶとよいでしょう。

6. C#オーバーライドでよくあるエラーと注意点

6-1. overrideできない原因(virtual不足など)

C#でオーバーライドできない代表的な原因は、親クラスのメソッドにvirtualが付いていないことです。

C#
class Animal
{
public void Speak()
{
Console.WriteLine("動物が鳴きます");
}
}

class Dog : Animal
{
public override void Speak()
{
Console.WriteLine("ワンワン");
}
}

このコードはエラーになります。Animal.Speakvirtualが付いていないためです。

正しくは次のようにします。

C#
class Animal
{
public virtual void Speak()
{
Console.WriteLine("動物が鳴きます");
}
}

class Dog : Animal
{
public override void Speak()
{
Console.WriteLine("ワンワン");
}
}

また、staticメソッドやprivateメソッドは通常のオーバーライド対象にはできません。オーバーライドは、インスタンスメンバーに対して使う仕組みです。

フィールドもオーバーライドできません。オーバーライドできる主な対象は、メソッド、プロパティ、インデクサー、イベントなどです。

6-2. アクセス修飾子によるエラー

オーバーライドでは、アクセス修飾子にも注意が必要です。

親クラスでpublic virtualとして定義したメソッドを、子クラスでprotected overrideに変えることはできません。

C#
class Animal
{
public virtual void Speak()
{
Console.WriteLine("動物が鳴きます");
}
}

class Dog : Animal
{
protected override void Speak()
{
Console.WriteLine("ワンワン");
}
}

このコードはエラーになります。親クラスのSpeakpublicなので、子クラス側もpublic overrideにする必要があります。

C#
class Dog : Animal
{
public override void Speak()
{
Console.WriteLine("ワンワン");
}
}

アクセス修飾子を変えてしまうと、親クラスとして扱ったときの呼び出し可能範囲が変わってしまいます。そのため、C#ではアクセスレベルの不一致を許可していません。

6-3. 意図しない挙動を防ぐポイント

オーバーライドと似た動作に、newキーワードによるメソッドの隠蔽があります。

C#
class Animal
{
public void Speak()
{
Console.WriteLine("動物が鳴きます");
}
}

class Dog : Animal
{
public new void Speak()
{
Console.WriteLine("ワンワン");
}
}

この場合、DogクラスにはSpeakがありますが、これはオーバーライドではありません。親クラスのメソッドを隠しているだけです。

C#
Dog dog = new Dog();
dog.Speak(); // ワンワン

Animal animal = new Dog();
animal.Speak(); // 動物が鳴きます

Animal型の変数にDogのインスタンスを入れても、呼ばれるのは親クラスのSpeakです。

一方、virtualoverrideを使った場合は、実際のインスタンス型に応じて子クラスのメソッドが呼ばれます。

意図しない挙動を防ぐには、「ポリモーフィズムを使いたいならvirtualoverride」「単に同名メソッドで隠したいだけならnew」という違いを理解しておく必要があります。ただし、実務ではnewによる隠蔽は混乱を招きやすいため、明確な理由がない限り避けるのが無難です。

7. 実務で役立つ設計の考え方とベストプラクティス

7-1. オーバーライドを使うべき設計パターン

オーバーライドは、共通の流れは同じだが、一部の処理だけを子クラスごとに変えたい場合に向いています。

代表的なのがテンプレートメソッドパターンです。

C#
abstract class Report
{
public void Create()
{
LoadData();
FormatData();
Print();
}

protected virtual void LoadData()
{
Console.WriteLine("データを読み込みます");
}

protected abstract void FormatData();

protected virtual void Print()
{
Console.WriteLine("印刷します");
}
}

class SalesReport : Report
{
protected override void FormatData()
{
Console.WriteLine("売上レポート用に整形します");
}
}

この例では、Createメソッドで全体の処理の流れを決めています。子クラスはFormatDataだけを実装すればよく、処理の順番は親クラス側で管理できます。

このように、オーバーライドは「共通の枠組みを親クラスで提供し、詳細な処理を子クラスで差し替える」設計に向いています。

7-2. 拡張性を意識したクラス設計

オーバーライドを使うときは、何でもvirtualにすればよいわけではありません。

virtualを付けるということは、「このメソッドは派生クラスで変更される可能性がある」と宣言することです。そのため、将来変更されても問題ない設計になっているかを考える必要があります。

たとえば、クラス内部の重要な状態を変更するメソッドを無制限にオーバーライド可能にすると、派生クラス側の実装によって不整合が起きる可能性があります。

安全な設計にするには、次の点を意識するとよいです。

まず、オーバーライドしてほしいメソッドだけにvirtualを付けます。次に、共通処理として必ず実行したい部分は親クラス側に残し、差し替えたい部分だけをprotected virtualprotected abstractにします。

また、外部から呼ばれる公開メソッドと、子クラスが拡張するためのメソッドを分けると、設計が安定しやすくなります。

C#
abstract class Processor
{
public void Execute()
{
Console.WriteLine("前処理");
ProcessCore();
Console.WriteLine("後処理");
}

protected abstract void ProcessCore();
}

class CsvProcessor : Processor
{
protected override void ProcessCore()
{
Console.WriteLine("CSVを処理します");
}
}

この設計では、外部からはExecuteだけを呼び出します。子クラスはProcessCoreを実装します。これにより、前処理と後処理は必ず実行され、拡張ポイントも明確になります。

オーバーライドは強力な機能ですが、使いすぎると継承関係が複雑になり、コードの見通しが悪くなることもあります。単純な機能追加であれば、インターフェースや委譲を使った方が分かりやすい場合もあります。

まとめ

C#のオーバーライドとは、親クラスで定義されたメソッドやプロパティの処理を、子クラス側で上書きする仕組みです。

親クラス側ではvirtualを付け、子クラス側ではoverrideを付けて実装します。これにより、共通の型として扱いながら、実際のインスタンスに応じて異なる処理を実行できます。

overrideは継承が前提であり、overloadとは異なります。virtualは必要に応じて上書きできるメソッド、abstractは子クラスで必ず実装すべきメソッドです。また、sealed overrideを使うと、それ以降の派生クラスでのオーバーライドを禁止できます。

オーバーライドは、ポリモーフィズムを実現するための重要な仕組みです。実務では、決済処理、通知処理、帳票出力、ゲームのキャラクター処理など、共通の枠組みを持ちながら処理を差し替えたい場面で役立ちます。

一方で、何でもvirtualにすると設計が複雑になるため、拡張してほしい部分を明確にし、必要な場所だけオーバーライド可能にすることが大切です。

C#のオーバーライドを理解すると、継承やポリモーフィズムを活かした柔軟な設計ができるようになります。まずはvirtualoverrideの基本形を押さえ、サンプルコードを書きながら動作を確認してみましょう。