C#のvirtualとは?overrideとの違い・使い方・注意点を初心者向けに徹底解説
はじめに
C#で継承を学び始めると、virtualやoverrideというキーワードがよく出てきます。
しかし、初心者の方にとっては、
「virtualは何のために使うの?」
「overrideとの違いがよく分からない」
「newやabstractとは何が違うの?」
「とりあえず全部virtualにしておけばいいの?」
と迷いやすいポイントでもあります。
結論からいうと、C#のvirtualは「このメソッドやプロパティは、派生クラスで上書きできます」と親クラス側で示すためのキーワードです。
この記事では、C#のvirtualの意味、overrideとの違い、基本的な使い方、注意点、よくあるエラーまで初心者向けに分かりやすく解説します。
1. C#のvirtualとは?初心者向けに結論から解説
1-1. virtualは「派生クラスで上書きできる」ことを示すキーワード
C#のvirtualとは、親クラスに定義したメンバーを、子クラスで上書きできるようにするためのキーワードです。
たとえば、次のような親クラスがあるとします。
C#class Animal
{
public virtual void Speak()
{
Console.WriteLine("動物が鳴きます");
}
}
このSpeakメソッドにはvirtualが付いています。
そのため、Animalを継承したクラスでは、Speakメソッドの処理を上書きできます。
C#class Dog : Animal
{
public override void Speak()
{
Console.WriteLine("ワンワン");
}
}
このように、親クラス側でvirtualを付け、子クラス側でoverrideを使うことで、処理内容を変更できます。
1-2. virtualを使う目的は継承先で処理を差し替えること
virtualを使う主な目的は、継承先のクラスごとに処理を差し替えられるようにすることです。
たとえば、動物を表すAnimalクラスがあり、犬、猫、鳥などのクラスを作る場合、それぞれ鳴き声は異なります。
C#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("ニャー");
}
}
このように、共通のメソッド名を使いながら、クラスごとに異なる処理を実行できるのがvirtualとoverrideの大きな役割です。
1-3. virtualを付けられる対象:メソッド・プロパティ・インデクサー・イベント
C#でvirtualを付けられる主な対象は次のとおりです。
| 対象 | 例 |
|---|---|
| メソッド | public virtual void Run() |
| プロパティ | public virtual string Name { get; set; } |
| インデクサー | public virtual string this[int index] |
| イベント | public virtual event EventHandler Changed |
よく使われるのはメソッドとプロパティです。
たとえば、プロパティにもvirtualを付けられます。
C#class User
{
public virtual string Role
{
get { return "一般ユーザー"; }
}
}
class AdminUser : User
{
public override string Role
{
get { return "管理者"; }
}
}
このように、メソッドだけでなくプロパティの動作も派生クラスで変更できます。
1-4. virtualを使わない通常メソッドとの違い
通常のメソッドにはvirtualが付いていないため、子クラスでoverrideできません。
C#class Animal
{
public void Speak()
{
Console.WriteLine("動物が鳴きます");
}
}
この場合、次のようにoverrideしようとするとエラーになります。
C#class Dog : Animal
{
public override void Speak()
{
Console.WriteLine("ワンワン");
}
}
親クラスのSpeakメソッドにvirtualが付いていないため、子クラスで上書きできないからです。
つまり、通常メソッドは「基本的にそのまま使うメソッド」、virtualメソッドは「必要に応じて子クラスで変更できるメソッド」と考えると分かりやすいです。
2. virtualとoverrideの違い
2-1. virtualは親クラス側に書くキーワード
virtualは、親クラス側で使うキーワードです。
「このメソッドは、子クラスで上書きしてもよい」という意味を持ちます。
C#class Parent
{
public virtual void ShowMessage()
{
Console.WriteLine("親クラスの処理");
}
}
このように、上書きされる側にvirtualを書きます。
2-2. overrideは子クラス側に書くキーワード
overrideは、子クラス側で使うキーワードです。
親クラスでvirtualとして定義されたメンバーを、子クラスで上書きするときに使います。
C#class Child : Parent
{
public override void ShowMessage()
{
Console.WriteLine("子クラスの処理");
}
}
overrideは「親クラスのvirtualメソッドを上書きします」という意味です。
2-3. virtualがないメソッドはoverrideできない
C#では、親クラスのメソッドにvirtual、abstract、またはoverrideが付いていない場合、子クラスでoverrideできません。
たとえば、次のコードはエラーになります。
C#class Parent
{
public void ShowMessage()
{
Console.WriteLine("親クラスの処理");
}
}
class Child : Parent
{
public override void ShowMessage()
{
Console.WriteLine("子クラスの処理");
}
}
ParentクラスのShowMessageメソッドが通常メソッドだからです。
子クラスで上書きしたい場合は、親クラス側にvirtualを付ける必要があります。
C#class Parent
{
public virtual void ShowMessage()
{
Console.WriteLine("親クラスの処理");
}
}
2-4. virtualとoverrideの関係を表で比較
| キーワード | 書く場所 | 役割 | 例 |
|---|---|---|---|
virtual | 親クラス | 子クラスで上書き可能にする | public virtual void Run() |
override | 子クラス | 親クラスのvirtualメンバーを上書きする | public override void Run() |
virtualとoverrideは、親子関係でセットになって使われることが多いです。
ただし、virtualを付けたからといって、必ず子クラスでoverrideしなければならないわけではありません。
2-5. overrideしない場合は親クラスの処理がそのまま使われる
親クラスでvirtualを付けたメソッドでも、子クラスでoverrideしなければ、親クラスの処理がそのまま使われます。
C#class Animal
{
public virtual void Speak()
{
Console.WriteLine("動物が鳴きます");
}
}
class Dog : Animal
{
}
この場合、DogクラスはSpeakメソッドを上書きしていません。
そのため、次のコードを実行すると、親クラスの処理が呼ばれます。
C#Dog dog = new Dog();
dog.Speak();
実行結果は次のようになります。
C#動物が鳴きます
virtualは「上書きできる」という意味であり、「必ず上書きする」という意味ではありません。
3. C#でvirtualを使う基本構文とサンプルコード
3-1. 親クラスにvirtualメソッドを定義する
まず、親クラスにvirtualメソッドを定義します。
C#class Employee
{
public virtual void Work()
{
Console.WriteLine("社員が仕事をします");
}
}
ここでは、EmployeeクラスにWorkメソッドを用意しています。
virtualを付けているため、派生クラスでこのメソッドを上書きできます。
3-2. 子クラスでoverrideして処理を上書きする
次に、Employeeクラスを継承した子クラスを作り、overrideで処理を上書きします。
C#class Engineer : Employee
{
public override void Work()
{
Console.WriteLine("エンジニアがプログラムを書きます");
}
}
Engineerクラスでは、親クラスのWorkメソッドを上書きしています。
3-3. 実行結果から動作の流れを確認する
実際に動かしてみましょう。
C#using System;
class Employee
{
public virtual void Work()
{
Console.WriteLine("社員が仕事をします");
}
}
class Engineer : Employee
{
public override void Work()
{
Console.WriteLine("エンジニアがプログラムを書きます");
}
}
class Program
{
static void Main()
{
Employee employee = new Employee();
employee.Work();
Engineer engineer = new Engineer();
engineer.Work();
}
}
実行結果は次のようになります。
C#社員が仕事をします
エンジニアがプログラムを書きます
Employee型のインスタンスでは親クラスの処理が呼ばれ、Engineer型のインスタンスでは子クラスで上書きした処理が呼ばれます。
3-4. 親クラス型の変数で子クラスのoverrideメソッドが呼ばれる仕組み
virtualとoverrideの重要なポイントは、親クラス型の変数に子クラスのインスタンスを入れた場合でも、実際のインスタンスに応じたメソッドが呼ばれることです。
C#Employee employee = new Engineer();
employee.Work();
実行結果は次のようになります。
C#エンジニアがプログラムを書きます
変数の型はEmployeeですが、実際に入っているインスタンスはEngineerです。
そのため、EngineerクラスでoverrideされたWorkメソッドが呼ばれます。
この仕組みは「ポリモーフィズム」と呼ばれます。
3-5. baseキーワードで親クラスの処理を呼び出す方法
子クラスでoverrideしたメソッドの中から、親クラスの処理を呼び出したい場合はbaseキーワードを使います。
C#class Engineer : Employee
{
public override void Work()
{
base.Work();
Console.WriteLine("エンジニアがプログラムを書きます");
}
}
このコードでは、まずbase.Work()で親クラスのWorkメソッドを呼び出し、そのあとに子クラス独自の処理を実行しています。
実行結果は次のようになります。
C#社員が仕事をします
エンジニアがプログラムを書きます
共通処理は親クラスに置き、追加処理だけ子クラスに書きたい場合に便利です。
4. virtualが必要になる代表的な使い方
4-1. 継承先ごとに異なる処理を実装したい場合
virtualが必要になる代表的な場面は、継承先ごとに異なる処理を実装したい場合です。
たとえば、支払い方法ごとに決済処理を変えたいケースを考えます。
C#class Payment
{
public virtual void Pay()
{
Console.WriteLine("支払いを行います");
}
}
class CreditCardPayment : Payment
{
public override void Pay()
{
Console.WriteLine("クレジットカードで支払います");
}
}
class BankTransferPayment : Payment
{
public override void Pay()
{
Console.WriteLine("銀行振込で支払います");
}
}
このように、共通のPayメソッドを使いながら、支払い方法ごとに異なる処理を実装できます。
4-2. 共通処理は親クラスに置き、一部だけ子クラスで変更したい場合
共通処理は親クラスにまとめ、一部だけ子クラスで変更したい場合にもvirtualは役立ちます。
C#class Report
{
public void Print()
{
Console.WriteLine("レポートを作成します");
PrintContent();
Console.WriteLine("レポートを出力します");
}
protected virtual void PrintContent()
{
Console.WriteLine("標準レポートの内容");
}
}
class SalesReport : Report
{
protected override void PrintContent()
{
Console.WriteLine("売上レポートの内容");
}
}
この例では、Printメソッドの流れは親クラスで固定し、具体的な内容だけを子クラスで変更しています。
このような設計にすると、共通処理の重複を減らしながら、必要な部分だけ柔軟に差し替えられます。
4-3. ポリモーフィズムを使って柔軟な設計にしたい場合
virtualとoverrideを使うと、ポリモーフィズムを活用できます。
C#List<Payment> payments = new List<Payment>
{
new CreditCardPayment(),
new BankTransferPayment()
};
foreach (Payment payment in payments)
{
payment.Pay();
}
実行結果は次のようになります。
C#クレジットカードで支払います
銀行振込で支払います
変数の型はどちらもPaymentですが、実際のインスタンスに応じて異なるPayメソッドが呼ばれます。
これにより、呼び出し側のコードをシンプルに保ちながら、処理の差し替えがしやすくなります。
4-4. ゲーム開発や業務アプリでの実用例
ゲーム開発では、キャラクターごとに攻撃処理を変えたい場合にvirtualを使えます。
C#class Character
{
public virtual void Attack()
{
Console.WriteLine("通常攻撃");
}
}
class Warrior : Character
{
public override void Attack()
{
Console.WriteLine("剣で攻撃");
}
}
class Mage : Character
{
public override void Attack()
{
Console.WriteLine("魔法で攻撃");
}
}
業務アプリでは、ユーザー種別ごとに権限チェックや料金計算を変える場合にも使えます。
C#class User
{
public virtual decimal GetDiscountRate()
{
return 0.0m;
}
}
class PremiumUser : User
{
public override decimal GetDiscountRate()
{
return 0.1m;
}
}
このように、種類ごとに振る舞いが変わる処理はvirtualの代表的な活用場面です。
4-5. テストしやすいコードにするためのvirtual活用例
virtualは、テストしやすいコードを書くために使われることもあります。
たとえば、外部APIを呼び出す処理をvirtualにしておくと、テスト用のクラスで処理を差し替えられます。
C#class ApiClient
{
public virtual string GetData()
{
return "本番APIのデータ";
}
}
class TestApiClient : ApiClient
{
public override string GetData()
{
return "テスト用データ";
}
}
本番環境では実際のAPIを呼び出し、テスト時には固定のデータを返すようにできます。
ただし、テストのためだけに何でもvirtualにするのではなく、インターフェイスや依存性注入を使った設計も検討することが大切です。
5. virtual・override・abstract・new・sealedの違い
5-1. abstractとの違い:必ず実装させるかどうか
virtualと似たキーワードにabstractがあります。
virtualは、親クラスに基本の処理を書いておき、必要なら子クラスで上書きできる仕組みです。
一方、abstractは親クラス側に処理を書かず、子クラスで必ず実装させる仕組みです。
C#abstract class Animal
{
public abstract void Speak();
}
class Dog : Animal
{
public override void Speak()
{
Console.WriteLine("ワンワン");
}
}
abstractメソッドには処理本体がありません。
そのため、継承した子クラスは必ずoverrideして実装する必要があります。
使い分けとしては、親クラスに共通の既定処理を持たせたいならvirtual、子クラスごとに必ず実装させたいならabstractを使います。
5-2. newとの違い:上書きではなく隠ぺいになる点
newは、親クラスのメンバーを「上書き」するのではなく「隠ぺい」するためのキーワードです。
C#class Parent
{
public void Show()
{
Console.WriteLine("Parent.Show");
}
}
class Child : Parent
{
public new void Show()
{
Console.WriteLine("Child.Show");
}
}
一見するとoverrideと似ていますが、動作は異なります。
C#Parent obj = new Child();
obj.Show();
実行結果は次のようになります。
C#Parent.Show
変数の型がParentなので、親クラスのShowが呼ばれます。
一方、virtualとoverrideを使った場合は、実際のインスタンスであるChildのメソッドが呼ばれます。
C#class Parent
{
public virtual void Show()
{
Console.WriteLine("Parent.Show");
}
}
class Child : Parent
{
public override void Show()
{
Console.WriteLine("Child.Show");
}
}
C#Parent obj = new Child();
obj.Show();
実行結果は次のようになります。
C#Child.Show
newは親クラスのメンバーを隠すだけで、ポリモーフィズムによる上書きではない点に注意しましょう。
5-3. sealed overrideとの違い:それ以上overrideさせない指定
sealed overrideは、上書きしたメソッドを、それ以上派生クラスで上書きさせないために使います。
C#class Animal
{
public virtual void Speak()
{
Console.WriteLine("動物が鳴きます");
}
}
class Dog : Animal
{
public sealed override void Speak()
{
Console.WriteLine("ワンワン");
}
}
class ToyPoodle : Dog
{
// Dog.Speak は sealed override なので、ここでは override できない
}
sealed overrideを使うと、継承階層の途中で上書きを禁止できます。
「このクラスでは処理を確定させたい」「これ以上変更されると困る」という場合に使います。
5-4. interfaceとの違い:継承設計と契約の使い分け
interfaceは、クラスに「このメソッドやプロパティを持つべき」という契約を定義する仕組みです。
C#interface IPayment
{
void Pay();
}
class CreditCardPayment : IPayment
{
public void Pay()
{
Console.WriteLine("クレジットカードで支払います");
}
}
virtualはクラス継承の中で、親クラスの処理を必要に応じて変更するために使います。
一方、interfaceは「この機能を持っている」というルールを定義し、実装内容は各クラスに任せます。
| 項目 | virtual | interface |
|---|---|---|
| 主な目的 | 親クラスの処理を子クラスで変更する | 実装すべき機能の契約を定義する |
| 共通処理 | 親クラスに書ける | 基本的には実装クラス側に書く |
| 継承関係 | クラス継承が前提 | 異なるクラスに共通の型を与えられる |
| 使う場面 | 基底クラスの一部を変更したい | 機能の共通ルールを作りたい |
継承による共通処理が必要ならvirtual、実装のルールだけを決めたいならinterfaceを検討するとよいでしょう。
5-5. どのキーワードを使うべきか判断するポイント
それぞれのキーワードは、次のように使い分けると分かりやすいです。
| キーワード | 使う場面 |
|---|---|
virtual | 親クラスに基本処理があり、必要なら子クラスで変更したい |
override | 親クラスのvirtualやabstractメンバーを上書きしたい |
abstract | 子クラスに必ず実装させたい |
new | 親クラスのメンバーを隠したい。ただし使用は慎重にする |
sealed override | これ以上、派生クラスで上書きさせたくない |
interface | 機能の契約を定義したい |
初心者のうちは、まずvirtualとoverrideの基本を押さえ、その後にabstractやinterfaceとの違いを理解していくとよいです。
6. virtualを使うときの注意点
6-1. 何でもvirtualにすると設計が複雑になる
virtualを使うと、派生クラスで処理を自由に変更できます。
これは便利ですが、何でもvirtualにすると、どこで処理が上書きされているのか分かりにくくなります。
たとえば、あるメソッドの動作を確認したいときに、親クラスだけでなく、すべての派生クラスを確認しなければならなくなることがあります。
そのため、virtualは「本当に派生クラスで変更される可能性がある処理」に限定して使うのが基本です。
6-2. コンストラクター内でvirtualメソッドを呼ばない
C#では、コンストラクター内でvirtualメソッドを呼ぶのは避けるべきです。
理由は、親クラスのコンストラクターが実行されている段階で、子クラスのoverrideメソッドが呼ばれる可能性があるからです。
C#class Parent
{
public Parent()
{
Initialize();
}
public virtual void Initialize()
{
Console.WriteLine("Parent Initialize");
}
}
class Child : Parent
{
private string message = "Child Initialize";
public override void Initialize()
{
Console.WriteLine(message);
}
}
このようなコードでは、子クラスの初期化が完了する前にoverrideメソッドが呼ばれる可能性があります。
結果として、想定外の値や未初期化の状態を使ってしまう危険があります。
コンストラクター内ではvirtualメソッドを呼ばず、初期化処理は別の方法で設計するのが安全です。
6-3. staticメソッドにはvirtualを付けられない
staticメソッドにはvirtualを付けられません。
C#class Sample
{
public static virtual void Show()
{
}
}
このコードはエラーになります。
staticメソッドはクラスそのものに属するメソッドであり、インスタンスごとに動作を切り替えるものではありません。
virtualはインスタンスの実際の型に応じて呼び出すメソッドを変える仕組みなので、staticとは組み合わせられません。
6-4. privateメソッドにはvirtualを付けられない
privateメソッドにもvirtualを付けられません。
C#class Sample
{
private virtual void Show()
{
}
}
このコードもエラーになります。
privateメソッドは、そのクラスの中からしか呼び出せません。
子クラスからアクセスできないため、上書きする意味がありません。
派生クラスで上書きしたい場合は、public、protected、internalなど、適切なアクセス修飾子を使います。
特に、外部には公開したくないが派生クラスでは上書きしたい場合は、protected virtualがよく使われます。
C#class BaseClass
{
protected virtual void OnExecute()
{
Console.WriteLine("基本処理");
}
}
6-5. override時はアクセス修飾子や戻り値に注意する
overrideするときは、親クラスのメソッドとシグネチャを一致させる必要があります。
基本的には、次の要素を合わせます。
| 項目 | 注意点 |
|---|---|
| メソッド名 | 親クラスと同じ名前にする |
| 引数 | 型・数・順番を一致させる |
| 戻り値 | 原則として一致させる |
| アクセス修飾子 | 基本的に親クラスと同じにする |
たとえば、親クラスが次のようになっている場合、
C#class Parent
{
public virtual int GetValue()
{
return 1;
}
}
子クラスでは次のように書きます。
C#class Child : Parent
{
public override int GetValue()
{
return 2;
}
}
戻り値をstringに変えたり、引数を追加したりすると、同じメソッドの上書きとはみなされません。
6-6. 予期しない上書きを防ぐにはsealedを使う
派生クラスでこれ以上メソッドを上書きさせたくない場合は、sealed overrideを使います。
C#class BaseService
{
public virtual void Execute()
{
Console.WriteLine("基本処理");
}
}
class CustomService : BaseService
{
public sealed override void Execute()
{
Console.WriteLine("確定した処理");
}
}
この場合、CustomServiceをさらに継承したクラスでは、Executeメソッドをoverrideできません。
設計上、処理を固定したい場合に有効です。
7. virtualでよくあるエラーと解決方法
7-1. 「no suitable method found to override」が出る原因
C#でoverrideを書いたときに、次のようなエラーが出ることがあります。
C#no suitable method found to override
これは「上書きできる適切なメソッドが見つかりません」という意味です。
主な原因は次のとおりです。
| 原因 | 内容 |
|---|---|
親クラスにvirtualがない | 通常メソッドはoverrideできない |
| メソッド名が違う | 名前のスペルミスなど |
| 引数が違う | 引数の型・数・順番が一致していない |
| 戻り値が違う | 親クラスと戻り値が一致していない |
| 継承していない | そもそも対象の親クラスを継承していない |
まずは、親クラスのメソッド定義と、子クラスのoverrideメソッドの定義を比較しましょう。
7-2. virtualを付け忘れてoverrideできない場合
よくあるミスが、親クラス側にvirtualを付け忘れるケースです。
C#class Parent
{
public void Show()
{
Console.WriteLine("Parent");
}
}
class Child : Parent
{
public override void Show()
{
Console.WriteLine("Child");
}
}
このコードはエラーになります。
解決するには、親クラスのメソッドにvirtualを付けます。
C#class Parent
{
public virtual void Show()
{
Console.WriteLine("Parent");
}
}
これで、子クラス側でoverrideできるようになります。
7-3. メソッド名・引数・戻り値の不一致によるエラー
親クラスと子クラスで、メソッド名や引数が少しでも違うとoverrideできません。
C#class Parent
{
public virtual void Show(string message)
{
Console.WriteLine(message);
}
}
class Child : Parent
{
public override void Show()
{
Console.WriteLine("Child");
}
}
この場合、親クラスのShowはstring型の引数を持っていますが、子クラスのShowには引数がありません。
そのため、同じメソッドとはみなされません。
正しくは次のように書きます。
C#class Child : Parent
{
public override void Show(string message)
{
Console.WriteLine("Child: " + message);
}
}
overrideでエラーが出たら、メソッド名、引数、戻り値、アクセス修飾子を確認しましょう。
7-4. static・private・abstractとの組み合わせエラー
virtualは、すべてのキーワードと自由に組み合わせられるわけではありません。
たとえば、次のような書き方はできません。
C#public static virtual void Show()
{
}
staticメソッドはインスタンスに属さないため、virtualにできません。
また、次のような書き方もできません。
C#private virtual void Show()
{
}
privateメソッドは派生クラスから見えないため、virtualにできません。
さらに、abstractとvirtualを同時に付けることもできません。
C#public abstract virtual void Show();
abstractメソッドは、そもそも派生クラスで上書きされることを前提としているため、virtualを重ねて書く必要はありません。
正しくは次のように書きます。
C#public abstract void Show();
7-5. newとoverrideを混同したときの動作ミス
newとoverrideを混同すると、意図したメソッドが呼ばれないことがあります。
C#class Parent
{
public virtual void Show()
{
Console.WriteLine("Parent");
}
}
class Child : Parent
{
public new void Show()
{
Console.WriteLine("Child");
}
}
このコードでは、親クラスのShowがvirtualであるにもかかわらず、子クラス側でoverrideではなくnewを使っています。
C#Parent obj = new Child();
obj.Show();
実行結果は次のようになります。
C#Parent
newは上書きではなく隠ぺいなので、親クラス型の変数から呼び出すと親クラスのメソッドが実行されます。
意図した動作にするには、overrideを使います。
C#class Child : Parent
{
public override void Show()
{
Console.WriteLine("Child");
}
}
これで、親クラス型の変数から呼び出しても、子クラスの処理が実行されます。
8. virtualを使うべき場面・使わないほうがよい場面
8-1. 使うべき場面:継承先で処理変更を想定している場合
virtualを使うべきなのは、継承先で処理を変更することを明確に想定している場合です。
たとえば、次のようなケースです。
| ケース | 例 |
|---|---|
| 種類ごとに処理が変わる | 犬・猫・鳥で鳴き声が違う |
| 共通処理の一部だけ変えたい | レポート出力の本文だけ変える |
| ポリモーフィズムを使いたい | 支払い方法ごとに決済処理を切り替える |
| テスト時に処理を差し替えたい | 外部API呼び出しをテスト用に置き換える |
「このメソッドは派生クラスで変更される可能性がある」と設計上はっきりしているなら、virtualを使う価値があります。
8-2. 使わないほうがよい場面:単純な共通処理だけの場合
逆に、単純な共通処理だけをまとめたい場合は、無理にvirtualにする必要はありません。
C#class Logger
{
public void Log(string message)
{
Console.WriteLine(message);
}
}
このように、ただ共通処理として使いたいだけなら、通常メソッドで十分です。
virtualにすると、派生クラスで処理が変わる可能性が生まれます。
変更される必要がない処理までvirtualにすると、コードの見通しが悪くなることがあります。
8-3. 継承よりコンポジションを検討すべきケース
virtualは継承を前提とした仕組みです。
しかし、すべての設計で継承が最適とは限りません。
たとえば、処理の一部を差し替えたいだけなら、継承ではなくコンポジションを使う方法もあります。
コンポジションとは、クラスの中に別のクラスを持たせて機能を組み合わせる設計です。
C#interface INotificationSender
{
void Send(string message);
}
class EmailSender : INotificationSender
{
public void Send(string message)
{
Console.WriteLine("メール送信: " + message);
}
}
class NotificationService
{
private readonly INotificationSender sender;
public NotificationService(INotificationSender sender)
{
this.sender = sender;
}
public void Notify(string message)
{
sender.Send(message);
}
}
この方法なら、継承を使わずに送信方法を差し替えられます。
継承関係が自然でない場合や、機能の組み合わせを柔軟に変えたい場合は、virtualよりコンポジションを検討しましょう。
8-4. 初心者が判断に迷ったときのチェックリスト
virtualを使うべきか迷ったときは、次の点を確認すると判断しやすくなります。
| チェック項目 | 判断 |
|---|---|
| 派生クラスで処理を変える予定があるか | あるならvirtualを検討 |
| 親クラスに共通の既定処理があるか | あるならvirtualが向いている |
| 子クラスに必ず実装させたいか | そうならabstractを検討 |
| 継承関係が自然か | 不自然ならコンポジションを検討 |
| 外部から変更されると困る処理か | 困るならvirtualにしない |
初心者のうちは、「上書きしてほしい理由が明確にあるときだけvirtualにする」と考えると失敗しにくいです。
9. C#のvirtualに関するよくある質問
9-1. virtualとoverrideは必ずセットで使う?
必ずセットで使うわけではありません。
親クラス側でvirtualを付けても、子クラスでoverrideしないことはあります。
C#class Animal
{
public virtual void Speak()
{
Console.WriteLine("動物が鳴きます");
}
}
class Dog : Animal
{
}
この場合、DogクラスではSpeakを上書きしていないため、親クラスの処理がそのまま使われます。
ただし、子クラスで上書きしたい場合は、親クラス側のvirtualと子クラス側のoverrideが必要です。
9-2. virtualメソッドは必ずoverrideしないといけない?
いいえ、必ずoverrideする必要はありません。
virtualは「上書きできる」という意味であり、「上書きしなければならない」という意味ではありません。
必ず子クラスで実装させたい場合は、virtualではなくabstractを使います。
C#abstract class Animal
{
public abstract void Speak();
}
この場合、Animalを継承したクラスは、必ずSpeakメソッドを実装する必要があります。
9-3. overrideしたメソッドをさらにoverrideできる?
はい、できます。
overrideしたメソッドは、さらに派生したクラスで再度overrideできます。
C#class Animal
{
public virtual void Speak()
{
Console.WriteLine("動物が鳴きます");
}
}
class Dog : Animal
{
public override void Speak()
{
Console.WriteLine("ワンワン");
}
}
class SmallDog : Dog
{
public override void Speak()
{
Console.WriteLine("キャンキャン");
}
}
このように、overrideされたメソッドも、さらに派生クラスで上書きできます。
ただし、途中でsealed overrideが指定されている場合は、それ以上overrideできません。
9-4. プロパティにもvirtualは使える?
はい、プロパティにもvirtualを使えます。
C#class Product
{
public virtual decimal Price
{
get { return 1000m; }
}
}
class DiscountProduct : Product
{
public override decimal Price
{
get { return 800m; }
}
}
このように、プロパティの取得処理を子クラスで変更できます。
メソッドだけでなく、プロパティの動作を差し替えたい場合にもvirtualは有効です。
9-5. UnityのStartやUpdateにvirtualは必要?
UnityのStartやUpdateは、通常のC#のvirtualとoverrideの仕組みで呼ばれているわけではありません。
Unityでは、MonoBehaviourに定義した特定の名前のメソッドをUnity側が自動的に呼び出します。
C#void Start()
{
// 最初に呼ばれる処理
}
void Update()
{
// 毎フレーム呼ばれる処理
}
そのため、通常はStartやUpdateにvirtualを付ける必要はありません。
ただし、自分で作った基底クラスのStartやUpdateを派生クラスで上書きしたい場合は、設計によってvirtualやoverrideを使うことがあります。
C#class BaseCharacter : MonoBehaviour
{
protected virtual void Start()
{
Debug.Log("共通の初期化");
}
}
class Player : BaseCharacter
{
protected override void Start()
{
base.Start();
Debug.Log("プレイヤーの初期化");
}
}
この場合は、C#の継承設計としてvirtualを使っています。
9-6. virtualを使うと処理速度は遅くなる?
virtualメソッドは、通常のメソッド呼び出しと比べて、実行時に呼び出すメソッドを判断する仕組みが入ります。
そのため、理論上はわずかなオーバーヘッドがあります。
ただし、一般的なアプリケーション開発では、virtualの速度差を気にする場面は多くありません。
パフォーマンスよりも、設計の分かりやすさや保守性を優先して判断することが大切です。
大量に呼ばれる処理やゲームの毎フレーム処理などでパフォーマンスが気になる場合は、実際に計測して判断しましょう。
まとめ
C#のvirtualは、親クラスのメソッドやプロパティを、派生クラスで上書きできるようにするためのキーワードです。
親クラス側ではvirtualを使い、子クラス側ではoverrideを使います。
C#class Parent
{
public virtual void Show()
{
Console.WriteLine("親クラスの処理");
}
}
class Child : Parent
{
public override void Show()
{
Console.WriteLine("子クラスの処理");
}
}
virtualを使うことで、継承先ごとに異なる処理を実装したり、共通処理の一部だけを差し替えたり、ポリモーフィズムを活用した柔軟な設計ができます。
一方で、何でもvirtualにすると、どこで処理が変更されるのか分かりにくくなり、設計が複雑になることもあります。
そのため、virtualは「派生クラスで処理を変更することを明確に想定している場合」に使うのが基本です。
最後に、重要なポイントを整理します。
| キーワード | 意味 |
|---|---|
virtual | 親クラスで、子クラスによる上書きを許可する |
override | 子クラスで、親クラスのvirtualメンバーを上書きする |
abstract | 子クラスに必ず実装させる |
new | 上書きではなく、親クラスのメンバーを隠す |
sealed override | それ以上の上書きを禁止する |
C#のvirtualを理解すると、継承やポリモーフィズムを使った設計が分かりやすくなります。
まずは、親クラスにvirtual、子クラスにoverrideを書く基本形から覚えていきましょう。

