C# オブジェクト指向とは?初心者がつまずくクラス・継承・ポリモーフィズムをわかりやすく解説
はじめに
C#を学び始めると、早い段階で「オブジェクト指向」という言葉に出会います。最初は、クラス、オブジェクト、インスタンス、継承、ポリモーフィズムなど、聞き慣れない用語が多く、難しく感じるかもしれません。
しかし、C#のオブジェクト指向は、プログラムを整理して、再利用しやすく、変更に強いコードを書くための重要な考え方です。特にC#はオブジェクト指向を前提として設計されているため、クラスやオブジェクトの理解は避けて通れません。
この記事では、「C# オブジェクト指向」を初めて学ぶ初心者向けに、クラス、オブジェクト、カプセル化、継承、ポリモーフィズム、インターフェースの基本を、コード例を交えながらわかりやすく解説します。
1. C#のオブジェクト指向とは?
1-1. オブジェクト指向の基本概念
オブジェクト指向とは、プログラムを「もの」と「その働き」に分けて考えるプログラミングの考え方です。
たとえば、現実世界の「車」を考えると、車には次のような情報や動作があります。
色
速度
メーカー名
走る
止まる
曲がる
オブジェクト指向では、このような情報を「データ」、動作を「処理」として、1つのまとまりにします。C#では、このまとまりを表現するために「クラス」を使います。
つまり、C#のオブジェクト指向では、プログラムを単なる命令の集まりとして書くのではなく、役割を持った部品として分けて作っていきます。
1-2. C#でオブジェクト指向を学ぶ理由
C#でオブジェクト指向を学ぶ理由は、C#の多くの機能がクラスを中心に作られているからです。
たとえば、C#でコンソールに文字を表示するときによく使う次のコードも、オブジェクト指向の考え方に基づいています。
C#Console.WriteLine("Hello, World!");
ここでは、ConsoleというクラスのWriteLineメソッドを使っています。つまり、C#では基本的なコードを書くときにも、クラスやメソッドの考え方が自然に登場します。
C#のオブジェクト指向を理解すると、次のようなメリットがあります。
コードの役割を整理しやすくなる
同じ処理を再利用しやすくなる
変更に強いプログラムを作りやすくなる
UnityやASP.NETなどの学習にもつながる
大規模な開発で読みやすいコードを書けるようになる
1-3. 手続き型プログラミングとの違い
手続き型プログラミングは、処理を上から順番に書いていく考え方です。小さなプログラムではわかりやすいですが、コードが増えてくると、どこに何の処理があるのかわかりにくくなることがあります。
たとえば、ユーザー情報を扱うプログラムで、名前、年齢、メールアドレス、登録処理、表示処理などがバラバラに書かれていると、管理が大変になります。
一方、オブジェクト指向では「ユーザー」というクラスを作り、その中にユーザーに関係するデータや処理をまとめます。
C#class User
{
public string Name { get; set; }
public int Age { get; set; }
public void ShowProfile()
{
Console.WriteLine($"{Name}さんは{Age}歳です。");
}
}
このように、関連するデータと処理を1つにまとめることで、コードの見通しがよくなります。
1-4. 初心者がオブジェクト指向でつまずきやすいポイント
初心者がC#のオブジェクト指向でつまずきやすい理由は、概念が抽象的だからです。
特につまずきやすいポイントは次のとおりです。
クラスとオブジェクトの違いがわからない
インスタンス化の意味が理解しづらい
privateやpublicの使い分けが難しい継承をいつ使えばよいかわからない
ポリモーフィズムの必要性がイメージしにくい
インターフェースと抽象クラスの違いが曖昧
これらは一度にすべて理解しようとすると混乱します。まずは「クラスは設計図」「オブジェクトは実体」という基本から順番に理解していきましょう。
2. C#のクラスとオブジェクトを理解しよう
2-1. クラスとは「設計図」のこと
C#のクラスとは、オブジェクトを作るための設計図です。
たとえば、「車」をプログラムで表現したい場合、車に必要な情報や動作をクラスとして定義します。
C#class Car
{
public string Color { get; set; }
public int Speed { get; set; }
public void Drive()
{
Console.WriteLine("車が走ります。");
}
}
このCarクラスは、まだ実際の車そのものではありません。あくまで「車とはこういう情報と動作を持つものです」と定義しているだけです。
設計図だけでは車が走らないように、クラスを定義しただけではプログラム上の実体は作られません。
2-2. オブジェクトとはクラスから作られる実体
オブジェクトとは、クラスという設計図から作られた実体です。
たとえば、Carクラスから「赤い車」や「青い車」を作ることができます。
C#Car car1 = new Car();
car1.Color = "赤";
car1.Speed = 60;
Car car2 = new Car();
car2.Color = "青";
car2.Speed = 80;
car1とcar2は、同じCarクラスから作られていますが、それぞれ別のデータを持つ別のオブジェクトです。
つまり、クラスは設計図、オブジェクトはその設計図から作られた実物と考えると理解しやすくなります。
2-3. フィールド・プロパティ・メソッドの違い
C#のクラスでは、主にフィールド、プロパティ、メソッドを使ってデータや処理を表現します。
フィールドは、クラスの中に直接定義する変数です。
C#private string name;
プロパティは、外部から値を取得したり設定したりするための仕組みです。
C#public string Name { get; set; }
メソッドは、処理をまとめたものです。
C#public void SayHello()
{
Console.WriteLine("こんにちは");
}
初心者のうちは、次のように覚えるとわかりやすいです。
フィールド:クラス内部で持つデータ
プロパティ:外部とデータをやり取りする窓口
メソッド:オブジェクトが行う処理
C#では、外部から直接フィールドにアクセスさせるよりも、プロパティを使って値を扱う書き方がよく使われます。
2-4. C#でクラスを定義する基本構文
C#でクラスを定義する基本構文は次のとおりです。
C#class クラス名
{
// フィールド
// プロパティ
// メソッド
}
具体例を見てみましょう。
C#class Person
{
public string Name { get; set; }
public int Age { get; set; }
public void Introduce()
{
Console.WriteLine($"私の名前は{Name}です。{Age}歳です。");
}
}
このPersonクラスには、NameとAgeというプロパティ、Introduceというメソッドがあります。
クラス名は、C#では一般的に先頭を大文字にするパスカルケースで書きます。
C#class Person
class Customer
class ProductItem
2-5. インスタンス化とは何か
インスタンス化とは、クラスからオブジェクトを作ることです。
C#では、newキーワードを使ってインスタンス化します。
C#Person person = new Person();
このコードでは、Personクラスをもとに、personというオブジェクトを作っています。
初心者が混乱しやすいのは、「クラス」と「インスタンス」の違いです。クラスは設計図であり、インスタンスはその設計図から作られた実体です。
C#Person person1 = new Person();
Person person2 = new Person();
person1とperson2は、同じPersonクラスから作られていますが、それぞれ別のインスタンスです。そのため、別々の名前や年齢を持つことができます。
2-6. クラスとオブジェクトの関係をコード例で確認
クラスとオブジェクトの関係をコードで確認してみましょう。
C#using System;
class Person
{
public string Name { get; set; }
public int Age { get; set; }
public void Introduce()
{
Console.WriteLine($"私の名前は{Name}です。{Age}歳です。");
}
}
class Program
{
static void Main()
{
Person person1 = new Person();
person1.Name = "田中";
person1.Age = 25;
Person person2 = new Person();
person2.Name = "佐藤";
person2.Age = 30;
person1.Introduce();
person2.Introduce();
}
}
実行結果は次のようになります。
私の名前は田中です。25歳です。
私の名前は佐藤です。30歳です。
同じPersonクラスから作ったオブジェクトでも、person1とperson2はそれぞれ別の値を持っています。これが、クラスとオブジェクトの基本的な関係です。
3. コンストラクタとthisの使い方
3-1. コンストラクタとは何か
コンストラクタとは、オブジェクトが作られるときに自動的に呼び出される特別なメソッドです。
通常のメソッドと違い、コンストラクタはクラス名と同じ名前で定義します。また、戻り値の型を書きません。
C#class Person
{
public Person()
{
Console.WriteLine("Personオブジェクトが作成されました。");
}
}
次のようにnewでオブジェクトを作ると、コンストラクタが自動で実行されます。
C#Person person = new Person();
コンストラクタは、オブジェクトを作るときに必要な初期設定を行うためによく使われます。
3-2. コンストラクタで初期値を設定する方法
コンストラクタを使うと、オブジェクト生成時にプロパティへ初期値を設定できます。
C#class Person
{
public string Name { get; set; }
public int Age { get; set; }
public Person(string name, int age)
{
Name = name;
Age = age;
}
public void Introduce()
{
Console.WriteLine($"{Name}です。{Age}歳です。");
}
}
このクラスは、オブジェクトを作るときに名前と年齢を渡す必要があります。
C#Person person = new Person("田中", 25);
person.Introduce();
実行結果は次のとおりです。
田中です。25歳です。
コンストラクタを使うことで、作成直後から正しい値を持ったオブジェクトを作ることができます。
3-3. thisキーワードの役割
thisは、現在のオブジェクト自身を表すキーワードです。
たとえば、次のコードを見てください。
C#class Person
{
private string name;
public Person(string name)
{
this.name = name;
}
}
ここでは、フィールドのnameと、コンストラクタの引数nameが同じ名前です。
C#this.name = name;
左側のthis.nameは「このオブジェクトが持っているフィールドのname」を意味します。右側のnameは「コンストラクタに渡された引数のname」を意味します。
3-4. 初心者が混乱しやすい変数名とthisの関係
初心者がよく混乱するのは、次のようなコードです。
C#class Person
{
private string name;
public Person(string name)
{
name = name;
}
}
一見すると正しく見えますが、このコードではフィールドに値を代入できていません。引数のnameに、引数のnameを代入しているだけです。
正しくは次のように書きます。
C#class Person
{
private string name;
public Person(string name)
{
this.name = name;
}
}
プロパティを使う場合も、必要に応じてthisを使えます。
C#class Person
{
public string Name { get; set; }
public Person(string name)
{
this.Name = name;
}
}
ただし、名前の衝突がない場合はthisを省略しても問題ありません。
C#Name = name;
thisは必ず毎回書く必要はありませんが、同じ名前の変数があるときに、オブジェクト自身のメンバーを明確にするために使うと覚えておきましょう。
4. カプセル化とは?データを安全に扱う考え方
4-1. カプセル化の目的
カプセル化とは、データを外部から直接変更できないようにし、決められた方法で安全に扱うための考え方です。
たとえば、年齢を表すAgeにマイナスの値が入ると不自然です。
C#person.Age = -10;
このような不正な値が入らないように、クラス内部で制御するのがカプセル化の目的です。
オブジェクト指向では、データをむき出しにするのではなく、必要な部分だけを外部に公開し、内部の実装は隠します。
4-2. private・public・protectedの違い
C#では、アクセス修飾子を使って、クラスのメンバーにどこからアクセスできるかを制御します。
代表的なアクセス修飾子は次の3つです。
C#public
private
protected
publicは、どこからでもアクセスできます。
C#public string Name { get; set; }
privateは、同じクラスの中からしかアクセスできません。
C#private int age;
protectedは、同じクラスと、そのクラスを継承した子クラスからアクセスできます。
C#protected string id;
初心者のうちは、次のように考えるとわかりやすいです。
public:外部に公開するprivate:クラス内部だけで使うprotected:自分と子クラスだけで使う
基本的には、フィールドはprivateにして、必要な操作だけをpublicなメソッドやプロパティで公開します。
4-3. プロパティを使って値を管理する方法
C#では、プロパティを使うことで、値の取得や設定をコントロールできます。
C#class Person
{
private int age;
public int Age
{
get
{
return age;
}
set
{
if (value >= 0)
{
age = value;
}
}
}
}
このコードでは、Ageに0以上の値だけを設定できるようにしています。
C#Person person = new Person();
person.Age = 25;
person.Age = -10;
Console.WriteLine(person.Age);
-10を設定しようとしても、条件を満たさないため、値は変更されません。
このように、プロパティを使うことで、外部からのアクセスを許可しながら、不正な値を防ぐことができます。
4-4. getter・setterの基本
プロパティのgetは値を取得するときに使われ、setは値を設定するときに使われます。
C#public string Name
{
get
{
return name;
}
set
{
name = value;
}
}
C#では、単純に値を取得・設定するだけなら、自動実装プロパティを使えます。
C#public string Name { get; set; }
読み取り専用にしたい場合は、setをprivateにすることもできます。
C#public string Name { get; private set; }
この場合、クラスの外からは値を取得できますが、外から変更することはできません。
C#class Person
{
public string Name { get; private set; }
public Person(string name)
{
Name = name;
}
}
データを守りながら必要な操作だけを許可することが、カプセル化の基本です。
4-5. カプセル化を使うメリット
カプセル化を使うメリットは、データを安全に管理できることです。
たとえば、商品の価格を扱うクラスで、価格にマイナスの値が入ると問題になります。そこで、プロパティのsetでチェックを入れることで、不正な値を防げます。
C#class Product
{
private int price;
public int Price
{
get
{
return price;
}
set
{
if (value >= 0)
{
price = value;
}
}
}
}
カプセル化には次のようなメリットがあります。
不正な値を防げる
クラス内部の実装を隠せる
変更の影響範囲を小さくできる
コードの保守性が上がる
外部からの使い方をわかりやすくできる
C#のオブジェクト指向では、カプセル化は非常に重要な考え方です。最初は「フィールドはprivate、外部とのやり取りはプロパティやメソッド」と覚えておくとよいでしょう。
5. 継承とは?既存クラスを再利用する仕組み
5-1. 継承の基本概念
継承とは、既存のクラスをもとにして、新しいクラスを作る仕組みです。
たとえば、「動物」という共通の特徴を持つクラスを作り、それをもとに「犬」や「猫」のクラスを作ることができます。
C#class Animal
{
public string Name { get; set; }
public void Eat()
{
Console.WriteLine("食べます。");
}
}
Animalクラスを継承すると、NameプロパティやEatメソッドを子クラスで再利用できます。
5-2. 親クラスと子クラスの関係
継承では、もとになるクラスを親クラス、継承して作られるクラスを子クラスと呼びます。
C#では、次のような関係になります。
C#class Dog : Animal
{
}
この場合、Animalが親クラス、Dogが子クラスです。
DogクラスはAnimalクラスを継承しているため、Animalに定義されているメンバーを使えます。
C#Dog dog = new Dog();
dog.Name = "ポチ";
dog.Eat();
継承は「AはBの一種である」という関係のときに使います。たとえば、犬は動物の一種なので、DogはAnimalを継承する関係として自然です。
5-3. C#で継承を書く基本構文
C#で継承を書く基本構文は次のとおりです。
C#class 子クラス名 : 親クラス名
{
}
具体例を見てみましょう。
C#using System;
class Animal
{
public string Name { get; set; }
public void Eat()
{
Console.WriteLine($"{Name}が食べます。");
}
}
class Dog : Animal
{
public void Bark()
{
Console.WriteLine($"{Name}が吠えます。");
}
}
class Program
{
static void Main()
{
Dog dog = new Dog();
dog.Name = "ポチ";
dog.Eat();
dog.Bark();
}
}
実行結果は次のようになります。
ポチが食べます。
ポチが吠えます。
DogクラスにはNameやEatを直接書いていませんが、Animalクラスを継承しているため使うことができます。
5-4. baseキーワードの使い方
baseキーワードは、親クラスのメンバーやコンストラクタを呼び出すときに使います。
たとえば、親クラスにコンストラクタがある場合、子クラスからbaseを使って呼び出せます。
C#class Animal
{
public string Name { get; set; }
public Animal(string name)
{
Name = name;
}
}
class Dog : Animal
{
public Dog(string name) : base(name)
{
}
public void Bark()
{
Console.WriteLine($"{Name}が吠えます。");
}
}
使い方は次のとおりです。
C#Dog dog = new Dog("ポチ");
dog.Bark();
また、親クラスのメソッドを子クラスから呼び出すこともできます。
C#class Animal
{
public void Sleep()
{
Console.WriteLine("眠ります。");
}
}
class Dog : Animal
{
public void Rest()
{
base.Sleep();
}
}
baseは、親クラスの機能を明示的に使いたいときに役立ちます。
5-5. 継承を使うメリットと注意点
継承を使うメリットは、共通する処理を親クラスにまとめられることです。
たとえば、犬、猫、鳥に共通するNameやEatをAnimalクラスにまとめれば、同じコードを何度も書かずに済みます。
継承のメリットは次のとおりです。
共通処理を再利用できる
コードの重複を減らせる
親クラスの型として子クラスを扱える
ポリモーフィズムを実現しやすい
一方で、継承には注意点もあります。継承を使いすぎると、親クラスの変更が子クラスに影響しやすくなります。また、継承階層が深くなると、コードの流れが追いにくくなります。
そのため、継承は「本当に親子関係として自然か」を考えて使うことが大切です。
5-6. 継承よりもコンポジションを検討すべきケース
継承は便利ですが、すべての再利用に使うべきではありません。
「AはBの一種である」という関係なら継承が向いています。一方で、「AはBを持っている」という関係なら、コンポジションを検討します。
たとえば、車はエンジンの一種ではありません。車はエンジンを持っています。
この場合、次のように継承ではなく、クラスの中に別のクラスを持たせるほうが自然です。
C#class Engine
{
public void Start()
{
Console.WriteLine("エンジンを始動します。");
}
}
class Car
{
private Engine engine = new Engine();
public void Drive()
{
engine.Start();
Console.WriteLine("車が走ります。");
}
}
これがコンポジションです。
初心者のうちは、次のように判断するとよいでしょう。
犬は動物の一種:継承
車はエンジンを持っている:コンポジション
注文は商品を持っている:コンポジション
社員は人の一種:継承を検討
継承は強い関係を作る仕組みなので、迷ったときはコンポジションを選ぶほうが柔軟な設計になりやすいです。
6. ポリモーフィズムとは?同じ命令で異なる動きを実現する考え方
6-1. ポリモーフィズムの基本概念
ポリモーフィズムとは、同じ命令で異なる動きを実現する仕組みです。日本語では「多態性」と呼ばれます。
たとえば、犬、猫、鳥はすべて動物ですが、鳴き方はそれぞれ違います。
犬:ワンワン
猫:ニャー
鳥:チュンチュン
プログラムでは、同じSpeakメソッドを呼び出しても、実際のオブジェクトの種類によって違う処理を実行できます。
これがポリモーフィズムです。
6-2. メソッドのオーバーライドとは
オーバーライドとは、親クラスで定義されたメソッドを、子クラスで上書きすることです。
たとえば、親クラスAnimalにSpeakメソッドを定義し、子クラスDogやCatでそれぞれ違う処理にします。
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("ニャー");
}
}
このように、親クラスのメソッドを子クラスで書き換えることで、同じメソッド名でも異なる動きを実現できます。
6-3. virtual・overrideの使い方
C#でメソッドをオーバーライドするには、親クラスのメソッドにvirtualを付けます。
C#public virtual void Speak()
{
Console.WriteLine("動物が鳴きます。");
}
子クラス側では、overrideを付けてメソッドを再定義します。
C#public override void Speak()
{
Console.WriteLine("ワンワン");
}
全体のコードは次のようになります。
C#using System;
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("ニャー");
}
}
class Program
{
static void Main()
{
Animal animal1 = new Dog();
Animal animal2 = new Cat();
animal1.Speak();
animal2.Speak();
}
}
実行結果は次のとおりです。
ワンワン
ニャー
変数の型はどちらもAnimalですが、実際に入っているオブジェクトがDogなら犬の処理、Catなら猫の処理が実行されます。
6-4. 抽象クラスとabstractメソッド
抽象クラスとは、直接インスタンス化せず、子クラスに共通する機能やルールを定義するためのクラスです。
C#では、abstractキーワードを使います。
C#abstract class Animal
{
public abstract void Speak();
}
abstractメソッドは、処理の中身を持たないメソッドです。子クラスで必ず実装する必要があります。
C#class Dog : Animal
{
public override void Speak()
{
Console.WriteLine("ワンワン");
}
}
抽象クラスは、共通の性質を持つクラスに対して「このメソッドは必ず実装してください」というルールを作りたいときに便利です。
C#abstract class Shape
{
public abstract double GetArea();
}
class Circle : Shape
{
public double Radius { get; set; }
public override double GetArea()
{
return Radius * Radius * Math.PI;
}
}
この例では、すべての図形は面積を計算できるべきだというルールをShapeクラスで定義しています。
6-5. インターフェースを使ったポリモーフィズム
ポリモーフィズムは、継承だけでなくインターフェースでも実現できます。
インターフェースは、「この機能を持っている」という約束を表すものです。
C#interface IPlayable
{
void Play();
}
このインターフェースを実装するクラスは、Playメソッドを持つ必要があります。
C#class Music : IPlayable
{
public void Play()
{
Console.WriteLine("音楽を再生します。");
}
}
class Movie : IPlayable
{
public void Play()
{
Console.WriteLine("動画を再生します。");
}
}
次のように、同じIPlayable型として扱えます。
C#IPlayable content1 = new Music();
IPlayable content2 = new Movie();
content1.Play();
content2.Play();
実行結果は次のとおりです。
音楽を再生します。
動画を再生します。
インターフェースを使うことで、継承関係がなくても、共通の操作を持つオブジェクトとして扱えます。
6-6. ポリモーフィズムをコード例で理解する
ポリモーフィズムのメリットは、処理を呼び出す側が具体的なクラスを意識しなくてよいことです。
次の例では、複数の動物をリストに入れて、同じSpeakメソッドを呼び出しています。
C#using System;
using System.Collections.Generic;
abstract class Animal
{
public abstract void Speak();
}
class Dog : Animal
{
public override void Speak()
{
Console.WriteLine("ワンワン");
}
}
class Cat : Animal
{
public override void Speak()
{
Console.WriteLine("ニャー");
}
}
class Bird : Animal
{
public override void Speak()
{
Console.WriteLine("チュンチュン");
}
}
class Program
{
static void Main()
{
List<Animal> animals = new List<Animal>
{
new Dog(),
new Cat(),
new Bird()
};
foreach (Animal animal in animals)
{
animal.Speak();
}
}
}
実行結果は次のようになります。
ワンワン
ニャー
チュンチュン
foreachの中では、すべてAnimalとして扱っています。しかし、実際にはDog、Cat、BirdそれぞれのSpeakが呼び出されています。
これが、C#のオブジェクト指向で重要なポリモーフィズムの基本です。
7. C#のインターフェースとは?
7-1. インターフェースの役割
インターフェースとは、クラスが持つべき機能のルールを定義するものです。
たとえば、「保存できる」という機能を表すインターフェースを作る場合、次のように書けます。
C#interface ISavable
{
void Save();
}
このインターフェースを実装するクラスは、必ずSaveメソッドを持つ必要があります。
インターフェースは、処理の中身よりも「何ができるか」を表すために使います。
7-2. クラスとの違い
クラスは、データや処理の実装を持つことができます。一方、インターフェースは、主に機能の約束を定義します。
クラスの例です。
C#class FileData
{
public string FileName { get; set; }
public void Save()
{
Console.WriteLine("ファイルを保存しました。");
}
}
インターフェースの例です。
C#interface ISavable
{
void Save();
}
初心者向けに違いを整理すると、次のようになります。
クラス:データと処理を持つ設計図
インターフェース:実装すべき機能の約束
クラス:インスタンス化できる
インターフェース:基本的に単体では実体を作らない
クラス:単一継承が基本
インターフェース:複数実装できる
C#では、1つのクラスが複数のインターフェースを実装できます。
7-3. インターフェースを実装する基本構文
C#でインターフェースを実装する基本構文は次のとおりです。
C#interface インターフェース名
{
void メソッド名();
}
class クラス名 : インターフェース名
{
public void メソッド名()
{
// 処理
}
}
具体例を見てみましょう。
C#using System;
interface IPrintable
{
void Print();
}
class Report : IPrintable
{
public void Print()
{
Console.WriteLine("レポートを印刷します。");
}
}
class Program
{
static void Main()
{
IPrintable printable = new Report();
printable.Print();
}
}
実行結果は次のとおりです。
レポートを印刷します。
インターフェース名は、C#では慣例的に先頭にIを付けることが多いです。
C#IPrintable
ISavable
IRunnable
7-4. 複数のクラスに共通の機能を持たせる方法
インターフェースを使うと、異なるクラスに共通の機能を持たせることができます。
C#interface INotifiable
{
void Send();
}
class EmailNotification : INotifiable
{
public void Send()
{
Console.WriteLine("メールを送信します。");
}
}
class SmsNotification : INotifiable
{
public void Send()
{
Console.WriteLine("SMSを送信します。");
}
}
class PushNotification : INotifiable
{
public void Send()
{
Console.WriteLine("プッシュ通知を送信します。");
}
}
これらのクラスは、すべてINotifiableとして扱えます。
C#List<INotifiable> notifications = new List<INotifiable>
{
new EmailNotification(),
new SmsNotification(),
new PushNotification()
};
foreach (INotifiable notification in notifications)
{
notification.Send();
}
このように、インターフェースを使うと、異なるクラスでも同じ操作をまとめて扱えるようになります。
7-5. 抽象クラスとインターフェースの使い分け
抽象クラスとインターフェースは似ていますが、使いどころが少し違います。
抽象クラスは、共通のデータや共通処理を持たせたいときに使います。
C#abstract class Animal
{
public string Name { get; set; }
public abstract void Speak();
}
インターフェースは、共通の機能やルールを定義したいときに使います。
C#interface IRunnable
{
void Run();
}
使い分けの目安は次のとおりです。
共通の状態や処理を持たせたい:抽象クラス
機能の約束だけを定義したい:インターフェース
複数の機能を組み合わせたい:インターフェース
親子関係を表したい:抽象クラス
「できること」を表したい:インターフェース
たとえば、「犬は動物である」は抽象クラスが向いています。一方、「犬は走ることができる」はインターフェースで表しやすいです。
8. C#オブジェクト指向の基本コード例
8-1. クラスを作成する
ここでは、C#のオブジェクト指向の基本をまとめたコード例を見ていきます。
まず、商品を表すProductクラスを作成します。
C#class Product
{
public string Name { get; set; }
public int Price { get; set; }
public Product(string name, int price)
{
Name = name;
Price = price;
}
public virtual void ShowInfo()
{
Console.WriteLine($"{Name}:{Price}円");
}
}
このクラスには、商品名と価格のプロパティ、初期値を設定するコンストラクタ、商品情報を表示するShowInfoメソッドがあります。
8-2. オブジェクトを生成する
次に、Productクラスからオブジェクトを生成します。
C#Product product = new Product("ノート", 200);
このコードでは、Productクラスをもとに、商品名が「ノート」、価格が「200円」のオブジェクトを作っています。
作成したオブジェクトのメソッドを呼び出すと、商品情報を表示できます。
C#product.ShowInfo();
8-3. メソッドを呼び出す
メソッドを呼び出すには、オブジェクト名の後にドットを付けてメソッド名を書きます。
C#product.ShowInfo();
C#のオブジェクト指向では、このように「オブジェクトに対して命令する」という形で処理を書くことが多くなります。
product.ShowInfo()は、「productというオブジェクトの商品情報を表示して」という意味です。
8-4. 継承を使って機能を拡張する
次に、Productクラスを継承して、割引商品を表すDiscountProductクラスを作ります。
C#class DiscountProduct : Product
{
public int DiscountRate { get; set; }
public DiscountProduct(string name, int price, int discountRate)
: base(name, price)
{
DiscountRate = discountRate;
}
public override void ShowInfo()
{
int discountedPrice = Price * (100 - DiscountRate) / 100;
Console.WriteLine($"{Name}:{discountedPrice}円({DiscountRate}%OFF)");
}
}
DiscountProductは、Productの機能を引き継ぎながら、割引率を追加しています。
また、ShowInfoメソッドをオーバーライドして、割引後の価格を表示するように変更しています。
8-5. ポリモーフィズムで処理を切り替える
Product型のリストに、通常の商品と割引商品をまとめて入れることができます。
C#List<Product> products = new List<Product>
{
new Product("ノート", 200),
new DiscountProduct("ペン", 100, 20),
new DiscountProduct("消しゴム", 150, 10)
};
foreach (Product product in products)
{
product.ShowInfo();
}
このコードでは、すべてProduct型として扱っていますが、実際のオブジェクトがDiscountProductの場合は、オーバーライドされたShowInfoが実行されます。
これがポリモーフィズムです。
8-6. サンプルコード全体の流れを解説
サンプルコード全体は次のとおりです。
C#using System;
using System.Collections.Generic;
class Product
{
public string Name { get; set; }
public int Price { get; set; }
public Product(string name, int price)
{
Name = name;
Price = price;
}
public virtual void ShowInfo()
{
Console.WriteLine($"{Name}:{Price}円");
}
}
class DiscountProduct : Product
{
public int DiscountRate { get; set; }
public DiscountProduct(string name, int price, int discountRate)
: base(name, price)
{
DiscountRate = discountRate;
}
public override void ShowInfo()
{
int discountedPrice = Price * (100 - DiscountRate) / 100;
Console.WriteLine($"{Name}:{discountedPrice}円({DiscountRate}%OFF)");
}
}
class Program
{
static void Main()
{
List<Product> products = new List<Product>
{
new Product("ノート", 200),
new DiscountProduct("ペン", 100, 20),
new DiscountProduct("消しゴム", 150, 10)
};
foreach (Product product in products)
{
product.ShowInfo();
}
}
}
実行結果は次のようになります。
ノート:200円
ペン:80円(20%OFF)
消しゴム:135円(10%OFF)
このコードには、C#のオブジェクト指向で重要な要素が含まれています。
クラス
オブジェクト
コンストラクタ
継承
オーバーライド
ポリモーフィズム
まずはこのような小さなコードを実際に書いて動かすことで、オブジェクト指向の理解が深まります。
9. 初心者がC#のオブジェクト指向でつまずく原因
9-1. クラスとオブジェクトの違いが曖昧
初心者が最初につまずきやすいのは、クラスとオブジェクトの違いです。
クラスは設計図、オブジェクトは設計図から作られた実体です。
C#class Person
{
public string Name { get; set; }
}
これは設計図です。
C#Person person = new Person();
これは実体を作る処理です。
クラスを作っただけでは、まだデータを持つ実体はありません。newでオブジェクトを作って初めて、値を持たせたりメソッドを呼び出したりできます。
9-2. インスタンス化の意味がわからない
インスタンス化とは、クラスからオブジェクトを作ることです。
C#Person person = new Person();
この1行では、次のことが行われています。
Personクラスをもとにするnew Person()でオブジェクトを作る作ったオブジェクトを
person変数に入れる
初心者のうちは、newが出てきたら「設計図から実物を作っている」と考えると理解しやすくなります。
9-3. 継承を使う場面が判断できない
継承は便利ですが、使う場面を間違えるとコードが複雑になります。
判断のポイントは、「AはBの一種である」と言えるかどうかです。
たとえば、次の関係は自然です。
犬は動物の一種である
猫は動物の一種である
社員は人の一種である
一方、次のような関係は継承に向いていません。
車はエンジンの一種である
注文は商品の一種である
会社は社員の一種である
このような場合は、継承ではなくコンポジションを使います。
継承は「共通化できるから使う」のではなく、「親子関係として自然だから使う」と考えることが大切です。
9-4. ポリモーフィズムが抽象的で理解しづらい
ポリモーフィズムは、言葉だけ見ると難しく感じます。しかし、考え方はシンプルです。
同じ命令を出しても、相手によって動きが変わる仕組みです。
C#Animal animal = new Dog();
animal.Speak();
このコードでは、変数の型はAnimalですが、実際の中身はDogです。そのため、DogのSpeakメソッドが実行されます。
ポリモーフィズムは、複数の種類のオブジェクトを同じ型として扱い、処理を共通化したいときに役立ちます。
9-5. コードを書く前に設計を考えすぎてしまう
オブジェクト指向を学び始めると、「正しい設計にしなければ」と考えすぎて、手が止まってしまうことがあります。
しかし、初心者の段階では、完璧な設計を目指す必要はありません。
まずは小さなクラスを作り、動くコードを書くことが大切です。
たとえば、次のような簡単なクラスから始めるとよいでしょう。
C#class Book
{
public string Title { get; set; }
public void Read()
{
Console.WriteLine($"{Title}を読みます。");
}
}
最初から大規模な設計を考えるよりも、実際に書いて、動かして、少しずつ改善するほうが理解しやすくなります。
10. C#のオブジェクト指向を効率よく学ぶ方法
10-1. 小さなクラスから作ってみる
C#のオブジェクト指向を効率よく学ぶには、小さなクラスから作ってみることが大切です。
いきなり複雑なシステムを作ろうとすると、クラス設計で悩みすぎてしまいます。
まずは、次のような身近なものをクラスにしてみましょう。
人
本
商品
車
銀行口座
ゲームキャラクター
たとえば、銀行口座を表すクラスは次のように作れます。
C#class BankAccount
{
public int Balance { get; private set; }
public void Deposit(int amount)
{
Balance += amount;
}
public void Withdraw(int amount)
{
if (amount <= Balance)
{
Balance -= amount;
}
}
}
このように、身近なものをコードに置き換えると、オブジェクト指向の感覚がつかみやすくなります。
10-2. 現実世界のものに置き換えて考える
オブジェクト指向は、現実世界のものに置き換えると理解しやすくなります。
たとえば、ゲームキャラクターを考えてみましょう。
キャラクターには、名前、HP、攻撃力などのデータがあります。また、攻撃する、ダメージを受ける、回復するなどの動作があります。
C#class Character
{
public string Name { get; set; }
public int Hp { get; set; }
public void Attack()
{
Console.WriteLine($"{Name}が攻撃しました。");
}
}
このように、「何のデータを持っているか」「何ができるか」を考えると、クラスの作り方が見えてきます。
10-3. サンプルコードを写経して動かす
C#のオブジェクト指向を理解するには、サンプルコードを読むだけでなく、実際に書いて動かすことが重要です。
コードを書き写すことを「写経」と呼ぶことがあります。写経には次のようなメリットがあります。
構文に慣れる
クラスの書き方を覚えられる
エラーの原因を学べる
実行結果を見て理解できる
特に、class、new、public、private、virtual、overrideなどは、何度も書くことで自然に覚えられます。
10-4. 継承・カプセル化・ポリモーフィズムを別々に練習する
オブジェクト指向には複数の重要な概念がありますが、一度にすべて理解しようとすると混乱します。
まずは、次のように分けて練習しましょう。
カプセル化の練習では、privateフィールドとプロパティを使って、不正な値を防ぐコードを書きます。
継承の練習では、親クラスと子クラスを作り、共通処理を再利用します。
ポリモーフィズムの練習では、virtualとoverrideを使って、同じメソッド名で異なる処理を実行します。
1つずつ練習すると、それぞれの役割が理解しやすくなります。
10-5. 簡単なアプリを作って理解を深める
基本を学んだら、簡単なアプリを作ってみましょう。
初心者におすすめの練習アプリは次のようなものです。
商品管理アプリ
本の管理アプリ
銀行口座シミュレーション
RPGのキャラクター管理
学生の成績管理
Todoリスト
たとえば、Todoリストなら、TodoItemクラスを作れます。
C#class TodoItem
{
public string Title { get; set; }
public bool IsDone { get; private set; }
public void Complete()
{
IsDone = true;
}
}
実際にアプリを作ると、「なぜクラスに分けるのか」「なぜカプセル化するのか」「どこで継承やインターフェースを使うのか」が少しずつ見えてきます。
11. C#オブジェクト指向に関するよくある質問
11-1. C#はオブジェクト指向言語ですか?
はい、C#はオブジェクト指向言語です。
C#では、クラス、オブジェクト、継承、カプセル化、ポリモーフィズム、インターフェースなど、オブジェクト指向を実現するための機能が豊富に用意されています。
また、C#はオブジェクト指向だけでなく、ジェネリック、LINQ、非同期処理、関数型プログラミングに近い書き方など、さまざまな機能も持っています。
ただし、C#をしっかり理解するうえで、オブジェクト指向の基礎は非常に重要です。
11-2. クラスと構造体の違いは何ですか?
C#のクラスと構造体は似ていますが、大きな違いがあります。
クラスは参照型、構造体は値型です。
簡単に言うと、クラスはオブジェクトの場所を参照し、構造体は値そのものを扱います。
C#class PersonClass
{
public string Name { get; set; }
}
struct PointStruct
{
public int X { get; set; }
public int Y { get; set; }
}
初心者のうちは、基本的にはクラスを中心に学べば問題ありません。構造体は、座標や色、日付のように、小さな値を表すときに使われることが多いです。
11-3. 継承とインターフェースはどちらを使うべきですか?
継承とインターフェースは目的が違います。
継承は、「AはBの一種である」という親子関係を表したいときに使います。
犬は動物の一種である
インターフェースは、「Aはこの機能を持っている」という機能の約束を表したいときに使います。
プリンターは印刷できる
PDFも印刷できる
レポートも印刷できる
迷ったときは、次のように考えるとよいでしょう。
共通の親として自然:継承
共通の機能を持たせたい:インターフェース
複数の機能を組み合わせたい:インターフェース
共通のフィールドや処理も持たせたい:抽象クラス
11-4. ポリモーフィズムは何のために使いますか?
ポリモーフィズムは、処理を共通化しながら、実際の動作をオブジェクトごとに変えたいときに使います。
たとえば、支払い処理を考えてみましょう。
C#interface IPayment
{
void Pay();
}
class CreditCardPayment : IPayment
{
public void Pay()
{
Console.WriteLine("クレジットカードで支払います。");
}
}
class CashPayment : IPayment
{
public void Pay()
{
Console.WriteLine("現金で支払います。");
}
}
呼び出す側は、具体的な支払い方法を細かく意識せず、Payを呼び出せます。
C#void ExecutePayment(IPayment payment)
{
payment.Pay();
}
このように、ポリモーフィズムを使うと、変更や拡張に強いコードを書きやすくなります。
11-5. 初心者はどこまで理解すればよいですか?
初心者の段階では、まず次の内容を理解できれば十分です。
クラスは設計図
オブジェクトはクラスから作られる実体
newでインスタンス化するプロパティでデータを持つ
メソッドで処理を書く
privateとpublicでアクセスを制御する継承で親クラスの機能を引き継ぐ
virtualとoverrideでメソッドを上書きするインターフェースで共通の機能を定義する
最初から完璧に理解する必要はありません。コードを書きながら、少しずつ感覚をつかんでいくことが大切です。
まとめ
C#のオブジェクト指向は、初心者にとって最初は難しく感じやすいテーマです。特に、クラス、オブジェクト、インスタンス化、継承、ポリモーフィズム、インターフェースといった用語が一度に出てくるため、混乱しやすいでしょう。
しかし、基本はとてもシンプルです。
クラスは設計図、オブジェクトは設計図から作られた実体です。カプセル化を使うと、データを安全に管理できます。継承を使うと、既存のクラスを再利用できます。ポリモーフィズムを使うと、同じ命令で異なる動きを実現できます。インターフェースを使うと、複数のクラスに共通の機能を持たせることができます。
C#のオブジェクト指向を理解するには、説明を読むだけでなく、実際にコードを書いて動かすことが大切です。まずは小さなクラスを作り、プロパティやメソッドを追加し、次に継承やポリモーフィズムを試してみましょう。
焦らず一つずつ学んでいけば、C#のオブジェクト指向は必ず理解できるようになります。

