なぜ、インスタンスの理解が重要なのか、その理由
この記事では、当社の新人エンジニア研修の参考にC#を解説します。
前回は「メソッドを定義して処理を再利用する」について学びました。処理を部品として再利用できるようになりましたね。
今回はいよいよインスタンスの活用について解説します。データと手続きを一体化したインスタンスによって、よりプログラムの部品化が進むことになります。
これまで話を単純化するために「インスタンス」を避けてきましたが、C#でのオブジェクト指向の本質はクラスからインスタンスを生成し、各インスタンスがデータと振る舞いを持つところにあります。staticメソッドだけではなく、インスタンスを活用する方法をしっかり学びましょう。
1. オブジェクト指向とは何だったか?
1.オブジェクト指向とは何だったか?
Objectとは「モノ」のことでした。
例えば、スマートフォンを考えてみましょう。スマートフォンには、メーカー、OS、バッテリー容量などの属性(データ)があり、通話する、アプリを開くといった動作(メソッド)を持っています。このように、データと動作をセットにしたものがオブジェクトです。
この記事の1回目でお話ししたように、オブジェクト指向とは他の工業製品と同じように部品を組み合わせることでプログラム全体を作り上げるという考え方でした。
例えば、新人エンジニアの皆さんがお使いのパソコンも多くの部品から構成されています。私たちは、それぞれの部品の内部構造を知らなくても、それらを組み合わせて使うことができます。大多数の人は“CPU”と“メモリ”の区別も曖昧なままスマホやパソコンを使っています。それでもスマホとパソコンをつないでデータを交換したりインターネットを見たりすることはできます。また、それぞれの部品単位でアップデートすることもできます。例えば、古くなったハードディスクを新しいものに交換する、などといったことができます。
この考え方をプログラムに応用して、再利用性やメンテナンス性の高いプログラム部品でシステムを組むのがオブジェクト指向です。
つまり、オブジェクトはプログラムの部品です。そして、オブジェクトの設計図がクラスです。クラスには、オブジェクトに共通する属性(情報や機能)を定義します。この設計図をもとに作成した実際の部品がインスタンスです。
人によって言葉の使い方に多少のずれがありますが、この研修では、
オブジェクト(抽象概念) = クラス(設計図) + インスタンス(具体的なもの)
という言葉で使い分けています。
単にオブジェクトといった場合には、それがクラスのことを指すこともあれば、インスタンスのことを指すこともあります。ですから本書ではオブジェクトという言葉の使用は極力避けて、クラスとインスタンスという言葉を使うようにしています。
繰り返しになりますが、オブジェクト指向とは、プログラムを「部品の組み合わせ」として設計し、再利用性やメンテナンス性を高める考え方です。
- オブジェクト: データとその振る舞いをまとめたもの(抽象概念)。
- クラス: オブジェクトの設計図。
- インスタンス: クラスを元に実際に作成されたオブジェクト。
例えば、今現在あなたが見ているこの画面も、ウインドウというインスタンスの上にメニューというインスタンスが乗っていて、クリックの情報を受け取るインスタンスがある、という風になっているというと少しはイメージの助けになりますでしょうか?
論より証拠、以下のプログラムを実行していただくとお分かりただけると思います。
using System;
using System.Windows.Forms;
class Example00
{
[STAThread]
static void Main()
{
Form form = new Form();
form.Width = 1000;
form.Height = 500;
Application.Run(form);
}
}
例えばウインドウサイズを変更したいとき、どこをいじれば良いか分かりますか?
これは比喩的な表現ですが、パソコンクラスというものを作ったとして、あなたや隣の人の机に乗っている具体的なパソコンがインスタンスです。新入社員クラスがあったとして、あなたやあなたの隣の人がそのインスタンスです。
2. フィールド(インスタンス変数)を持ったクラスの定義
例えば新入社員の皆さんで下図の名簿を作ったとします。名簿の項目はクラスに当たります。そして一人一人の行はインスタンスです。
例:新人エンジニアクラス
話を簡単にするため、新人エンジニアクラスが「社員番号(id)」と「名前(name)」2つの情報だけを持つとします。C#のクラスであれば、以下のように定義できます。

namespace Chap09
{
class NewEngineer1
{
// フィールド(または「インスタンス変数」)
public int Id;
public String Name;
}
}
int Id;
は「社員番号」のインスタンス変数String Name;
は「名前」のインスタンス変数
この時点ではメインメソッドがないので、実行はできません。「NewEngineer1クラス」を使ってテストしたいなら、別にエントリーポイント(Main
メソッド)を用意する必要があります。
インスタンスの生成とフィールドの使用
次は、クラスを使うクラスを作って試しましょう。
namespace Chap09
{
public class Example01
{
public static void Main(string[] args)
{
NewEngineer1 se1 = new NewEngineer1();
Console.WriteLine(se1.Id);
Console.WriteLine(se1.Name);
}
}
}
NewEngineer1 se1;
- これは「
NewEngineer1
型の変数se1
を宣言」している。まだ何も入っていない。
- これは「
se1 = new NewEngineer1();
new
演算子で「NewEngineer1
クラスのインスタンス」をヒープ領域に生成し、その参照をse1
に代入。- コンストラクタ
NewEngineer1()
が暗黙的に呼ばれ、インスタンスを初期化。
Console.WriteLine(se1.Id);
- フィールド
Id
の値を出力。まだ値を設定していないので、C#では既定値0が出力されます。(int
の既定値が0)
- フィールド
Console.WriteLine(se1.Name);
- こちらは文字列型の既定値が
null
なので、何もない状態が出力されます。
- こちらは文字列型の既定値が
<実行結果>
0 (空行またはnull) |
C#でのフィールドの既定値は以下のように決まっています。
bool
→false
- 数値型 (
int
,long
,double
etc.) →0
(各型に応じた0) char
→'\0'
(Unicode NUL)- 参照型 (
string
, カスタムクラスなど) →null
フィールドに値を代入
namespace Chap09
{
public class Example02
{
public static void Main(string[] args)
{
NewEngineer1 se1 = new NewEngineer1();
se1.Id = 1;
se1.Name = "yamada";
Console.WriteLine(se1.Id);
Console.WriteLine(se1.Name);
}
}
}
<実行結果>
1 yamada |
ここで使っている Id
と Name
は、各インスタンスが固有に持つデータなので「インスタンス変数」とも呼びます。
3. メソッドを持ったクラスの定義
例:show()
メソッドを追加
同じ新人エンジニアクラスにメソッドを持たせることもできます。次の NewEngineer2
は、Show()
メソッドを定義して、一度にId
とName
を表示します。
namespace Chap09
{
class NewEngineer2
{
public int Id;
public string Name;
// インスタンスメソッド
public void Show()
{
Console.WriteLine($"私のIDは{Id}、名前は{Name}です。");
}
}
}
namespace Chap09
{
public class Example03
{
public static void Main()
{
NewEngineer2 se2 = new NewEngineer2();
se2.Id = 2;
se2.Name = "tabuchi";
se2.Show();
}
}
}
<実行結果>
私のIDは2、名前はtabuchiです。 |
Show()
はいわゆるインスタンスメソッド。呼び出し方は変数名.メソッド名()
。Show()
内ではId
やName
を直接参照できる(同じインスタンス内なので)。
4. インスタンスを複数生成
namespace Chap09
{
public class Example04
{
public static void Main()
{
NewEngineer2 yamazaki = new NewEngineer2();
yamazaki.Id = 1;
yamazaki.Name = "yamazaki";
yamazaki.Show(); // 私のIDは1、名前はyamazakiです。
NewEngineer2 imai = new NewEngineer2();
imai.Id = 2;
imai.Name = "imai";
imai.Show(); // 私のIDは2、名前はimaiです。
}
}
}
同じクラスNewEngineer2
から複数インスタンスをnew
で作って、各々のフィールドを設定できます。
ToString()
メソッド呼び出し時の表示
C#でインスタンス変数を Console.WriteLine(se2)
のように直接書くと、ToString()
が呼ばれて文字列が返されます。未オーバーライドのままだと Namespace.ClassName
やハッシュコードが出力されます。
Console.WriteLine(yamazaki);
// 例: Chap09.NewEngineer2
ToString()
をオーバーライドすれば、任意の文字列を返すことができます。が、ここではまだ説明しません(継承ポリモーフィズムで詳説)。
5. 参照型とプリミティブ型
C#でも変数には値型と参照型があります。
- 値型:
bool
,int
,double
など- 変数そのものがデータの実体を持つ
- 参照型:
string
, 配列 (T[]
), ユーザー定義クラス (class
)、object
など- 変数には実体(オブジェクト)へのポインタ/アドレス(参照)が入る
NewEngineer2 se2 = new NewEngineer2();
の場合、se2
には インスタンスそのものではなく「ヒープ上のインスタンスを指す参照」が格納されます。

1つのインスタンスを複数の変数で参照
NewEngineer2 se1 = new NewEngineer2();
se1.Id = 1;
se1.Name = "yamazaki";
NewEngineer2 se2 = se1; // 同じ参照をコピー
se2.Show();
se2
も同じオブジェクトを指すため、se2.Show()
の結果は se1.Show()
と全く同じ出力になります。

6. 参照型の配列
インスタンスを配列にまとめることもできます。
namespace Chap09
{
public class Example06
{
public static void Main()
{
NewEngineer2[] seArray = new NewEngineer2[3];
// インスタンスを生成して配列の各要素に代入
seArray[0] = new NewEngineer2();
seArray[1] = new NewEngineer2();
seArray[2] = new NewEngineer2();
seArray[0].Id = 1; seArray[0].Name = "tabuchi";
seArray[1].Id = 2; seArray[1].Name = "shinohara";
seArray[2].Id = 3; seArray[2].Name = "kokubun";
foreach (NewEngineer2 e in seArray)
{
e.Show();
}
}
}
}
<実行結果>
私のIDは1、名前はtabuchiです。 私のIDは2、名前はshinoharaです。 私のIDは3、名前はkokubunです。 |
配列の要素は最初null
値型の配列と異なり、「new NewEngineer2[3]
」 だけではインスタンスが生成されません。配列の各要素に参照を格納するため、seArray[i] = new NewEngineer2();
が必要です。
もしこれを忘れると、seArray[0]
は null
のままなので、後で seArray[0].Name = "tabuchi";
のようにアクセスすると NullReferenceException
が発生します。
7. NullReferenceException
C#で「存在しないオブジェクトへの参照」を使おうとすると、NullReferenceException
が発生します。
NewEngineer2[] seArray = new NewEngineer2[3];
// まだ要素はnull
seArray[0].Id = 1; // ← ここで NullReferenceException
エラー例:
Unhandled exception. System.NullReferenceException: Object reference not set to an instance of an object. |
seArray[0]
が null
なのに seArray[0].Id
とアクセスしようとしたためです。
null とは
null
は参照型変数が「どのオブジェクトも指していない」特別な状態null
と""
(空文字) は別物null
は0
や"null"
とも違う
string str = null;
Console.WriteLine(str == null); // True
Console.WriteLine(str == "null"); // False
Console.WriteLine(str == ""); // False
C#のガーベージコレクションにより、参照されなくなったオブジェクトは自動的にメモリ解放されます(いつ解放されるかはランタイム依存)。
8. 参照による値渡しと値渡し
C#では、引数の受け渡しはすべて“値渡し” ですが、参照型の場合「参照(アドレス)の値」がコピーされて渡るため、結果的に同じオブジェクトを共有します。
例: 参照(オブジェクト)を引数に渡す
static void Show(NewEngineer1 se)
{
Console.WriteLine($"{se.Id}:{se.Name}");
}
public static void Main()
{
NewEngineer1 yamazaki = new NewEngineer1 { Id = 1, Name = "yamazaki" };
Show(yamazaki);
}
Show(yamazaki)
で yamazaki
参照のコピーが渡り、Show
内のse
とyamazaki
が同じオブジェクトを指します。
メソッド内でオブジェクトを変更すると呼び出し元にも反映
static void Modify(NewEngineer1 se)
{
se.Name += "_san";
}
public static void Main()
{
NewEngineer1 yamazaki = new NewEngineer1 { Id = 1, Name = "yamazaki" };
Modify(yamazaki);
Console.WriteLine(yamazaki.Name); // yamazaki_san
}
呼び出し先メソッドで Name
を書き換えると、呼び出し元でも変更が見える。
プリミティブ(値型)の場合
static void PlusOne(int i)
{
i++;
Console.WriteLine($"呼び出し先のi: {i}");
}
public static void Main()
{
int i = 10;
Console.WriteLine($"呼び出し元のi: {i}");
PlusOne(i);
Console.WriteLine($"呼び出し元のi: {i}");
}
<実行結果>
呼び出し元のi: 10 呼び出し先のi: 11 呼び出し元のi: 10 |
値型ではメソッド呼び出し時に「値のコピー」が渡されるため、呼び出し先で変更しても呼び出し元に影響しません。

9. メソッドの戻り値に参照を使う
メソッドの戻り値にクラスのインスタンスを返すと、実質的に複数の値(フィールド)をまとめて返せます。
static NewEngineer2 Compare(NewEngineer2 se1, NewEngineer2 se2)
{
return (se1.Id > se2.Id) ? se1 : se2;
}
public static void Main()
{
NewEngineer2 e1 = new NewEngineer2 { Id = 8, Name = "shinohara" };
NewEngineer2 e2 = new NewEngineer2 { Id = 2, Name = "imai" };
NewEngineer2 bigger = Compare(e1, e2);
bigger.Show();
}
ここで Compare
メソッドは「idが大きいほうのインスタンス」を戻り値として返します。戻ってきたインスタンスを呼び出し元で再利用可能です。
10. コンストラクタでインスタンスの初期化
クラス名と同じ名前のメソッドが「コンストラクタ」です。new クラス名(...)
で呼び出され、インスタンス生成時に一度だけ自動実行されます。
基本構文
class クラス名
{
// コンストラクタ
public クラス名(引数リスト)
{
// インスタンスの初期化処理
}
}
コンストラクタには戻り値の型を付けません。(void
も書きません)
例: NewEngineer3
クラス
namespace Chap09
{
class NewEngineer3
{
public int Id;
public string Name;
// コンストラクタ
public NewEngineer3(int id, string name)
{
this.Id = id;
this.Name = name;
}
}
}
namespace Chap09
{
public class Example15
{
public static void Main()
{
NewEngineer3 se = new NewEngineer3(3, "tabuchi");
Console.WriteLine($"{se.Id} : {se.Name}");
}
}
}
<実行結果>
3 : tabuchi |
this.Id = id;
のthis
は「このインスタンス自身」を指すキーワード。フィールドと引数の区別のためによく使われます。- コンストラクタを定義しなかった場合、C#では「デフォルトコンストラクタ」が自動生成されます(引数なしでインスタンス生成可能になる)。
コンストラクタのオーバーロード
コンストラクタもメソッド同様にオーバーロードできます。引数の数や型が違うコンストラクタを複数定義可能です。
class NewEngineer4
{
public int Id;
public string Name;
// (1) 引数2つ
public NewEngineer4(int id, string name)
{
this.Id = id;
this.Name = name;
}
// (2) 引数なし → (1)のコンストラクタを呼び出す
public NewEngineer4()
: this(0, "名無し")
{
}
}
namespace Chap09
{
public class Example16
{
public static void Main()
{
NewEngineer4 se = new NewEngineer4(); // 引数なし
Console.WriteLine($"{se.Id} : {se.Name}");
// => 0 : 名無し
}
}
}
: this(...)
構文を使ってコンストラクタの中で別のコンストラクタを呼び出せます。
11. インスタンス変数と static変数 の使い分け
- インスタンス変数 … 各インスタンスが固有に持つ情報(例:
Id
,Name
) - static変数 (クラス変数) … クラス全体で共有したい情報(例: 全新人エンジニアの総数)
class NewEngineer6
{
public static int Count;
public int Id;
public string Name;
public NewEngineer6(int id, string name)
{
this.Id = id;
this.Name = name;
Count++;
Show();
}
public void Show()
{
Console.WriteLine($"{Id} : {Name} : {Count}人目です。");
}
}
public class Example19
{
public static void Main()
{
new NewEngineer6(4, "imai");
new NewEngineer6(3, "shinohara");
new NewEngineer6(2, "tabuchi");
Console.WriteLine($"クラスの総人数: {NewEngineer6.Count}");
}
}
<実行結果>
4 : imai : 1人目です。 3 : shinohara : 2人目です。 2 : tabuchi : 3人目です。 クラスの総人数: 3 |
Count
は static int
なので、インスタンスごとに別々の値を持たない。「全体でただ1つ」
Id
や Name
は各インスタンス固有の情報
以下の「メモリの3つの領域のイメージ」も参考にして下さい。

staticメソッドとインスタンスメソッドの使い分け
- インスタンスメソッド … 個々のインスタンスが持つ情報(
this
)を操作したい場合。 - staticメソッド … インスタンスに依存しない処理(汎用的ユーティリティなど)。
C#の System.Math
クラスはstaticメソッド (Math.Sin(...)
, Math.Cos(...)
等) を多く持ち、「インスタンスを生成するまでもない共通処理」を表現します。
また、Mathクラスのようにインスタンスを作ってほしくない場合は、private constructor
でブロックします。
public static class MyUtility
{
// 静的クラスとするとインスタンス生成不可
public static double MyStaticMethod()
{
...
}
}
C#では static class
と書くと、そのクラスはすべて静的メンバーだけを持つようになります(インスタンス化禁止)。
次回は、「10章. 継承を使ってクラスをグループ化する」を学びます。