1. はじめに
今日のテーマはちょっと抽象的に感じるかもしれないけど、これがないと先に進めないんだ。今回は継承について話すよ。今日は軽く触れるだけだけど、今後もっと詳しくやるからね。
例えば、動物園の動物管理プログラムを書いているとしよう。いろんな種類の動物がいるよね:ライオン、トラ、ゾウ、オウム。みんな「動物」だけど、それぞれ特徴があるんだ:
- ライオンにはたてがみがある。
- トラはしま模様。
- ゾウはとても大きくて鼻が長い。
- オウムはしゃべれる。
もしそれぞれの動物を個別に書いたら、コードがすごく似てしまうよね:
// ライオン
public class Lion
{
public string Name { get; set; }
public int Age { get; set; }
public string Species { get; set; } = "ライオン"; // いつも「ライオン」
public void Eat() { Console.WriteLine("ライオンは肉を食べる。"); }
public void Sleep() { Console.WriteLine("ライオンは寝ている。"); }
public void Roar() { Console.WriteLine("ライオンが吠える!"); }
public string ManeColor { get; set; } // ライオンの特徴 - たてがみ
}
// トラ
public class Tiger
{
public string Name { get; set; }
public int Age { get; set; }
public string Species { get; set; } = "トラ"; // いつも「トラ」
public void Eat() { Console.WriteLine("トラは肉を食べる。"); }
public void Sleep() { Console.WriteLine("トラは寝ている。"); }
public void Stride() { Console.WriteLine("トラが忍び足で歩く。"); }
public string StripePattern { get; set; } // トラの特徴 - しま模様
}
ほら、コードがめっちゃ重複してるでしょ?Name、Age、Eat()、Sleep() ― これは全部の動物に共通だよ!もし動物の種類が100個あったら?地獄だよね!
そこで継承の出番だ!
2. C#のクラス継承って何?
継承はオブジェクト指向プログラミング(OOP)の基本原則の一つだよ。既存のクラスを元に新しいクラスを作れるんだ。新しいクラス(子クラス、派生クラス)は、親クラス(基底クラス、親クラス)のすべてのプロパティ(フィールド)や動作(メソッド)を受け継ぐ(継承する)ことができる。
現実世界でもそうだよね:親から何か特徴を受け継ぐけど、自分だけの特徴もある。
C#では、子クラス名の後にコロン:を書いて継承を表すよ:
// 親(基底)クラス - Animal
public class Animal
{
public string Name { get; set; } // 動物の名前
public int Age { get; set; } // 動物の年齢
public void Eat()
{
Console.WriteLine($"{Name}が食べている。");
}
public void Sleep()
{
Console.WriteLine($"{Name}が寝ている。");
}
}
// 子(派生)クラス - LionはAnimalを継承
public class Lion : Animal // <-- これが継承!
{
public string ManeColor { get; set; } // ライオンだけのプロパティ
public void Roar() // ライオンだけの動作
{
Console.WriteLine($"{Name}が吠える: ガオーー!");
}
}
// もう一つの子クラス - TigerはAnimalを継承
public class Tiger : Animal
{
public string StripePattern { get; set; } // トラだけのプロパティ
public void Stride() // トラだけの動作
{
Console.WriteLine($"{Name}が静かに忍び足で歩く。");
}
}
これでLionやTigerのオブジェクトを作ったら、自動的にNameやAge、Eat()やSleep()メソッドも持ってることになる。全部Animalから継承したからね。
class Program
{
static void Main(string[] args)
{
Lion simba = new Lion();
simba.Name = "シンバ";
simba.Age = 5;
simba.ManeColor = "ゴールド";
simba.Eat(); // Animalから継承したメソッド
simba.Sleep(); // Animalから継承したメソッド
simba.Roar(); // Lion独自のメソッド
Console.WriteLine($"{simba.Name} - 年齢: {simba.Age}, たてがみの色: {simba.ManeColor}");
Tiger shereKhan = new Tiger();
shereKhan.Name = "シェアカーン";
shereKhan.Age = 7;
shereKhan.StripePattern = "クラシック";
shereKhan.Eat(); // Animalから継承したメソッド
shereKhan.Sleep(); // Animalから継承したメソッド
shereKhan.Stride(); // Tiger独自のメソッド
Console.WriteLine($"{shereKhan.Name} - 年齢: {shereKhan.Age}, 模様: {shereKhan.StripePattern}");
}
}
継承のキーポイント:
- コードの再利用(Code Reusability): 共通のロジックを基底クラスにまとめて、重複を避けられる。
- 階層構造(Hierarchy): 「一般→個別」の論理構造ができる(Animal → Lion/Tiger)。
- ポリモーフィズム(Polymorphism):(今は名前だけ、詳しくは後で)基底クラスの参照で子クラスのオブジェクトを扱える。例えばAnimalのリストにライオンやトラを入れて、Eat()を呼ぶとか。
3. 基底クラスのコンストラクタ呼び出し(base)
子クラスのオブジェクト(例:new Lion())を作ると、C#は自動的に基底クラス(ここではAnimal)のコンストラクタを先に呼ぶんだ。これで基底部分がちゃんと初期化される。
ときどき、基底クラスのコンストラクタにパラメータを渡したいこともある。そのときは子クラスのコンストラクタの後ろにbaseを使うよ。
public class Animal
{
public string Name { get; set; }
public int Age { get; set; }
// 基底クラスのコンストラクタ
public Animal(string name, int age)
{
Name = name;
Age = age;
Console.WriteLine($"Animalのコンストラクタ: 動物{name}、年齢{age}が作成された。");
}
public void Eat()
{
Console.WriteLine($"{Name}が食べている。");
}
public void Sleep()
{
Console.WriteLine($"{Name}が寝ている。");
}
}
public class Lion : Animal
{
public string ManeColor { get; set; }
// LionのコンストラクタでAnimalのコンストラクタを呼ぶ
public Lion(string name, int age, string maneColor) : base(name, age) // <-- これが'base'!
{
ManeColor = maneColor;
Console.WriteLine($"Lionのコンストラクタ: たてがみの色は{ManeColor}。");
}
public void Roar()
{
Console.WriteLine($"{Name}が吠える: ガオーー!");
}
}
class Program
{
static void Main(string[] args)
{
// Lionを作ると、まずAnimalのコンストラクタ、次にLionのコンストラクタが呼ばれる
Lion simba = new Lion("シンバ", 5, "ゴールド");
simba.Roar();
}
}
new Lion("シンバ", 5, "ゴールド")を実行するとどうなる?
- Lion(string name, int age, string maneColor)のコンストラクタが呼ばれる。
- : base(name, age)のおかげで、Animal(string name, int age)のコンストラクタに制御が渡る。
- Animalのコンストラクタのコードが実行されて、Animalのコンストラクタ: 動物シンバ、年齢5が作成された。と表示される。
- その後、Lionのコンストラクタに戻る。
- Lionのコンストラクタのコードが実行されて、Lionのコンストラクタ: たてがみの色はゴールド。と表示される。
これ大事!基底クラスは必ず最初に初期化される!
4. isとas演算子
C#でプログラムを書いていると、オブジェクトの型をチェックしたり、別の型に変換したい場面がよくある。そんなときに便利なのがisとas演算子。特に継承やポリモーフィズムでよく使うけど、今から基本を押さえておこう。
is演算子(型チェック)
is演算子は、オブジェクトが特定の型と互換性があるかどうかを調べるもの。変換できるならtrue、できなければfalseを返すよ。
構文: 式 is 型
static void AnalyzeObject(object obj)
{
if (obj is string)
{
Console.WriteLine("これは文字列だよ!");
}
else if (obj is int)
{
Console.WriteLine("これは整数だよ!");
}
else
{
Console.WriteLine("これは他の何かだよ。");
}
}
static void Main()
{
AnalyzeObject("こんにちは、世界!"); // これは文字列だよ!
AnalyzeObject(123); // これは整数だよ!
AnalyzeObject(3.14); // これは他の何かだよ。
AnalyzeObject(new int[] { 1, 2 }); // これは他の何かだよ。
}
isとパターンマッチング:
最近のC#では、isで型チェックしつつ、その型の変数も同時に作れるんだ。これを「パターンマッチング」と呼んで、コードがすごくスッキリするよ。
static void AnalyzeObjectWithPatternMatching(object obj)
{
if (obj is string message) // objがstringならmessageに入れる
{
Console.WriteLine($"これは文字列、長さは:{message.Length}");
}
else if (obj is int number) // objがintならnumberに入れる
{
Console.WriteLine($"これは数字、2倍すると:{number * 2}");
}
else
{
Console.WriteLine("型が分からない。");
}
}
static void Main()
{
AnalyzeObjectWithPatternMatching("サンプル"); // これは文字列、長さは:4
AnalyzeObjectWithPatternMatching(50); // これは数字、2倍すると:100
AnalyzeObjectWithPatternMatching(new object()); // 型が分からない。
}
isとパターンマッチングは、従来の型変換+nullチェックよりずっと安全で読みやすいからおすすめ!
as演算子(安全な型変換)
as演算子は、オブジェクトを指定した型に変換しようとするもの。変換できればその型のオブジェクトを返すし、できなければnullを返す(例外は投げない)。だからasは「安全な」型変換なんだ。
構文: 式 as 型
static void ProcessData(object data)
{
string text = data as string; // dataをstringに変換してみる
if (text != null) // 変換できたかチェック
{
Console.WriteLine($"文字列を処理:{text.ToUpper()}");
}
else
{
Console.WriteLine("データは文字列じゃない。");
}
}
static void Main()
{
ProcessData("hello"); // 文字列を処理:HELLO
ProcessData(123); // データは文字列じゃない。
ProcessData(null); // データは文字列じゃない。
}
asと直接キャスト(型)式の使い分け:
- 安全性: オブジェクトが本当にその型か分からないときは、asを使えばInvalidCastExceptionを避けてnullで処理できる。
- 参照型だけ: asは参照型(クラス、インターフェース、デリゲート、配列)だけで使える。値型(例えばintからdoubleなど)には使えない。
注意: オブジェクトが絶対にその型だと分かっている場合や、値型を変換したい場合は、直接キャスト(型)式を使おう。もし変換できなければ、直接キャストはエラー(InvalidCastException)になる。これは型が違うのがプログラムのバグの場合にはむしろ望ましい動作だよ。
GO TO FULL VERSION