C#: 相続
1. 継承とは何か
継承は、あるクラスが別のクラスのメンバーを再利用し、新しい機能を追加して拡張することを可能にする、オブジェクト指向プログラミングの中核となる仕組みです。C# は単一継承のみをサポートしており、つまり、クラスは直接の基底クラスを 1 つしか持つことができません。
2. 継承の構文
class DerivedClass : BaseClassを使用して継承関係を宣言します。派生クラスは、基底クラスの非プライベートなメンバーをすべて自動的に取得します。
▶ サンプル
class Animal
{
public string Name { get; set; }
public void Eat()
{
Console.WriteLine($"{Name} is eating.");
}
}
class Dog : Animal
{
public void Bark()
{
Console.WriteLine($"{Name}: Woof!");
}
}
class Program
{
static void Main()
{
Dog dog = new Dog { Name = "Buddy" };
dog.Eat();
dog.Bark();
}
}
Buddy is eating.
Buddy: Woof!
3. 「base」キーワード
baseは、基底クラスのコンストラクタやメンバメソッドを呼び出すために使用されます。コンストラクタ内では、base(args)を介して親クラスのコンストラクタに引数を渡し、メソッド内では、base.Method()を介して親クラスの実装を呼び出します。
▶ サンプル
class Person
{
public string Name { get; }
public Person(string name)
{
Name = name;
}
public virtual void Introduce()
{
Console.WriteLine($"I am {Name}.");
}
}
class Student : Person
{
public int Grade { get; }
public Student(string name, int grade) : base(name)
{
Grade = grade;
}
public override void Introduce()
{
base.Introduce();
Console.WriteLine($"I am in grade {Grade}.");
}
}
class Program
{
static void Main()
{
Student s = new Student("Alice", 3);
s.Introduce();
}
}
I am Alice.
I am in grade 3.
4. virtual/override によるメソッドのオーバーライド
基底クラスでは、オーバーライド可能なメソッドにvirtualを付与し、派生クラスではoverrideを使用して新しい実装を提供します。これがポリモーフィズムの基礎となります。
▶ サンプル
class Shape
{
public virtual double Area()
{
return 0;
}
}
class Circle : Shape
{
public double Radius { get; set; }
public Circle(double radius)
{
Radius = radius;
}
public override double Area()
{
return Math.PI * Radius * Radius;
}
}
class Rectangle : Shape
{
public double Width { get; set; }
public double Height { get; set; }
public Rectangle(double w, double h)
{
Width = w;
Height = h;
}
public override double Area()
{
return Width * Height;
}
}
class Program
{
static void Main()
{
Shape[] shapes = { new Circle(2), new Rectangle(3, 4) };
foreach (Shape s in shapes)
{
Console.WriteLine($"Area: {s.Area():F2}");
}
}
}
Area: 12.57
Area: 12.00
5. new によるメソッドの隠蔽
派生クラスが、overrideを使用せずに、基底クラスのメソッドと同じ名前のメソッドを定義した場合、コンパイラは警告を出力します。newキーワードは、基底クラスのメソッドを明示的に非表示にし、これがオーバーライドではなく独立したメソッドであることを示します。
▶ サンプル
class Base
{
public void Greet()
{
Console.WriteLine("Hello from Base");
}
}
class Derived : Base
{
public new void Greet()
{
Console.WriteLine("Hello from Derived");
}
}
class Program
{
static void Main()
{
Derived d = new Derived();
d.Greet();
Base b = d;
b.Greet();
}
}
Hello from Derived
Hello from Base
newの隠蔽とoverrideのオーバーライドの違い:基底クラスの参照を通じて呼び出された場合、newは基底クラスのバージョンが実行されるのに対し、overrideは派生クラスのバージョンが実行される。
6. sealed:継承の防止
クラスにsealedを適用すると、そのクラスは継承できなくなります。メソッドに適用すると、そのメソッドはそれ以上オーバーライドできなくなります。
▶ サンプル
class Config
{
public virtual void Load()
{
Console.WriteLine("Loading config...");
}
}
class JsonConfig : Config
{
public sealed override void Load()
{
Console.WriteLine("Loading JSON config...");
}
}
sealed class FinalClass
{
public void DoWork() { }
}
class Program
{
static void Main()
{
JsonConfig cfg = new JsonConfig();
cfg.Load();
}
}
Loading JSON config...
JsonConfig.Loadがsealedにマークされると、JsonConfigを継承するクラスは、もはやLoadをオーバーライドできなくなります。 FinalClassがsealedにマークされると、いかなるクラスもそれを継承できなくなります。
7. 継承におけるコンストラクタの連鎖
派生クラスのオブジェクトが作成される際、基底クラスのコンストラクタは常に派生クラスのコンストラクタよりも先に実行されます。基底クラスにパラメータなしのコンストラクタがない場合、派生クラスではbase(args)を明示的に呼び出す必要があります。
▶ サンプル
class Vehicle
{
public string Type { get; }
public Vehicle(string type)
{
Type = type;
Console.WriteLine($"Vehicle({type}) constructed");
}
}
class Car : Vehicle
{
public string Brand { get; }
public Car(string brand) : base("Car")
{
Brand = brand;
Console.WriteLine($"Car({brand}) constructed");
}
}
class Program
{
static void Main()
{
Car car = new Car("Toyota");
}
}
Vehicle(Car) constructed
Car(Toyota) constructed
8. オブジェクトのルートクラス
C# のすべての型は、System.Object(別名object)を継承しています。この型は、ToString()、Equals()、GetHashCode()、およびGetType()といった基礎的なメソッドを提供しています。
▶ サンプル
class Point
{
public int X { get; }
public int Y { get; }
public Point(int x, int y)
{
X = x;
Y = y;
}
public override string ToString()
{
return $"({X}, {Y})";
}
public override bool Equals(object obj)
{
if (obj is Point other)
{
return X == other.X && Y == other.Y;
}
return false;
}
public override int GetHashCode()
{
return HashCode.Combine(X, Y);
}
}
class Program
{
static void Main()
{
Point p1 = new Point(1, 2);
Point p2 = new Point(1, 2);
Console.WriteLine(p1.ToString());
Console.WriteLine(p1.Equals(p2));
Console.WriteLine(p1.GetType().Name);
}
}
(1, 2)
True
Point
9. is および as 演算子
isは型チェックを行い、boolを返します。一方、asは安全な型変換を行い、失敗した場合は例外をスローするのではなく、nullを返します。
▶ サンプル
class Animal { }
class Cat : Animal
{
public void Meow() { Console.WriteLine("Meow!"); }
}
class Dog : Animal
{
public void Bark() { Console.WriteLine("Woof!"); }
}
class Program
{
static void Main()
{
Animal a = new Cat();
if (a is Cat)
{
Console.WriteLine("a is a Cat");
}
if (a is Dog)
{
Console.WriteLine("a is a Dog");
}
else
{
Console.WriteLine("a is not a Dog");
}
Cat c = a as Cat;
if (c != null)
{
c.Meow();
}
Dog d = a as Dog;
Console.WriteLine($"as Dog result is null: {d == null}");
}
}
a is a Cat
a is not a Dog
Meow!
as Dog result is null: True
is パターンマッチングがサポートされています。これにより、if (a is Cat cat) のように、型をチェックしながら変数を宣言することができます。
❓ よくある質問
newを追加することをお勧めします。📖 まとめ
- 継承には
class Derived : Baseという構文が使用されます。C#では単一継承のみがサポートされています。 baseというキーワードは、親クラスのコンストラクタおよびメンバメソッドを呼び出しますvirtual/overrideは、ポリモーフィズムの基礎となるメソッドのオーバーライドを実装していますnewというキーワードは、基底クラスのメソッドを明示的に非公開にし、オーバーライドとは異なる動作をしますsealedクラスに指定すると継承が阻止され、メソッドに指定するとそれ以上のオーバーライドが阻止される- コンストラクタは、基底クラスから派生クラスの順に実行される
- すべての型は
objectを継承しており、ToString、Equals、およびGetHashCodeをオーバーライドすることができます。 isは型チェックを行い、asは安全な変換を行います
📝 練習問題
Employee基底クラス(NameおよびSalaryプロパティを持つ)を定義し、Managerクラス(追加のLevelプロパティを持つ)を派生させ、Managerのコンストラクタ内でbaseを使用して親クラスのコンストラクタを呼び出す。Employee内でvirtual void Work()メソッドを定義し、Manager内でoverrideを用いてそれをオーバーライドし、オーバーライドされたメソッド内でbase.Work()を呼び出します。EmployeeとManagerの両方のオブジェクトを含むEmployee[]という配列を作成し、is演算子を使用して型を判定し、それに応じて異なる情報を出力する。Manager内でToString()メソッドをオーバーライドし、Name、Salary、およびLevelを含む文字列を返すようにします。sealedクラスをPayCalculatorとして宣言し、そこから継承するクラスを作成してみて、コンパイルエラーが発生するかどうかを確認してください。