C#: 相続

1. 継承とは何か

継承は、あるクラスが別のクラスのメンバーを再利用し、新しい機能を追加して拡張することを可能にする、オブジェクト指向プログラミングの中核となる仕組みです。C# は単一継承のみをサポートしており、つまり、クラスは直接の基底クラスを 1 つしか持つことができません。


2. 継承の構文

class DerivedClass : BaseClassを使用して継承関係を宣言します。派生クラスは、基底クラスの非プライベートなメンバーをすべて自動的に取得します。

▶ サンプル

CSHARP
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();
    }
}
▶ 試してみよう
TEXT 📖 参照専用
Buddy is eating.
Buddy: Woof!

3. 「base」キーワード

baseは、基底クラスのコンストラクタやメンバメソッドを呼び出すために使用されます。コンストラクタ内では、base(args)を介して親クラスのコンストラクタに引数を渡し、メソッド内では、base.Method()を介して親クラスの実装を呼び出します。

▶ サンプル

CSHARP
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();
    }
}
▶ 試してみよう
TEXT 📖 参照専用
I am Alice.
I am in grade 3.

4. virtual/override によるメソッドのオーバーライド

基底クラスでは、オーバーライド可能なメソッドにvirtualを付与し、派生クラスではoverrideを使用して新しい実装を提供します。これがポリモーフィズムの基礎となります。

▶ サンプル

CSHARP 📖 参照専用
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}");
        }
    }
}
論理コード 44 行(40 行制限超過、参照専用)
TEXT 📖 参照専用
Area: 12.57
Area: 12.00

5. new によるメソッドの隠蔽

派生クラスが、overrideを使用せずに、基底クラスのメソッドと同じ名前のメソッドを定義した場合、コンパイラは警告を出力します。newキーワードは、基底クラスのメソッドを明示的に非表示にし、これがオーバーライドではなく独立したメソッドであることを示します。

▶ サンプル

CSHARP
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();
    }
}
▶ 試してみよう
TEXT 📖 参照専用
Hello from Derived
Hello from Base
💡 newの隠蔽とoverrideのオーバーライドの違い:基底クラスの参照を通じて呼び出された場合、newは基底クラスのバージョンが実行されるのに対し、overrideは派生クラスのバージョンが実行される。


6. sealed:継承の防止

クラスにsealedを適用すると、そのクラスは継承できなくなります。メソッドに適用すると、そのメソッドはそれ以上オーバーライドできなくなります。

▶ サンプル

CSHARP
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();
    }
}
▶ 試してみよう
TEXT 📖 参照専用
Loading JSON config...
⚠️ JsonConfig.Loadsealedにマークされると、JsonConfigを継承するクラスは、もはやLoadをオーバーライドできなくなります。 FinalClasssealedにマークされると、いかなるクラスもそれを継承できなくなります。


7. 継承におけるコンストラクタの連鎖

派生クラスのオブジェクトが作成される際、基底クラスのコンストラクタは常に派生クラスのコンストラクタよりも先に実行されます。基底クラスにパラメータなしのコンストラクタがない場合、派生クラスではbase(args)を明示的に呼び出す必要があります。

▶ サンプル

CSHARP
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");
    }
}
▶ 試してみよう
TEXT 📖 参照専用
Vehicle(Car) constructed
Car(Toyota) constructed

8. オブジェクトのルートクラス

C# のすべての型は、System.Object(別名object)を継承しています。この型は、ToString()Equals()GetHashCode()、およびGetType()といった基礎的なメソッドを提供しています。

▶ サンプル

CSHARP
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);
    }
}
▶ 試してみよう
TEXT 📖 参照専用
(1, 2)
True
Point

9. is および as 演算子

isは型チェックを行い、boolを返します。一方、asは安全な型変換を行い、失敗した場合は例外をスローするのではなく、nullを返します。

▶ サンプル

CSHARP
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}");
    }
}
▶ 試してみよう
TEXT 📖 参照専用
a is a Cat
a is not a Dog
Meow!
as Dog result is null: True
💡 C# 7+ では is パターンマッチングがサポートされています。これにより、if (a is Cat cat) のように、型をチェックしながら変数を宣言することができます。

❓ よくある質問

Q C# は多重継承をサポートしていますか?
A いいえ。クラスは直接の基底クラスを 1 つしか持つことはできませんが、複数のインターフェースを実装することは可能です。
Q override を使わずに同じ名前のメソッドを定義するとどうなりますか?
A コンパイラから警告が出ます。そのメソッドは暗黙的に基底クラスのメソッドを隠してしまいます。これを明示的に宣言するために、newを追加することをお勧めします。
Q 静的メソッド内で「base」キーワードを使用できますか?
A いいえ。「base」も「this」もインスタンスを参照しますが、静的メソッドにはインスタンスのコンテキストが存在しないためです。
Q シールドクラスの実用的な意義は何ですか?
A クラスが継承されるのを防ぎ、その実装が変更されないようにするとともに、JITコンパイラがより多くの最適化を行えるようにします。
Q 「is」と「as」の違いは何ですか?
A 「is」は型チェックを行い、bool値を返します。「as」は型変換を試み、オブジェクトまたはnullを返します。「as」は値型では使用できません。

📖 まとめ

📝 練習問題

  1. Employee 基底クラス(Name および Salaryプロパティを持つ)を定義し、Managerクラス(追加のLevelプロパティを持つ)を派生させ、Managerのコンストラクタ内でbaseを使用して親クラスのコンストラクタを呼び出す。
  2. Employee内でvirtual void Work()メソッドを定義し、Manager内でoverrideを用いてそれをオーバーライドし、オーバーライドされたメソッド内でbase.Work()を呼び出します。
  3. EmployeeManagerの両方のオブジェクトを含むEmployee[]という配列を作成し、is演算子を使用して型を判定し、それに応じて異なる情報を出力する。
  4. Manager内でToString()メソッドをオーバーライドし、NameSalary、およびLevelを含む文字列を返すようにします。
  5. sealedクラスをPayCalculatorとして宣言し、そこから継承するクラスを作成してみて、コンパイルエラーが発生するかどうかを確認してください。
Web-Tutorial.com

Web-Tutorial 技術チーム

複数の開発者によって共同維持されているプログラミングチュートリアルプラットフォーム。各チュートリアルは専門分野の開発者が執筆・レビューしています。正確で信頼性の高いコンテンツを目指しています — 問題を見つけた場合はお知らせください。

100%