C#のbaseキーワードとは?継承・コンストラクタ呼び出し・overrideとの違いを初心者向けに徹底解説
はじめに
C#でクラスの継承を学ぶと、baseというキーワードが登場します。baseは、子クラスから親クラスのメソッドやプロパティを参照したり、親クラスのコンストラクタを呼び出したりするためのキーワードです。
たとえば、親クラスに共通処理があり、子クラスではその処理を実行したうえで独自の処理を追加したい場合にbaseを使用します。
C#public class Animal
{
public virtual void Speak()
{
Console.WriteLine("動物が鳴きます");
}
}
public class Dog : Animal
{
public override void Speak()
{
base.Speak();
Console.WriteLine("ワンワン");
}
}
この例では、base.Speak()によって親クラスであるAnimalのSpeakメソッドを呼び出しています。
この記事では、C#のbaseキーワードについて、継承との関係、メソッドやプロパティへのアクセス、コンストラクタの呼び出し、overrideやthisとの違いまで、初心者向けに詳しく解説します。
1. C#のbaseキーワードとは?初心者向けに役割をわかりやすく解説
1-1. baseは親クラスのメンバーを参照するためのキーワード
C#のbaseは、現在のクラスが継承している親クラスを表すキーワードです。
子クラスの中から、親クラスが持つ次のようなメンバーを明示的に参照できます。
インスタンスメソッド
プロパティ
フィールド
インデクサー
コンストラクタ
基本的な書き方は次のとおりです。
C#base.メンバー名
親クラスのメソッドを呼び出す場合は、次のように記述します。
C#base.MethodName();
親クラスのプロパティを参照する場合は、次のように記述します。
C#string value = base.PropertyName;
親クラスのコンストラクタを呼び出す場合は、子クラスのコンストラクタ宣言に: base(...)を追加します。
C#public Child(string name) : base(name)
{
}
baseを使うと、子クラス自身のメンバーではなく、親クラス側の実装を利用することをコード上で明確にできます。
1-2. 継承における「親クラス」と「子クラス」の関係
継承とは、既存のクラスが持つ機能を別のクラスに引き継ぐ仕組みです。
継承される側を「親クラス」「基底クラス」「スーパークラス」と呼びます。継承する側を「子クラス」「派生クラス」「サブクラス」と呼びます。
C#では、クラス名の後ろにコロンと親クラス名を書くことで継承できます。
C#public class Animal
{
public void Eat()
{
Console.WriteLine("食事をします");
}
}
public class Dog : Animal
{
}
この場合、Animalが親クラス、Dogが子クラスです。DogはAnimalの公開された機能を引き継ぐため、Eatメソッドを利用できます。
C#Dog dog = new Dog();
dog.Eat();
baseは、このような継承関係があるクラスの内部で、親クラス側のメンバーを指定するために使います。
1-3. baseを使う主な場面は「メソッド・プロパティ・コンストラクタ」
baseを使う代表的な場面は、次の3つです。
1つ目は、親クラスのメソッドを呼び出す場面です。
C#base.Execute();
2つ目は、親クラスのプロパティやフィールドを参照する場面です。
C#Console.WriteLine(base.Name);
3つ目は、親クラスのコンストラクタを呼び出す場面です。
C#public Dog(string name) : base(name)
{
}
特に実務では、オーバーライドしたメソッドの中で親クラスの共通処理を残したい場合や、親クラスの初期化に必要な値を渡す場合によく使われます。
1-4. thisキーワードとの違い
baseと似たキーワードにthisがあります。
thisは現在のインスタンス、つまり子クラス自身を表します。一方、baseは現在のクラスから見た親クラス部分を表します。
C#public class Parent
{
protected string Name = "親クラス";
}
public class Child : Parent
{
private new string Name = "子クラス";
public void ShowNames()
{
Console.WriteLine(this.Name);
Console.WriteLine(base.Name);
}
}
実行結果は次のようになります。
子クラス
親クラス
this.Nameは現在の子クラスにあるNameを参照し、base.Nameは親クラスにあるNameを参照します。
また、コンストラクタでは役割が異なります。
C#public Child() : this("名前なし")
{
}
public Child(string name) : base(name)
{
}
: this(...)は同じクラス内の別のコンストラクタを呼び出し、: base(...)は親クラスのコンストラクタを呼び出します。
2. C#でbaseを使う前に理解したい継承の基本
2-1. 継承とは何か
継承は、複数のクラスに共通する処理を親クラスにまとめ、子クラスで再利用するための仕組みです。
たとえば、犬と猫には「名前を持つ」「食事をする」といった共通点があります。これらをそれぞれのクラスに重複して書くのではなく、Animalクラスにまとめられます。
C#public class Animal
{
public string Name { get; set; } = "";
public void Eat()
{
Console.WriteLine($"{Name}が食事をします");
}
}
public class Dog : Animal
{
public void Bark()
{
Console.WriteLine($"{Name}がワンワンと鳴きます");
}
}
public class Cat : Animal
{
public void Meow()
{
Console.WriteLine($"{Name}がニャーと鳴きます");
}
}
継承を使うことで、共通処理の重複を減らし、修正箇所をまとめやすくなります。
ただし、単にコードを再利用したいという理由だけで継承を使うと、クラス同士の結びつきが強くなることがあります。継承は、子クラスが親クラスの一種であるという関係が自然な場合に使うことが重要です。
2-2. 親クラスと子クラスのサンプルコード
次の例では、Employeeを親クラス、Managerを子クラスとして定義しています。
C#public class Employee
{
public string Name { get; }
public Employee(string name)
{
Name = name;
}
public virtual void Work()
{
Console.WriteLine($"{Name}が仕事をします");
}
}
public class Manager : Employee
{
public Manager(string name) : base(name)
{
}
public override void Work()
{
base.Work();
Console.WriteLine($"{Name}がチームを管理します");
}
}
Managerクラスでは、次の2か所でbaseを使っています。
C#public Manager(string name) : base(name)
これは親クラスのコンストラクタにnameを渡しています。
C#base.Work();
これは親クラスのWorkメソッドを呼び出しています。
2-3. 継承すると使えるメンバーと使えないメンバー
子クラスから利用できる親クラスのメンバーは、アクセス修飾子によって決まります。
publicメンバーは、子クラスからも外部のクラスからも利用できます。
C#public void PublicMethod()
{
}
protectedメンバーは、親クラス自身と子クラスから利用できます。
C#protected void ProtectedMethod()
{
}
privateメンバーは、宣言したクラスの内部でしか利用できません。子クラスからbaseを使ってもアクセスできません。
C#private void PrivateMethod()
{
}
internalメンバーは、原則として同じアセンブリ内から利用できます。
また、コンストラクタは通常のメソッドのように継承されません。子クラスから必要な親クラスのコンストラクタを: base(...)で明示的に呼び出します。
2-4. access modifierとbaseの関係
baseを付ければ、親クラスのすべてのメンバーにアクセスできるわけではありません。アクセス可能かどうかは、アクセス修飾子のルールに従います。
C#public class Parent
{
public int PublicValue = 1;
protected int ProtectedValue = 2;
private int PrivateValue = 3;
}
public class Child : Parent
{
public void Show()
{
Console.WriteLine(base.PublicValue);
Console.WriteLine(base.ProtectedValue);
// コンパイルエラー
// Console.WriteLine(base.PrivateValue);
}
}
主なアクセス修飾子と子クラスからのアクセス可否は次のとおりです。
| アクセス修飾子 | 子クラスからのアクセス |
|---|---|
public | 可能 |
protected | 可能 |
private | 不可 |
internal | 同じアセンブリなら可能 |
protected internal | 同じアセンブリ、または派生クラスなら可能 |
private protected | 同じアセンブリ内の派生クラスなら可能 |
baseはアクセス制限を解除するキーワードではなく、アクセス可能な親クラスのメンバーを明示的に選択するキーワードです。
3. baseを使って親クラスのメソッドを呼び出す方法
3-1. base.メソッド名()の基本構文
親クラスのインスタンスメソッドを呼び出す基本構文は次のとおりです。
C#base.メソッド名();
引数が必要な場合は、通常のメソッド呼び出しと同じように指定します。
C#base.メソッド名(引数1, 引数2);
具体例を見てみましょう。
C#public class Printer
{
protected void PrintMessage(string message)
{
Console.WriteLine($"親クラス: {message}");
}
}
public class ColorPrinter : Printer
{
public void Print()
{
base.PrintMessage("印刷を開始します");
}
}
ColorPrinterはPrinterを継承しているため、protectedなPrintMessageメソッドをbase経由で呼び出せます。
3-2. 子クラスで同名メソッドを定義したときの呼び出し
親クラスと子クラスに同じ名前のメソッドがある場合、通常の呼び出しでは子クラス側のメソッドが対象になります。親クラス側を呼び出したい場合にbaseを使います。
C#public class Parent
{
public void Show()
{
Console.WriteLine("親クラスのShow");
}
}
public class Child : Parent
{
public new void Show()
{
Console.WriteLine("子クラスのShow");
}
public void ShowBoth()
{
this.Show();
base.Show();
}
}
実行コードは次のとおりです。
C#Child child = new Child();
child.ShowBoth();
実行結果は次のようになります。
子クラスのShow
親クラスのShow
この例のnewは、親クラスのメソッドを隠蔽することを明示するキーワードです。newによる隠蔽とoverrideによるオーバーライドは異なる仕組みなので注意が必要です。
3-3. overrideしたメソッド内でbaseを使う例
親クラスのメソッドがvirtualで宣言されている場合、子クラスではoverrideを使って処理を上書きできます。
C#public class NotificationService
{
public virtual void Send(string message)
{
Console.WriteLine($"通知を送信しました: {message}");
}
}
public class LoggingNotificationService : NotificationService
{
public override void Send(string message)
{
Console.WriteLine("送信処理を開始します");
base.Send(message);
Console.WriteLine("送信処理が完了しました");
}
}
LoggingNotificationService.Sendでは、親クラスの送信処理をbase.Send(message)で実行し、その前後にログ処理を追加しています。
C#var service = new LoggingNotificationService();
service.Send("メンテナンスのお知らせ");
実行結果は次のようになります。
送信処理を開始します
通知を送信しました: メンテナンスのお知らせ
送信処理が完了しました
3-4. baseを使うメリットと実務での使いどころ
baseを使う主なメリットは、親クラスに用意された共通処理を再利用できることです。
実務では、次のような場面で使われます。
共通の入力チェックを実行してから子クラス独自の処理を行う
親クラスの保存処理を残しつつ、ログ出力を追加する
画面やコンポーネントの初期化処理を拡張する
例外処理や後片付け処理を共通化する
フレームワークの基底クラスが提供する処理を呼び出す
たとえば、共通のバリデーションを親クラスに定義できます。
C#public class Form
{
public virtual bool Validate()
{
Console.WriteLine("共通項目を検証します");
return true;
}
}
public class UserForm : Form
{
public override bool Validate()
{
bool baseResult = base.Validate();
Console.WriteLine("ユーザー名を検証します");
bool userNameResult = true;
return baseResult && userNameResult;
}
}
ただし、親クラスの処理を呼び出す必要がない場合まで、習慣的にbaseを使う必要はありません。親クラスの契約や処理内容を理解したうえで判断します。
4. baseを使って親クラスのプロパティやフィールドにアクセスする方法
4-1. base.プロパティ名の基本構文
親クラスのプロパティにアクセスする場合は、次の形式で記述します。
C#base.プロパティ名
値を読み取る例は次のとおりです。
C#string value = base.Name;
書き込み可能なプロパティであれば、値を代入することもできます。
C#base.Name = "新しい名前";
具体例を見てみましょう。
C#public class Product
{
protected decimal Price { get; set; }
}
public class DiscountProduct : Product
{
public void SetOriginalPrice(decimal price)
{
base.Price = price;
}
public decimal GetDiscountedPrice()
{
return base.Price * 0.9m;
}
}
Priceはprotectedなので、子クラスからbase.Priceとして参照できます。
4-2. 親クラスと子クラスで同じ名前のメンバーがある場合
親クラスと子クラスに同じ名前のプロパティやフィールドがある場合、baseを使うと親クラス側を明示できます。
C#public class Parent
{
protected string Message { get; set; } = "親クラスのメッセージ";
}
public class Child : Parent
{
private new string Message { get; set; } = "子クラスのメッセージ";
public void ShowMessages()
{
Console.WriteLine(this.Message);
Console.WriteLine(base.Message);
}
}
実行結果は次のとおりです。
子クラスのメッセージ
親クラスのメッセージ
ただし、同名メンバーをむやみに定義すると、どのメンバーを参照しているのか分かりにくくなります。設計上の明確な理由がない限り、異なる名前を付けるか、オーバーライド可能なプロパティとして設計する方が安全です。
4-3. protectedメンバーとbaseの関係
protectedは、継承先で利用することを想定したアクセス修飾子です。
C#public class Account
{
protected decimal Balance { get; private set; }
protected void Deposit(decimal amount)
{
Balance += amount;
}
}
public class SavingsAccount : Account
{
public void AddInterest(decimal interest)
{
base.Deposit(interest);
Console.WriteLine($"現在の残高: {base.Balance}");
}
}
SavingsAccountから見ると、DepositとBalanceはアクセス可能です。
なお、同名メンバーが存在しない場合は、必ずしもbaseを書く必要はありません。
C#Deposit(interest);
Console.WriteLine(Balance);
このように書いても動作します。baseを付けると、親クラスのメンバーを利用していることが明確になります。
4-4. privateメンバーにbaseでアクセスできない理由
privateメンバーは、宣言されたクラスの内部実装だけで使用するためのものです。子クラスであっても直接アクセスできません。
C#public class Parent
{
private string secret = "秘密";
}
public class Child : Parent
{
public void ShowSecret()
{
// コンパイルエラー
// Console.WriteLine(base.secret);
}
}
子クラスに値を公開する必要がある場合は、親クラス側でprotectedなプロパティやメソッドを用意します。
C#public class Parent
{
private string secret = "秘密";
protected string GetSecret()
{
return secret;
}
}
public class Child : Parent
{
public void ShowSecret()
{
Console.WriteLine(base.GetSecret());
}
}
この方法なら、フィールド自体を直接公開せず、親クラスが決めた方法で値を取得できます。カプセル化を保つため、フィールドを安易にprotectedへ変更するより、必要な操作だけをメソッドやプロパティとして公開する方法が適しています。
5. base()で親クラスのコンストラクタを呼び出す方法
5-1. base()の基本構文
子クラスのコンストラクタから親クラスのコンストラクタを呼び出すには、コンストラクタ初期化子として: base(...)を記述します。
C#public 子クラス名() : base()
{
}
引数を渡す場合は次のように記述します。
C#public 子クラス名(string value) : base(value)
{
}
: base(...)は、コンストラクタ本体の前に記述します。
C#public Child(string name) : base(name)
{
Console.WriteLine("子クラスを初期化します");
}
コンストラクタ本体の中で、通常のメソッドのようにbase()を呼び出すことはできません。
5-2. 引数なしコンストラクタを呼び出す例
親クラスに引数なしコンストラクタがある場合、次のように呼び出せます。
C#public class Parent
{
public Parent()
{
Console.WriteLine("親クラスのコンストラクタ");
}
}
public class Child : Parent
{
public Child() : base()
{
Console.WriteLine("子クラスのコンストラクタ");
}
}
実行コードは次のとおりです。
C#Child child = new Child();
実行結果は次のようになります。
親クラスのコンストラクタ
子クラスのコンストラクタ
子クラスのコンストラクタ本体より先に、親クラスのコンストラクタが実行されます。
5-3. 引数ありコンストラクタを呼び出す例
親クラスに引数付きコンストラクタしかない場合、子クラスは必要な引数を: base(...)で渡します。
C#public class Person
{
public string Name { get; }
public Person(string name)
{
Name = name;
}
}
public class Student : Person
{
public int Grade { get; }
public Student(string name, int grade) : base(name)
{
Grade = grade;
}
}
使用例は次のとおりです。
C#Student student = new Student("佐藤", 2);
Console.WriteLine(student.Name);
Console.WriteLine(student.Grade);
実行結果は次のようになります。
佐藤
2
nameは親クラスのコンストラクタに渡され、gradeは子クラスのコンストラクタ本体で処理されています。
5-4. 子クラスのコンストラクタ実行前に親クラスのコンストラクタが呼ばれる仕組み
オブジェクトを安全に生成するには、親クラス部分を先に初期化し、その後で子クラス部分を初期化する必要があります。
たとえば、3段階の継承がある場合を考えます。
C#public class A
{
public A()
{
Console.WriteLine("A");
}
}
public class B : A
{
public B()
{
Console.WriteLine("B");
}
}
public class C : B
{
public C()
{
Console.WriteLine("C");
}
}
new C()を実行すると、結果は次のようになります。
A
B
C
最上位の親クラスから順番にコンストラクタが実行されます。
AのコンストラクタBのコンストラクタCのコンストラクタ
この順番により、子クラスの初期化処理が始まる時点で、親クラス側の状態が準備済みになります。
5-5. base()を省略できるケースと省略できないケース
親クラスにアクセス可能な引数なしコンストラクタがある場合、: base()は省略できます。
次の2つは実質的に同じです。
C#public Child() : base()
{
}
C#public Child()
{
}
省略した場合、コンパイラが暗黙的にbase()の呼び出しを追加します。
一方、親クラスに引数なしコンストラクタがなく、引数付きコンストラクタしかない場合は省略できません。
C#public class Parent
{
public Parent(string name)
{
}
}
public class Child : Parent
{
// コンパイルエラー
// public Child()
// {
// }
}
正しくは、次のように親クラスのコンストラクタへ値を渡します。
C#public class Child : Parent
{
public Child() : base("デフォルト名")
{
}
}
また、親クラスの引数なしコンストラクタがprivateの場合も、子クラスから呼び出せないため、別のアクセス可能なコンストラクタを指定する必要があります。
6. baseとoverrideの違いを初心者向けに整理
6-1. overrideは親クラスのvirtualメソッドを上書きする仕組み
overrideは、親クラスでvirtualまたはabstractとして宣言されたメソッドやプロパティを、子クラスで再実装するためのキーワードです。
C#public class Animal
{
public virtual void Speak()
{
Console.WriteLine("動物が鳴きます");
}
}
public class Dog : Animal
{
public override void Speak()
{
Console.WriteLine("ワンワン");
}
}
Dogのインスタンスに対してSpeakを呼び出すと、子クラス側の処理が実行されます。
C#Animal animal = new Dog();
animal.Speak();
実行結果は次のとおりです。
ワンワン
変数の型がAnimalであっても、実際のインスタンスがDogであるため、オーバーライドされたDog.Speakが呼び出されます。
6-2. baseは上書き元の親クラス処理を呼び出す仕組み
overrideが「親クラスの処理を上書きする仕組み」であるのに対し、baseは「親クラス側の処理を明示的に呼び出す仕組み」です。
C#public class Dog : Animal
{
public override void Speak()
{
base.Speak();
Console.WriteLine("ワンワン");
}
}
実行結果は次のようになります。
動物が鳴きます
ワンワン
overrideとbaseは対立するものではありません。親クラスの処理を拡張したい場合は、両方を組み合わせて使います。
6-3. virtual・override・baseの関係
3つのキーワードの役割は次のように整理できます。
virtual:親クラス側で、子クラスによる上書きを許可するoverride:子クラス側で、親クラスの仮想メンバーを上書きするbase:子クラス側から、親クラスの実装を呼び出す
C#public class Parent
{
public virtual void Execute()
{
Console.WriteLine("親クラスの処理");
}
}
public class Child : Parent
{
public override void Execute()
{
base.Execute();
Console.WriteLine("子クラスの処理");
}
}
この流れでは、親クラスがvirtualで拡張可能な処理を用意し、子クラスがoverrideで処理を変更し、必要に応じてbaseで元の処理を再利用しています。
なお、baseはoverrideの中だけで使うキーワードではありません。アクセス可能であれば、非仮想メソッドやプロパティに対しても使用できます。
6-4. override内でbaseを呼ぶべきケース
override内でbaseを呼ぶべき代表的なケースは、親クラスの処理が子クラスでも必要な場合です。
C#public class Repository
{
public virtual void Save()
{
Console.WriteLine("データを保存します");
}
}
public class LoggingRepository : Repository
{
public override void Save()
{
Console.WriteLine("保存開始");
base.Save();
Console.WriteLine("保存終了");
}
}
親クラスのSaveが実際の保存処理を担当し、子クラスがログだけを追加する設計なら、base.Save()を呼ぶ必要があります。
ほかにも、次のような処理ではbaseの呼び出しが重要になる場合があります。
基底クラスによる状態の更新
リソースの解放
イベントの発生
フレームワーク内部の初期化
共通の入力検証
共通ログや監査処理
親クラスのドキュメントや実装上の契約で、基底処理の呼び出しが求められている場合は、適切な位置でbaseを呼び出します。
6-5. override内でbaseを呼ばないケース
子クラスで親クラスの処理を完全に置き換えたい場合は、baseを呼び出しません。
C#public class PaymentService
{
public virtual void Pay()
{
Console.WriteLine("標準の支払い処理");
}
}
public class TestPaymentService : PaymentService
{
public override void Pay()
{
Console.WriteLine("テスト用の支払い処理");
}
}
この例では、テスト用クラスが実際の支払い処理を実行しないように、親クラスのPayを呼んでいません。
ただし、親クラスの処理を呼ばないことで、必要な初期化や状態更新まで失われることがあります。baseを省略するときは、親クラスの処理を完全に置き換えて問題がないか確認する必要があります。
7. base・this・override・virtualの違いを比較
7-1. baseとthisの違い
thisは現在のインスタンスを表し、baseは親クラス側のメンバーを指定します。
C#public class Parent
{
protected virtual string GetName()
{
return "Parent";
}
}
public class Child : Parent
{
protected override string GetName()
{
return "Child";
}
public void Show()
{
Console.WriteLine(this.GetName());
Console.WriteLine(base.GetName());
}
}
実行結果は次のとおりです。
Child
Parent
また、コンストラクタ初期化子として使う場合は、次の違いがあります。
C#public Child() : this("名前なし")
{
}
public Child(string name) : base()
{
}
this(...):同じクラスの別コンストラクタを呼ぶbase(...):親クラスのコンストラクタを呼ぶ
1つのコンストラクタで、: this(...)と: base(...)を同時に直接指定することはできません。ただし、: this(...)で呼ばれた別のコンストラクタが、最終的に: base(...)を呼び出すことはできます。
7-2. baseとoverrideの違い
baseとoverrideの違いは、目的にあります。
overrideは、親クラスの仮想メンバーに対して子クラス独自の実装を定義します。
C#public override void Run()
{
}
baseは、親クラスにある実装を呼び出します。
C#base.Run();
両者を組み合わせると、親クラスの処理を残しながら子クラスの処理を追加できます。
C#public override void Run()
{
base.Run();
Console.WriteLine("追加処理");
}
7-3. overrideとvirtualの違い
virtualは、親クラス側で使用します。
C#public virtual void Execute()
{
}
overrideは、それを上書きする子クラス側で使用します。
C#public override void Execute()
{
}
親クラスのメソッドにvirtual、abstract、または既存のoverrideが付いていなければ、子クラスでoverrideできません。
C#public class Parent
{
public void Execute()
{
}
}
public class Child : Parent
{
// コンパイルエラー
// public override void Execute()
// {
// }
}
この場合、メソッドを隠すことはできますが、オーバーライドとは異なります。
C#public new void Execute()
{
}
7-4. abstractメソッドではbase呼び出しできるのか
abstractメソッドには処理本体がありません。そのため、抽象メソッドを直接オーバーライドしたクラスから、その抽象実装をbaseで呼ぶことはできません。
C#public abstract class Animal
{
public abstract void Speak();
}
public class Dog : Animal
{
public override void Speak()
{
// コンパイルエラー
// base.Speak();
Console.WriteLine("ワンワン");
}
}
Animal.Speakには実行できる本体がないため、base.Speak()は成立しません。
ただし、中間クラスが抽象メソッドをオーバーライドして具体的な処理を実装している場合、そのさらに下の子クラスからは中間クラスの実装を呼び出せます。
C#public abstract class Animal
{
public abstract void Speak();
}
public class Dog : Animal
{
public override void Speak()
{
Console.WriteLine("犬が鳴きます");
}
}
public class ShibaInu : Dog
{
public override void Speak()
{
base.Speak();
Console.WriteLine("ワンワン");
}
}
この場合、ShibaInuの直接の親クラスであるDogには具体的な実装があるため、base.Speak()を呼び出せます。
7-5. 用語の違いを一覧で理解する
| キーワード | 主な役割 | 主に記述する場所 |
|---|---|---|
base | 親クラスのメンバーやコンストラクタを参照する | 子クラス |
this | 現在のインスタンスや同じクラスのコンストラクタを参照する | インスタンスメンバー、コンストラクタ |
virtual | 子クラスによる上書きを許可する | 親クラス |
override | 親クラスの仮想メンバーを上書きする | 子クラス |
abstract | 実装を子クラスに要求する | 抽象クラス |
new | 親クラスと同名のメンバーを隠す | 子クラス |
初心者が混乱しやすい点は、baseとoverrideが同じ種類の機能ではないことです。overrideはメンバーを定義するためのキーワードで、baseは親クラスのメンバーを参照するためのキーワードです。
8. C#のbaseキーワードの具体的なサンプルコード
8-1. 親クラスのメソッドをbaseで呼び出すサンプル
C#using System;
public class Machine
{
protected void Start()
{
Console.WriteLine("機械を起動します");
}
}
public class Robot : Machine
{
public void Run()
{
base.Start();
Console.WriteLine("ロボットが動作します");
}
}
public class Program
{
public static void Main()
{
Robot robot = new Robot();
robot.Run();
}
}
実行結果は次のとおりです。
機械を起動します
ロボットが動作します
Robot.Runの中で、親クラスのStartをbase.Start()として呼び出しています。
8-2. 親クラスのプロパティをbaseで参照するサンプル
C#using System;
public class User
{
protected string Role { get; set; } = "一般ユーザー";
}
public class Administrator : User
{
private new string Role { get; set; } = "管理者";
public void ShowRoles()
{
Console.WriteLine($"子クラスのRole: {this.Role}");
Console.WriteLine($"親クラスのRole: {base.Role}");
}
}
public class Program
{
public static void Main()
{
Administrator administrator = new Administrator();
administrator.ShowRoles();
}
}
実行結果は次のとおりです。
子クラスのRole: 管理者
親クラスのRole: 一般ユーザー
this.Roleとbase.Roleを使い分けることで、同名のプロパティを区別しています。
8-3. 親クラスのコンストラクタをbase()で呼び出すサンプル
C#using System;
public class Vehicle
{
public string Name { get; }
public Vehicle(string name)
{
Name = name;
Console.WriteLine($"Vehicleを初期化: {Name}");
}
}
public class Car : Vehicle
{
public int NumberOfDoors { get; }
public Car(string name, int numberOfDoors) : base(name)
{
NumberOfDoors = numberOfDoors;
Console.WriteLine($"Carを初期化: ドア数は{NumberOfDoors}");
}
}
public class Program
{
public static void Main()
{
Car car = new Car("サンプルカー", 4);
}
}
実行結果は次のようになります。
Vehicleを初期化: サンプルカー
Carを初期化: ドア数は4
まずVehicleのコンストラクタが実行され、その後にCarのコンストラクタ本体が実行されます。
8-4. overrideとbaseを組み合わせたサンプル
C#using System;
public class OrderService
{
public virtual void Process()
{
Console.WriteLine("注文データを登録します");
}
}
public class EmailOrderService : OrderService
{
public override void Process()
{
Console.WriteLine("注文処理を開始します");
base.Process();
Console.WriteLine("確認メールを送信します");
}
}
public class Program
{
public static void Main()
{
OrderService service = new EmailOrderService();
service.Process();
}
}
実行結果は次のとおりです。
注文処理を開始します
注文データを登録します
確認メールを送信します
変数の型はOrderServiceですが、実体はEmailOrderServiceなので、オーバーライドされたProcessが実行されます。その中のbase.Process()によって、親クラスの登録処理も実行されます。
8-5. 実行結果から処理の流れを確認する
コンストラクタ、オーバーライド、baseをまとめた例を見てみましょう。
C#using System;
public class BaseService
{
public BaseService()
{
Console.WriteLine("1. 親クラスのコンストラクタ");
}
public virtual void Execute()
{
Console.WriteLine("3. 親クラスのExecute");
}
}
public class CustomService : BaseService
{
public CustomService() : base()
{
Console.WriteLine("2. 子クラスのコンストラクタ");
}
public override void Execute()
{
Console.WriteLine("子クラスのExecute開始");
base.Execute();
Console.WriteLine("4. 子クラスのExecute終了");
}
}
public class Program
{
public static void Main()
{
CustomService service = new CustomService();
service.Execute();
}
}
実行結果は次のとおりです。
1. 親クラスのコンストラクタ
2. 子クラスのコンストラクタ
子クラスのExecute開始
3. 親クラスのExecute
4. 子クラスのExecute終了
処理の流れは次のようになります。
親クラスのコンストラクタが実行される
子クラスのコンストラクタが実行される
子クラスのオーバーライドメソッドが呼ばれる
base.Execute()で親クラスの処理が実行される子クラスの残りの処理が実行される
9. baseを使うときによくあるエラーと対処法
9-1. privateメンバーにアクセスしようとしてエラーになる
次のコードでは、親クラスのprivateフィールドへアクセスしようとしているため、コンパイルエラーになります。
C#public class Parent
{
private int value = 10;
}
public class Child : Parent
{
public void Show()
{
// エラー
// Console.WriteLine(base.value);
}
}
対処方法は、親クラスにアクセス可能なプロパティやメソッドを用意することです。
C#public class Parent
{
private int value = 10;
protected int Value
{
get { return value; }
}
}
public class Child : Parent
{
public void Show()
{
Console.WriteLine(base.Value);
}
}
privateを単純にprotectedへ変更するのではなく、必要な情報だけを公開する設計を検討します。
9-2. 親クラスに該当するコンストラクタがなくてエラーになる
: base(...)に渡した引数と一致するコンストラクタが親クラスにない場合、コンパイルエラーになります。
C#public class Parent
{
public Parent(int id)
{
}
}
public class Child : Parent
{
// stringを受け取る親コンストラクタがない
// public Child() : base("1")
// {
// }
}
正しい型の引数を渡します。
C#public class Child : Parent
{
public Child() : base(1)
{
}
}
次の点を確認すると原因を見つけやすくなります。
引数の数が一致しているか
引数の型が一致しているか
コンストラクタへアクセスできるか
引数なしコンストラクタが存在するか
親クラスで引数付きコンストラクタを1つでも明示的に定義すると、引数なしコンストラクタは自動生成されません。必要なら親クラス側に明示的に定義します。
9-3. staticメンバーにbaseでアクセスしようとしてエラーになる
baseは現在のインスタンスに含まれる親クラス部分を表すため、静的なコンテキストでは利用できません。
C#public class Parent
{
public static void Show()
{
Console.WriteLine("Parent.Show");
}
}
public class Child : Parent
{
public static void Execute()
{
// エラー
// base.Show();
}
}
静的メンバーはクラス名を使って呼び出します。
C#public static void Execute()
{
Parent.Show();
}
また、インスタンスメソッドの中であっても、静的メンバーをbase経由で呼び出すのではなく、宣言元のクラス名で参照する方が明確です。
C#Parent.Show();
9-4. overrideできないメソッドでbaseを使おうとして混乱する
親クラスのメソッドがvirtualではない場合、子クラスでoverrideできません。
C#public class Parent
{
public void Execute()
{
Console.WriteLine("親クラス");
}
}
public class Child : Parent
{
// エラー
// public override void Execute()
// {
// }
}
ただし、base.Execute()で親クラスのメソッドを呼び出すこと自体は可能です。
C#public class Child : Parent
{
public new void Execute()
{
base.Execute();
Console.WriteLine("子クラス");
}
}
ここで重要なのは、baseを使えることと、overrideできることは別の問題だという点です。
baseで呼び出せる条件:親クラスのアクセス可能なインスタンスメンバーであるoverrideできる条件:親クラス側がvirtual、abstract、またはオーバーライド可能なoverrideとして定義されている
9-5. エラーを防ぐための確認ポイント
baseに関するエラーが発生した場合は、次の点を確認します。
対象のメンバーが親クラスに存在するか
メソッド名やプロパティ名のスペルが正しいか
引数の数と型が一致しているか
対象が
privateになっていないかインスタンスメンバーと静的メンバーを混同していないか
baseを静的メソッド内で使っていないか親クラスのコンストラクタへアクセスできるか
オーバーライド対象に
virtualなどが付いているか抽象メソッドの実装を
baseで呼ぼうとしていないか
また、baseを呼び出す位置にも注意が必要です。親クラスの処理より前に追加処理を行うのか、後に行うのかによって、結果が変わることがあります。
C#public override void Execute()
{
BeforeExecute();
base.Execute();
AfterExecute();
}
処理順序が重要な場合は、実行結果や親クラスの仕様を確認します。
10. C#のbaseキーワードを使うべきケース・使わなくてもよいケース
10-1. 親クラスの共通処理を再利用したい場合
親クラスに複数の子クラスで共通する処理がある場合、baseを使って再利用できます。
C#public class Report
{
public virtual void Generate()
{
Console.WriteLine("共通データを取得します");
}
}
public class SalesReport : Report
{
public override void Generate()
{
base.Generate();
Console.WriteLine("売上レポートを作成します");
}
}
共通処理を親クラスへ集約することで、重複コードを減らせます。
ただし、子クラスが親クラスの内部処理に強く依存しすぎると、親クラスの変更が多くの子クラスへ影響します。再利用する処理が本当に継承階層の共通責務なのかを確認することが大切です。
10-2. 子クラスで処理を追加しつつ親クラスの処理も残したい場合
親クラスの処理を完全に置き換えるのではなく、前後に処理を追加したい場合は、overrideとbaseの組み合わせが適しています。
C#public class FileProcessor
{
public virtual void Process()
{
Console.WriteLine("ファイルを処理します");
}
}
public class LoggedFileProcessor : FileProcessor
{
public override void Process()
{
Console.WriteLine("処理開始");
base.Process();
Console.WriteLine("処理終了");
}
}
このパターンは、ログ、検証、計測、通知などの処理を追加するときに使えます。
10-3. 親クラスの初期化処理を確実に実行したい場合
親クラスがオブジェクトの成立に必要な値を受け取る場合は、: base(...)で初期化します。
C#public class Entity
{
public Guid Id { get; }
public Entity(Guid id)
{
if (id == Guid.Empty)
{
throw new ArgumentException("IDを指定してください", nameof(id));
}
Id = id;
}
}
public class Customer : Entity
{
public string Name { get; }
public Customer(Guid id, string name) : base(id)
{
Name = name;
}
}
親クラスのコンストラクタを通すことで、Entityとして必要な条件を確実に満たせます。
10-4. 無理にbaseを使わない方がよいケース
次のような場合は、baseを無理に使う必要はありません。
親クラスの処理が子クラスの目的に合わず、完全に置き換える設計になっている場合は、オーバーライド内でbaseを呼ぶ必要はありません。
また、同名メンバーがなく、親クラスのメンバーであることを強調する必要もない場合は、baseを省略できます。
C#public class Parent
{
protected void Validate()
{
}
}
public class Child : Parent
{
public void Execute()
{
Validate();
}
}
このコードでValidate()がどこから来たのか十分に明確なら、base.Validate()と書かなくても問題ありません。
さらに、親クラスの内部実装に依存してbase呼び出しを多用すると、継承階層が複雑になりやすいため注意が必要です。
10-5. 継承よりコンポジションを検討すべきケース
継承は強力ですが、クラス同士の結合が強くなります。単に別クラスの機能を使いたいだけなら、継承ではなくコンポジションを検討します。
コンポジションとは、必要な機能を持つオブジェクトをフィールドやプロパティとして保持する設計です。
C#public class Logger
{
public void Log(string message)
{
Console.WriteLine(message);
}
}
public class OrderService
{
private readonly Logger logger;
public OrderService(Logger logger)
{
this.logger = logger;
}
public void Process()
{
logger.Log("注文処理を開始します");
}
}
OrderServiceはLoggerを継承せず、必要な機能として利用しています。
次のような場合は、継承よりコンポジションが適している可能性があります。
「AはBの一種」と自然に表現できない
親クラスの一部の機能だけを利用したい
実行時に利用する処理を切り替えたい
多数の子クラスが親クラスの変更に影響される
base呼び出しの順序に強く依存している継承階層が深く、処理の追跡が難しい
baseが必要になる設計そのものが悪いわけではありませんが、継承関係が本当に適切かを定期的に見直すことが重要です。
11. C#のbaseキーワードに関するよくある質問
11-1. baseは必ず書く必要がある?
親クラスのアクセス可能なメンバーは、名前の競合がなければbaseを付けずに呼び出せます。
C#public class Parent
{
protected void Show()
{
Console.WriteLine("親クラス");
}
}
public class Child : Parent
{
public void Execute()
{
Show();
}
}
このShow()は、親クラスから継承したメソッドを呼び出します。
一方、親クラスと子クラスに同名メンバーがあり、親クラス側を指定したい場合はbaseが必要です。
C#base.Show();
コンストラクタについては、親クラスにアクセス可能な引数なしコンストラクタがあれば: base()を省略できます。引数付きコンストラクタを呼ぶ必要がある場合は省略できません。
11-2. base()とbase.メソッド名()は何が違う?
: base(...)は、親クラスのコンストラクタを呼び出します。
C#public Child(string name) : base(name)
{
}
base.メソッド名()は、親クラスのインスタンスメソッドを呼び出します。
C#base.Execute();
両者の違いは次のとおりです。
| 書き方 | 呼び出す対象 | 使用場所 |
|---|---|---|
: base(...) | 親クラスのコンストラクタ | 子クラスのコンストラクタ宣言 |
base.Method() | 親クラスのメソッド | インスタンスメソッドやアクセサーなど |
base.Property | 親クラスのプロパティ | インスタンスメンバー内 |
base()をコンストラクタ本体に書いたり、base.Method()を静的メソッド内に書いたりすることはできません。
11-3. baseで祖父クラスのメソッドを直接呼べる?
baseが直接表すのは、現在のクラスの直接の親クラスです。特定の祖父クラスを指定して、その実装を直接呼び出す構文はありません。
C#public class GrandParent
{
public virtual void Show()
{
Console.WriteLine("祖父クラス");
}
}
public class Parent : GrandParent
{
public override void Show()
{
Console.WriteLine("親クラス");
}
}
public class Child : Parent
{
public override void Show()
{
base.Show();
}
}
Childのbase.Show()が呼び出すのは、直接の親であるParent.Show()です。GrandParent.Show()を飛び越えて直接呼ぶことはできません。
ただし、直接の親クラスが対象メソッドをオーバーライドしていない場合、メンバー検索の結果として祖父クラスに定義された実装が呼ばれることはあります。
祖父クラスの処理を再利用する必要があるなら、親クラス側に呼び出し用のメソッドを用意するなど、継承設計を見直します。
11-4. staticメソッドでbaseは使える?
静的メソッド内ではbaseを使用できません。
C#public class Child : Parent
{
public static void Execute()
{
// コンパイルエラー
// base.Show();
}
}
baseはインスタンスに含まれる親クラス部分を参照するキーワードです。静的メソッドには特定のインスタンスがないため使用できません。
静的メンバーを呼び出す場合は、クラス名を指定します。
C#Parent.Show();
11-5. interfaceでbaseは使える?
インターフェースを実装しただけでは、そのインターフェースのメンバーをbaseで呼び出すことはできません。
C#public interface IWorker
{
void Work();
}
public class Worker : IWorker
{
public void Work()
{
// IWorkerの実装をbase.Work()として呼ぶことはできない
Console.WriteLine("作業します");
}
}
baseは基本的に、クラスの継承関係における親クラスを参照します。インターフェースは実装すべき契約を表すものであり、通常の親クラスと同じ扱いではありません。
C#のインターフェースには既定の実装を持たせられる場合もありますが、クラスの基底メソッドと同じようにbase.メソッド名()で呼び出すものではありません。インターフェースの既定実装を前提に複雑な処理を組み立てる場合は、クラス継承との違いや呼び出し規則を個別に確認する必要があります。
11-6. UnityのC#でもbaseは同じように使える?
Unityで使用するC#でも、baseの基本的な意味と構文は同じです。
たとえば、独自の親クラスを作り、子クラスで処理を拡張できます。
C#using UnityEngine;
public class BaseEnemy : MonoBehaviour
{
protected virtual void Initialize()
{
Debug.Log("敵の共通初期化");
}
}
public class BossEnemy : BaseEnemy
{
protected override void Initialize()
{
base.Initialize();
Debug.Log("ボス固有の初期化");
}
}
ただし、UnityのStart、Awake、Updateなどのイベント関数は、常にMonoBehaviour上のvirtualメソッドとして定義されているわけではありません。そのため、単純に次のようなコードを書けるとは限りません。
C#// 親クラスにアクセス可能なStartが定義されていなければ使えない
// base.Start();
継承したUnityスクリプトでbase.Start()を使いたい場合は、独自の親クラス側でStartをprotected virtualなどとして定義します。
C#using UnityEngine;
public class BaseCharacter : MonoBehaviour
{
protected virtual void Start()
{
Debug.Log("キャラクターの共通初期化");
}
}
public class PlayerCharacter : BaseCharacter
{
protected override void Start()
{
base.Start();
Debug.Log("プレイヤー固有の初期化");
}
}
Unityでも、親クラスのメソッドが実際に存在するか、アクセス可能か、オーバーライド可能かを確認することが重要です。
まとめ
C#のbaseは、子クラスから親クラスのメンバーやコンストラクタを参照するためのキーワードです。
親クラスのメソッドを呼び出す場合は、次のように記述します。
C#base.MethodName();
親クラスのプロパティやフィールドを参照する場合は、次の形式です。
C#base.PropertyName
親クラスのコンストラクタを呼び出す場合は、子クラスのコンストラクタ宣言に: base(...)を記述します。
C#public Child(string name) : base(name)
{
}
virtualは親クラスのメンバーを上書き可能にし、overrideは子クラスでそのメンバーを上書きします。baseを組み合わせると、親クラスの処理を残しながら子クラス独自の処理を追加できます。
C#public override void Execute()
{
base.Execute();
Console.WriteLine("子クラス独自の処理");
}
ただし、baseを使ってもprivateメンバーにはアクセスできず、静的メソッド内でも使用できません。また、抽象メソッドには実装本体がないため、その抽象実装をbaseで呼び出すこともできません。
baseを使うときは、次の点を意識するとよいでしょう。
親クラスの共通処理を残す必要があるか
親クラスのメンバーへアクセスできるか
コンストラクタの引数が一致しているか
overrideとメンバー隠蔽を混同していないか親クラスの処理を呼び出す順序が適切か
継承ではなくコンポジションの方が適していないか
base、this、virtual、overrideの役割をそれぞれ理解すると、C#の継承を使ったコードを正しく読み書きできるようになります。

