C#クラスの基本を初心者向けに解説|定義・使い方・インスタンス化までわかる完全ガイド

はじめに

C#でアプリケーションを開発するうえで、クラスは欠かせない重要な仕組みです。しかし、プログラミングを学び始めたばかりの方にとっては、「クラスとインスタンスは何が違うのか」「どのように定義して使うのか」といった点がわかりにくいかもしれません。

C#のクラスは、データと処理をひとつにまとめるための設計図です。クラスを適切に利用すると、プログラムを機能ごとに整理でき、コードの再利用や修正がしやすくなります。

この記事では、C#クラスの基本概念から定義方法、インスタンス化、コンストラクタ、アクセス修飾子、static、継承までを初心者向けに解説します。実際に動かせるサンプルコードも紹介するため、コードを書きながらクラスの使い方を身につけていきましょう。

1. C#のクラスとは?初心者向けに基本概念をわかりやすく解説

1-1. クラスは「オブジェクトの設計図」

C#のクラスとは、オブジェクトが持つデータや処理を定義した設計図です。

たとえば、ゲームに登場するキャラクターをプログラムで表現する場合、キャラクターには名前、体力、攻撃力などのデータがあります。また、攻撃する、回復する、移動するといった処理も必要です。

これらをまとめて定義するのがクラスです。

public class Character{public string Name { get; set; } = "";public int Health { get; set; }
public void Attack(){Console.WriteLine($"{Name}が攻撃しました。");}

}

このCharacterクラスには、次の要素が含まれています。

  • キャラクター名を表すName

  • 体力を表すHealth

  • 攻撃処理を表すAttackメソッド

クラスを定義しておけば、同じ特徴を持つキャラクターを何人でも作成できます。

1-2. オブジェクト指向におけるクラスの役割

C#は、オブジェクト指向を中心に設計されたプログラミング言語です。

オブジェクト指向では、プログラムを役割ごとのオブジェクトに分け、それぞれを組み合わせてシステムを構築します。クラスは、そのオブジェクトを作るための定義です。

たとえば、ECサイトを開発する場合は、次のようなクラスが考えられます。

  • 商品を表すProductクラス

  • 顧客を表すCustomerクラス

  • 注文を表すOrderクラス

  • 支払いを表すPaymentクラス

各クラスに必要なデータと処理をまとめることで、プログラム全体の構造がわかりやすくなります。

1-3. クラスとインスタンスの違い

クラスとインスタンスは、設計図と実物の関係にあります。

クラスは、オブジェクトがどのようなデータや処理を持つかを定義したものです。一方、インスタンスは、そのクラスをもとに実際に作成されたオブジェクトです。

家に例えると、次のように考えられます。

  • クラス:家の設計図

  • インスタンス:設計図をもとに建てられた家

C#では、通常newキーワードを使ってインスタンスを作成します。

Character hero = new Character();

このコードでは、Characterクラスをもとに、heroという変数から参照できるインスタンスを作成しています。

同じクラスから複数のインスタンスを作ることも可能です。

Character hero = new Character();Character enemy = new Character();

heroenemyは同じCharacterクラスから作られていますが、それぞれ別のデータを持つ独立したインスタンスです。

1-4. C#でクラスを使うメリット

C#でクラスを使う主なメリットは、コードを整理し、再利用しやすくできることです。

クラスを使うと、関連するデータと処理をひとつにまとめられます。たとえば、顧客に関する情報や処理をCustomerクラスに集約すれば、顧客機能がどこに実装されているかを把握しやすくなります。

また、クラスから複数のインスタンスを作成できるため、同じコードを何度も書く必要がありません。

ほかにも、クラスには次のようなメリットがあります。

  • プログラムを役割ごとに分割できる

  • 同じ仕組みを再利用できる

  • データを外部から保護できる

  • 修正による影響範囲を小さくできる

  • 複数人で開発するときに担当を分けやすい

小規模なプログラムではクラスの必要性を感じにくいこともあります。しかし、プログラムの規模が大きくなるほど、クラスによる整理が重要になります。

2. C#クラスの基本構文と定義方法

2-1. クラス定義の基本形

C#でクラスを定義する基本構文は次のとおりです。

アクセス修飾子 class クラス名{// フィールド// プロパティ// コンストラクタ// メソッド}

簡単なクラスを定義すると、次のようになります。

public class Person{public string Name { get; set; } = "";

public void Introduce(){Console.WriteLine($"私の名前は{Name}です。");}

}

Personがクラス名です。波かっこの中に、そのクラスが持つデータや処理を記述します。

2-2. classキーワードの使い方

C#では、クラスを定義するときにclassキーワードを使用します。

class Sample{}

このコードは、Sampleという名前のクラスを定義しています。

クラスをほかのクラスから利用できるようにする場合は、一般的にpublicを付けます。

public class Sample{}

classは、あくまでクラスの設計を定義するためのキーワードです。クラスを定義しただけでは、オブジェクトは作成されません。実際に利用するには、基本的にクラスからインスタンスを作成する必要があります。

2-3. クラス名の命名ルール

C#のクラス名には、数字、英字、アンダースコアなどを使用できます。ただし、数字から始めることはできません。

public class User{}

public class UserAccount{}

C#では、クラス名の各単語の先頭を大文字にするPascalCaseが一般的です。

良い例は次のとおりです。

public class Product{}

public class ShoppingCart{}

public class OrderService{}

一方、次のような名前は、クラスの役割がわかりにくいため避けたほうがよいでしょう。

public class Data{}

public class Sample1{}

public class TestClass{}

クラス名には、そのクラスが何を表しているのかが伝わる具体的な名前を付けることが大切です。

2-4. アクセス修飾子の基本

アクセス修飾子とは、クラスやメンバーをどこから利用できるか指定するキーワードです。

クラス定義では、主にpublicinternalが使われます。

public class PublicClass{}

internal class InternalClass{}

publicを指定したクラスは、ほかのプロジェクトからも利用できます。internalを指定したクラスは、原則として同じアセンブリ内からのみ利用できます。

トップレベルに定義したクラスでアクセス修飾子を省略すると、既定ではinternalになります。

class Sample{}

このSampleクラスは、次の定義と同じ扱いです。

internal class Sample{}

クラス内部のフィールドやメソッドでは、publicprivateprotectedinternalなどを使用できます。

2-5. クラスを定義する場所とファイル構成

C#では、ひとつのファイルに複数のクラスを定義できます。

public class Person{}

public class Product{}

ただし、実際の開発では、基本的にひとつのクラスをひとつのファイルに定義すると管理しやすくなります。

たとえば、次のようなファイル構成です。

MyApp├── Program.cs├── Person.cs├── Product.cs└── Order.cs

Person.csにはPersonクラスを定義します。

namespace MyApp;

public class Person{public string Name { get; set; } = "";}

Program.csからは、次のように利用できます。

using MyApp;

Person person = new Person();person.Name = "田中";

クラスを役割ごとにファイルへ分割すると、目的のコードを見つけやすくなり、修正やレビューもしやすくなります。

3. C#クラスのメンバーを理解する

C#では、クラスの中に定義する変数や処理をまとめてメンバーと呼びます。代表的なクラスメンバーは、フィールド、プロパティ、メソッド、コンストラクタです。

3-1. フィールドとは

フィールドは、クラス内でデータを保持するための変数です。

public class Person{private string name = "";}

この例では、nameがフィールドです。

フィールドには、外部から直接変更されたくないデータを保存することが多いため、一般的にprivateを指定します。

public class BankAccount{private decimal balance;}

このbalanceフィールドは、BankAccountクラスの外部から直接アクセスできません。

BankAccount account = new BankAccount();

// privateなのでアクセスできない// account.balance = 10000;

フィールドを非公開にすると、意図しない値の変更を防ぎやすくなります。

3-2. プロパティとは

プロパティは、クラスが持つ値を取得または設定するための仕組みです。

public class Person{public string Name { get; set; } = "";}

getは値の取得、setは値の設定を表します。

Person person = new Person();

person.Name = "佐藤"; // setConsole.WriteLine(person.Name); // get

getsetを自動的に実装するプロパティは、自動実装プロパティと呼ばれます。

値を外部から変更させたくない場合は、setprivateにできます。

public class User{public string Id { get; private set; }

public User(string id){Id = id;}

}

この場合、Idはクラスの外部から読み取れますが、外部から変更することはできません。

User user = new User("U001");

Console.WriteLine(user.Id);

// コンパイルエラー// user.Id = "U002";

3-3. メソッドとは

メソッドは、クラスが行う処理を定義したものです。

public class Calculator{public int Add(int x, int y){return x + y;}}

Addメソッドは、2つの整数を受け取り、その合計を返します。

Calculator calculator = new Calculator();

int result = calculator.Add(10, 20);Console.WriteLine(result);

実行結果は次のとおりです。

30

戻り値がないメソッドでは、戻り値の型にvoidを指定します。

public void ShowMessage(){Console.WriteLine("メッセージを表示します。");}

メソッドを使うと、複数の処理をわかりやすい名前でまとめられます。

3-4. コンストラクタとは

コンストラクタは、インスタンスが作成されたときに自動的に呼び出される特別な処理です。

コンストラクタ名はクラス名と同じにし、戻り値の型は書きません。

public class Person{public string Name { get; set; }

public Person(string name){Name = name;}

}

インスタンスを作成すると、コンストラクタが実行されます。

Person person = new Person("鈴木");Console.WriteLine(person.Name);

コンストラクタは、インスタンスが利用可能になる前に必要な初期値を設定する目的で使われます。

3-5. クラスメンバーの具体例

フィールド、プロパティ、メソッド、コンストラクタを組み合わせたクラスを確認しましょう。

public class BankAccount{private decimal balance;

public string OwnerName { get; }public decimal Balance{get{return balance;}}public BankAccount(string ownerName, decimal initialBalance){OwnerName = ownerName;balance = initialBalance;}public void Deposit(decimal amount){if (amount <= 0){Console.WriteLine("入金額は0より大きい値を指定してください。");return;}balance += amount;}public bool Withdraw(decimal amount){if (amount <= 0 || amount > balance){return false;}balance -= amount;return true;}

}

このクラスでは、残高を表すbalanceフィールドをprivateにしています。外部から残高を直接変更できないため、入出金は必ずDepositメソッドまたはWithdrawメソッドを通して行われます。

このように、データとそのデータを操作する処理をクラスにまとめ、外部からの不正な変更を防ぐ考え方をカプセル化といいます。

4. C#クラスの使い方|インスタンス化の基本

4-1. newキーワードでインスタンスを作成する方法

クラスを定義しただけでは、クラス内のプロパティやメソッドを通常のオブジェクトとして利用できません。クラスからインスタンスを作成する必要があります。

インスタンスを作成する基本構文は次のとおりです。

クラス名 変数名 = new クラス名();

具体例を見てみましょう。

public class Dog{public string Name { get; set; } = "";}

Dogクラスのインスタンスは、次のように作成します。

Dog dog = new Dog();

C#では、型が明らかな場合にvarを使うこともできます。

var dog = new Dog();

どちらもDogクラスのインスタンスを作成するコードです。

4-2. インスタンスからプロパティにアクセスする方法

インスタンスのプロパティには、ドット演算子を使ってアクセスします。

Dog dog = new Dog();

dog.Name = "ポチ";Console.WriteLine(dog.Name);

dog.Nameのように、インスタンスを格納した変数名とプロパティ名を.でつなぎます。

オブジェクト初期化子を使うと、インスタンスの作成とプロパティの設定をまとめて記述できます。

Dog dog = new Dog{Name = "ポチ"};

複数のプロパティがある場合にも便利です。

public class Product{public string Name { get; set; } = "";public decimal Price { get; set; }}
Product product = new Product{Name = "キーボード",Price = 5000};

4-3. インスタンスからメソッドを呼び出す方法

インスタンスメソッドも、ドット演算子を使って呼び出します。

public class Dog{public string Name { get; set; } = "";

public void Bark(){Console.WriteLine($"{Name}がワンと鳴きました。");}

}

Dog dog = new Dog{Name = "ポチ"};

dog.Bark();

実行結果は次のとおりです。

ポチがワンと鳴きました。

Barkメソッドは、同じインスタンスが持つNameプロパティの値を使用しています。

4-4. 複数のインスタンスを作成する例

同じクラスから複数のインスタンスを作成できます。

Dog dog1 = new Dog{Name = "ポチ"};

Dog dog2 = new Dog{Name = "ハチ"};

dog1.Bark();dog2.Bark();

実行結果は次のとおりです。

ポチがワンと鳴きました。ハチがワンと鳴きました。

dog1dog2は同じDogクラスをもとに作られていますが、それぞれ別のNameを持っています。

一方のインスタンスのプロパティを変更しても、もう一方には影響しません。

dog1.Name = "コロ";

Console.WriteLine(dog1.Name);Console.WriteLine(dog2.Name);

実行結果は次のとおりです。

コロハチ

4-5. クラス定義から実行までのサンプルコード

クラスの定義、インスタンス化、プロパティへのアクセス、メソッドの呼び出しまでをまとめた例です。

public class Product{public string Name { get; set; } = "";public decimal Price { get; set; }

public void ShowInfo(){Console.WriteLine($"商品名:{Name}");Console.WriteLine($"価格:{Price:N0}円");}

}

public class Program{public static void Main(){Product product = new Product{Name = "マウス",Price = 3000};

    product.ShowInfo();}

}

実行結果は次のとおりです。

商品名:マウス価格:3,000円

トップレベルステートメントを使用するプロジェクトでは、実行部分を次のように書くこともできます。

Product product = new Product{Name = "マウス",Price = 3000};

product.ShowInfo();

public class Product{public string Name { get; set; } = "";public decimal Price { get; set; }

public void ShowInfo(){Console.WriteLine($"商品名:{Name}");Console.WriteLine($"価格:{Price:N0}円");}

}

5. C#クラスとコンストラクタの使い方

5-1. コンストラクタの役割

コンストラクタは、インスタンス作成時に必要な初期化処理を行うための仕組みです。

たとえば、ユーザーを表すクラスに名前が必須である場合、コンストラクタで名前を受け取るようにします。

public class User{public string Name { get; }

public User(string name){Name = name;}

}

利用するときは、new User()の丸かっこ内に名前を渡します。

User user = new User("山田");Console.WriteLine(user.Name);

この設計では、名前を指定せずにインスタンスを作成できません。そのため、必須データが未設定のまま利用されることを防げます。

5-2. デフォルトコンストラクタとは

クラスにコンストラクタをひとつも定義していない場合、C#コンパイラは引数のないコンストラクタを自動的に用意します。これをデフォルトコンストラクタと呼びます。

public class Person{public string Name { get; set; } = "";}

コンストラクタを書いていなくても、次のようにインスタンス化できます。

Person person = new Person();

ただし、引数付きコンストラクタをひとつでも定義すると、引数なしのコンストラクタは自動生成されません。

public class Person{public string Name { get; }

public Person(string name){Name = name;}

}

この場合、次のコードはコンパイルエラーになります。

// 引数なしコンストラクタが存在しない// Person person = new Person();

引数なしでも作成できるようにしたい場合は、自分でコンストラクタを定義します。

public class Person{public string Name { get; }

public Person(){Name = "未設定";}public Person(string name){Name = name;}

}

5-3. 引数付きコンストラクタの書き方

引数付きコンストラクタを使うと、インスタンス作成時に外部から値を受け取れます。

public class Product{public string Name { get; }public decimal Price { get; }

public Product(string name, decimal price){Name = name;Price = price;}

}

インスタンスを作成するときは、引数を順番に指定します。

Product product = new Product("モニター", 25000);

引数の型や数が一致しない場合は、コンパイルエラーになります。

// priceが不足している// Product product = new Product("モニター");

必須項目をコンストラクタの引数にすると、必要な値がそろった状態でインスタンスを作成できます。

5-4. コンストラクタで初期値を設定する方法

コンストラクタでは、受け取った引数をプロパティやフィールドへ代入できます。

public class Character{public string Name { get; }public int Health { get; private set; }

public Character(string name){Name = name;Health = 100;}

}

このクラスでは、キャラクター名を引数から設定し、体力を常に100で初期化しています。

Character character = new Character("勇者");

Console.WriteLine(character.Name);Console.WriteLine(character.Health);

実行結果は次のとおりです。

勇者100

不正な値が渡されないよう、コンストラクタ内で検証することもできます。

public class Product{public string Name { get; }public decimal Price { get; }

public Product(string name, decimal price){if (string.IsNullOrWhiteSpace(name)){throw new ArgumentException("商品名を入力してください。",nameof(name));}if (price < 0){throw new ArgumentOutOfRangeException(nameof(price),"価格には0以上の値を指定してください。");}Name = name;Price = price;}

}

5-5. コンストラクタのオーバーロード

同じクラス内に、引数の数や型が異なる複数のコンストラクタを定義できます。これをコンストラクタのオーバーロードと呼びます。

public class Product{public string Name { get; }public decimal Price { get; }

public Product(){Name = "名称未設定";Price = 0;}public Product(string name){Name = name;Price = 0;}public Product(string name, decimal price){Name = name;Price = price;}

}

それぞれ次のように利用できます。

Product product1 = new Product();Product product2 = new Product("キーボード");Product product3 = new Product("モニター", 25000);

コンストラクタ初期化子を使うと、別のコンストラクタを呼び出して処理の重複を減らせます。

public class Product{public string Name { get; }public decimal Price { get; }

public Product(): this("名称未設定", 0){}public Product(string name): this(name, 0){}public Product(string name, decimal price){Name = name;Price = price;}

}

6. C#クラスでよく使うアクセス修飾子

6-1. publicの使い方

publicを指定したクラスやメンバーは、アクセス可能な場所であれば外部から利用できます。

public class Person{public string Name { get; set; } = "";

public void Introduce(){Console.WriteLine($"私は{Name}です。");}

}

NameプロパティとIntroduceメソッドはpublicなので、インスタンスからアクセスできます。

Person person = new Person();

person.Name = "高橋";person.Introduce();

外部へ公開する必要がある機能にはpublicを使います。ただし、すべてのメンバーを無条件にpublicにすると、意図しない操作を受けやすくなるため注意が必要です。

6-2. privateの使い方

privateを指定したメンバーは、定義されたクラスの内部からのみアクセスできます。

public class BankAccount{private decimal balance;

public void Deposit(decimal amount){if (amount > 0){balance += amount;}}public decimal GetBalance(){return balance;}

}

balanceは外部から直接変更できません。

BankAccount account = new BankAccount();

// コンパイルエラー// account.balance = -10000;

外部に公開する必要がないフィールドや補助メソッドには、基本的にprivateを使います。

クラスメンバーのアクセス修飾子を省略した場合、既定ではprivateになります。ただし、意図を明確にするため、明示的に記述することも一般的です。

6-3. protectedの使い方

protectedを指定したメンバーは、定義元のクラスと、そのクラスを継承した派生クラスからアクセスできます。

public class Animal{protected string Name { get; set; } = "";}
public class Dog : Animal{public void SetName(string name){Name = name;}

public void ShowName(){Console.WriteLine(Name);}

}

DogクラスはAnimalクラスを継承しているため、protectedNameへアクセスできます。

一方、通常の外部コードからはアクセスできません。

Dog dog = new Dog();

// protectedなので外部からはアクセスできない// dog.Name = "ポチ";

protectedは、継承先に公開したい一方で、一般の外部コードには公開したくないメンバーに使用します。

6-4. internalの使い方

internalを指定したクラスやメンバーは、原則として同じアセンブリ内からアクセスできます。

internal class OrderValidator{public bool IsValid(){return true;}}

同じプロジェクト内で利用する補助クラスや、外部ライブラリの利用者には公開したくない機能に向いています。

クラスのメンバーにも指定できます。

public class Product{internal decimal Cost { get; set; }}

Productクラス自体は外部に公開されますが、Costプロパティは同じアセンブリ内からのみアクセスできます。

6-5. アクセス修飾子を使い分けるポイント

アクセス修飾子は、必要な範囲だけにクラスやメンバーを公開することが基本です。

目安は次のとおりです。

アクセス修飾子主なアクセス範囲主な用途
publicアクセス可能なすべての場所外部へ公開する機能
private定義したクラスの内部内部データや補助処理
protected定義したクラスと派生クラス継承先へ公開する機能
internal同じアセンブリ内プロジェクト内部の機能

迷った場合は、まずprivateで実装し、外部から利用する必要があるメンバーだけをpublicにするとよいでしょう。

公開範囲を必要最小限にすることで、クラス内部の実装を変更しやすくなり、不正な値の設定も防ぎやすくなります。

7. C#クラスとstaticの違い

7-1. staticクラスとは

staticクラスは、インスタンスを作成せずに利用するクラスです。クラスの定義にstaticキーワードを付けます。

public static class MathHelper{public static int Square(int number){return number * number;}}

staticクラスは、クラス名から直接メンバーを呼び出します。

int result = MathHelper.Square(5);Console.WriteLine(result);

staticクラスのインスタンスは作成できません。

// コンパイルエラー// MathHelper helper = new MathHelper();

また、staticクラス内に定義するメンバーは、基本的にすべてstaticでなければなりません。

7-2. staticメンバーとは

通常のクラスにもstaticメンバーを定義できます。staticメンバーは、特定のインスタンスではなく、クラスそのものに属するメンバーです。

public class User{public static int CreatedCount { get; private set; }

public string Name { get; }public User(string name){Name = name;CreatedCount++;}

}

CreatedCountは、すべてのUserインスタンスで共有されます。

User user1 = new User("佐藤");User user2 = new User("鈴木");

Console.WriteLine(User.CreatedCount);

実行結果は次のとおりです。

2

staticメンバーには、インスタンス名ではなくクラス名からアクセスします。

7-3. インスタンスメンバーとの違い

インスタンスメンバーは、それぞれのインスタンスが個別に持つデータや処理です。

public class User{public string Name { get; set; } = "";}
User user1 = new User{Name = "佐藤"};

User user2 = new User{Name = "鈴木"};

user1.Nameuser2.Nameは、それぞれ異なる値を保持できます。

一方、staticメンバーはクラス全体で共有されます。

public class AppSettings{public static string AppName { get; set; } = "SampleApp";}
Console.WriteLine(AppSettings.AppName);

違いを整理すると、次のようになります。

項目インスタンスメンバーstaticメンバー
所属個別のインスタンスクラス自体
呼び出し方instance.MemberClassName.Member
インスタンス化必要不要
データインスタンスごとに保持全体で共有

7-4. staticを使うべきケース

staticは、特定のオブジェクトの状態に依存しない処理に適しています。

たとえば、単位変換や数値計算などです。

public static class TemperatureConverter{public static double CelsiusToFahrenheit(double celsius){return celsius * 9 / 5 + 32;}}
double fahrenheit =TemperatureConverter.CelsiusToFahrenheit(25);

Console.WriteLine(fahrenheit);

次のようなケースでもstaticが使われます。

  • 複数箇所で使う単純な計算処理

  • 文字列や数値の変換処理

  • アプリケーション全体で共有する定数

  • インスタンス数などの共有情報

  • インスタンスを必要としないユーティリティー処理

ただし、ある対象ごとに異なる状態を持つ必要がある場合は、通常のクラスとインスタンスメンバーが適しています。

7-5. staticを使う際の注意点

staticメンバーの値はアプリケーション内で共有されるため、どこからでも変更できる設計にすると、値が変更された場所を追いにくくなります。

public static class GlobalData{public static int Count { get; set; }}

このような変更可能なstaticプロパティを多用すると、処理同士の依存関係が複雑になる可能性があります。

特に、複数の処理が同時に実行される環境では、共有データの更新が競合することもあります。また、staticな状態に強く依存したコードは、テスト時に状態を初期化しにくくなることがあります。

単に「インスタンス化が面倒だから」という理由でstaticにするのではなく、そのデータや処理が本当にクラス全体で共有されるべきかを判断することが大切です。

8. C#クラスの継承と基本的な使い方

8-1. 継承とは

継承とは、既存のクラスが持つ機能を別のクラスへ引き継ぐ仕組みです。

たとえば、犬と猫には、名前を持つ、鳴くといった共通点があります。共通する部分をAnimalクラスにまとめ、DogクラスとCatクラスに継承させられます。

public class Animal{public string Name { get; set; } = "";

public void Eat(){Console.WriteLine($"{Name}が食事をしています。");}

}

public class Dog : Animal{public void Bark(){Console.WriteLine($"{Name}がワンと鳴きました。");}}

DogクラスにはNameEatを直接定義していませんが、Animalクラスから継承しているため利用できます。

8-2. 基底クラスと派生クラス

継承元となるクラスを基底クラス、継承して作るクラスを派生クラスと呼びます。

次の例では、Animalが基底クラス、Dogが派生クラスです。

public class Animal{}

public class Dog : Animal{}

次のように考えるとわかりやすいでしょう。

  • Animal:動物全般に共通する特徴を定義

  • Dog:動物の特徴を引き継ぎ、犬独自の機能を追加

C#のクラスが直接継承できる基底クラスはひとつです。複数のクラスを同時に継承することはできません。

8-3. 継承の基本構文

クラスを継承するときは、派生クラス名の後ろにコロンと基底クラス名を記述します。

public class 派生クラス : 基底クラス{}

具体例は次のとおりです。

public class Employee{public string Name { get; set; } = "";

public void Work(){Console.WriteLine($"{Name}が仕事をしています。");}

}

public class Manager : Employee{public void Manage(){Console.WriteLine($"{Name}がチームを管理しています。");}}

Manager manager = new Manager{Name = "田中"};

manager.Work();manager.Manage();

Managerクラスのインスタンスから、基底クラスのWorkと派生クラスのManageを呼び出せます。

8-4. overrideとvirtualの基本

基底クラスの処理を派生クラスで変更したい場合は、virtualoverrideを使います。

基底クラスで、上書きを許可するメソッドにvirtualを付けます。

public class Animal{public virtual void Speak(){Console.WriteLine("動物が鳴きました。");}}

派生クラスでは、上書きするメソッドにoverrideを付けます。

public class Dog : Animal{public override void Speak(){Console.WriteLine("ワン");}}

public class Cat : Animal{public override void Speak(){Console.WriteLine("ニャー");}}

Animal dog = new Dog();Animal cat = new Cat();

dog.Speak();cat.Speak();

実行結果は次のとおりです。

ワンニャー

変数の型はどちらもAnimalですが、実際に格納されたインスタンスに対応するメソッドが実行されます。このように、同じ呼び出し方で異なる処理を実行できる仕組みは、ポリモーフィズムの代表例です。

8-5. 継承を使うメリットと注意点

継承を使うと、複数のクラスに共通するプロパティやメソッドを基底クラスへまとめられます。そのため、同じコードを繰り返し書く必要がなくなります。

また、基底クラス型の変数やコレクションで、複数種類の派生クラスを同じように扱えます。

List<Animal> animals =[new Dog(),new Cat()];

foreach (Animal animal in animals){animal.Speak();}

一方で、コードを再利用したいだけの理由で継承を使うと、基底クラスと派生クラスの関係が不自然になることがあります。

継承は、「派生クラスは基底クラスの一種である」と自然に説明できる関係で使うのが基本です。

  • 犬は動物の一種である

  • 管理者は従業員の一種である

  • 自動車は乗り物の一種である

単に機能を利用したいだけの場合は、別のクラスをフィールドやプロパティとして持たせるコンポジションも検討しましょう。

9. C#クラスとインターフェース・構造体の違い

9-1. クラスとインターフェースの違い

クラスは、データや処理の実装を持ち、インスタンスを作成できる設計図です。

public class EmailSender{public void Send(string message){Console.WriteLine($"メール送信:{message}");}}

インターフェースは、クラスが備えるべき機能の契約を定義するために使います。

public interface IMessageSender{void Send(string message);}

インターフェースを実装するクラスは、契約で定義されたメンバーを実装します。

public class EmailSender : IMessageSender{public void Send(string message){Console.WriteLine($"メール送信:{message}");}}

public class SmsSender : IMessageSender{public void Send(string message){Console.WriteLine($"SMS送信:{message}");}}

インターフェースを利用すると、具体的なクラスが異なっても、同じ契約に基づいて扱えます。

public class NotificationService{private readonly IMessageSender sender;

public NotificationService(IMessageSender sender){this.sender = sender;}public void Notify(string message){sender.Send(message);}

}

クラスは状態と実装を持つ具体的な設計に向き、インターフェースは複数の型に共通する役割や契約を表現するのに向いています。

9-2. クラスと構造体の違い

構造体は、structキーワードで定義します。

public struct Point{public int X { get; set; }public int Y { get; set; }}

クラスと構造体の大きな違いは、クラスが参照型で、構造体が値型であることです。

構造体は、小さなデータのまとまりを表現する場合に適しています。たとえば、座標、色、日付に関連する値などです。

クラスは、識別性を持つオブジェクト、変更可能な状態、複雑な処理、継承などが必要な場合に適しています。

なお、構造体もコンストラクタ、プロパティ、メソッド、インターフェースの実装などを利用できますが、クラスとは代入や継承の仕組みが異なります。

9-3. 参照型と値型の違い

参照型の変数には、オブジェクトそのものではなく、そのオブジェクトを参照する情報が格納されます。

クラスの例を見てみましょう。

public class Person{public string Name { get; set; } = "";}
Person person1 = new Person{Name = "佐藤"};

Person person2 = person1;person2.Name = "鈴木";

Console.WriteLine(person1.Name);

実行結果は次のとおりです。

鈴木

person1person2は、同じインスタンスを参照しています。そのため、person2から変更した内容がperson1からも確認できます。

一方、構造体は値型です。

public struct Point{public int X { get; set; }public int Y { get; set; }}
Point point1 = new Point{X = 10,Y = 20};

Point point2 = point1;point2.X = 100;

Console.WriteLine(point1.X);

実行結果は次のとおりです。

10

構造体を代入すると、基本的に値がコピーされます。そのため、point2を変更してもpoint1には影響しません。

9-4. どれを使うべきかの判断基準

クラス、インターフェース、構造体は、それぞれ目的が異なります。

種類主な用途
クラス状態と処理を持つオブジェクトを表現する
インターフェース複数の型に共通する役割や契約を定義する
構造体小さく単純な値のまとまりを表現する

ユーザー、商品、注文、車、ゲームキャラクターなど、識別性や状態の変化を持つ対象にはクラスが適しています。

メール送信機能、保存機能、ログ出力機能など、実装方法が異なっても共通の操作を定義したい場合にはインターフェースが適しています。

座標や範囲など、比較的小さく、ひとつの値として扱いたいデータには構造体が適しています。

初心者の段階では、まずクラスを基本として学び、共通の契約が必要になったときにインターフェース、小さな値を表現したいときに構造体を検討すると理解しやすいでしょう。

10. C#クラスの実践サンプルコード

10-1. Personクラスを作成する例

ここでは、人物を表すPersonクラスを段階的に作成します。

最初に、空のクラスを定義します。

public class Person{}

この時点では、Personという型が定義されただけで、人物の名前や年齢などの情報は持っていません。

10-2. プロパティとメソッドを追加する例

Personクラスに、名前と年齢を表すプロパティを追加します。また、自己紹介を行うメソッドも追加します。

public class Person{public string Name { get; set; } = "";public int Age { get; set; }

public void Introduce(){Console.WriteLine($"私の名前は{Name}です。{Age}歳です。");}

}

NameAgeは、インスタンスごとに異なる値を保持します。

Introduceメソッドを呼び出すと、そのインスタンスが持つ名前と年齢が表示されます。

10-3. コンストラクタを使って初期化する例

名前と年齢が必ず設定された状態でインスタンスを作成できるよう、コンストラクタを追加します。

public class Person{public string Name { get; }public int Age { get; private set; }

public Person(string name, int age){if (string.IsNullOrWhiteSpace(name)){throw new ArgumentException("名前を入力してください。",nameof(name));}if (age &lt; 0){throw new ArgumentOutOfRangeException(nameof(age),"年齢には0以上の値を指定してください。");}Name = name;Age = age;}public void Introduce(){Console.WriteLine($"私の名前は{Name}です。{Age}歳です。");}

}

Nameはコンストラクタでのみ設定し、Ageはクラスの外部から自由に変更できないようにしています。

年齢を増やす処理は、専用のメソッドとして追加できます。

public void HaveBirthday(){Age++;}

10-4. インスタンスを作成して値を表示する例

完成したPersonクラスを利用するコードは次のとおりです。

Person person1 = new Person("山田太郎", 25);Person person2 = new Person("佐藤花子", 30);

person1.Introduce();person2.Introduce();

person1.HaveBirthday();person1.Introduce();

public class Person{public string Name { get; }public int Age { get; private set; }

public Person(string name, int age){if (string.IsNullOrWhiteSpace(name)){throw new ArgumentException("名前を入力してください。",nameof(name));}if (age &lt; 0){throw new ArgumentOutOfRangeException(nameof(age),"年齢には0以上の値を指定してください。");}Name = name;Age = age;}public void Introduce(){Console.WriteLine($"私の名前は{Name}です。{Age}歳です。");}public void HaveBirthday(){Age++;}

}

実行結果は次のとおりです。

私の名前は山田太郎です。25歳です。私の名前は佐藤花子です。30歳です。私の名前は山田太郎です。26歳です。

10-5. 初心者向けのコード解説

サンプルコードの処理を順番に確認しましょう。

Person person1 = new Person("山田太郎", 25);

この行では、Personクラスから新しいインスタンスを作成しています。"山田太郎"25はコンストラクタへ渡され、NameAgeの初期値として設定されます。

person1.Introduce();

この行では、person1インスタンスのIntroduceメソッドを呼び出しています。メソッド内では、同じインスタンスが持つNameAgeを使って文章を表示します。

person1.HaveBirthday();

この行では、Ageを1増やしています。

Ageプロパティのsetprivateなので、次のようにクラスの外部から変更することはできません。

// コンパイルエラー// person1.Age = -100;

年齢の変更方法をHaveBirthdayメソッドに限定することで、不自然な値が設定されにくい設計になっています。

このように、C#のクラスでは、データをプロパティとして持たせるだけでなく、データを変更する処理もクラス内にまとめることが重要です。

11. C#クラスで初心者がつまずきやすいポイント

11-1. クラスを定義しただけでは実行されない

クラスはオブジェクトの設計図です。クラスを定義しただけでは、通常、そのクラスのインスタンスメソッドは実行されません。

public class Message{public void Show(){Console.WriteLine("こんにちは");}}

このコードだけでは、Showメソッドは呼び出されません。

インスタンスを作成し、メソッドを呼び出す必要があります。

Message message = new Message();message.Show();

コンストラクタも、インスタンスが作成されたときに初めて実行されます。

11-2. インスタンス化を忘れる

通常のクラスに定義したインスタンスメンバーは、クラス名から直接呼び出せません。

public class Calculator{public int Add(int x, int y){return x + y;}}

次のコードはエラーになります。

// インスタンスメソッドはクラス名から呼び出せない// int result = Calculator.Add(10, 20);

先にインスタンスを作成します。

Calculator calculator = new Calculator();int result = calculator.Add(10, 20);

クラス名から直接呼び出したい場合は、メソッドをstaticにする必要があります。

public static class Calculator{public static int Add(int x, int y){return x + y;}}
int result = Calculator.Add(10, 20);

11-3. publicとprivateの使い分けを間違える

フィールドやプロパティをすべてpublicにすると、外部から自由に値を変更される可能性があります。

public class BankAccount{public decimal Balance { get; set; }}

この設計では、次のように負の残高を設定できてしまいます。

BankAccount account = new BankAccount();account.Balance = -100000;

残高を外部から直接変更させず、専用メソッドを通して操作する設計が適切です。

public class BankAccount{public decimal Balance { get; private set; }

public void Deposit(decimal amount){if (amount &lt;= 0){throw new ArgumentOutOfRangeException(nameof(amount));}Balance += amount;}

}

何でもprivateにすると外部から必要な機能を使えなくなり、何でもpublicにするとクラスの状態を守れなくなります。外部へ公開すべき操作だけをpublicにすることが大切です。

11-4. staticと通常クラスを混同する

staticメンバーとインスタンスメンバーでは、呼び出し方が異なります。

public class Sample{public static void StaticMethod(){Console.WriteLine("staticメソッド");}

public void InstanceMethod(){Console.WriteLine("インスタンスメソッド");}

}

staticメソッドはクラス名から呼び出します。

Sample.StaticMethod();

インスタンスメソッドはインスタンスから呼び出します。

Sample sample = new Sample();sample.InstanceMethod();

また、staticメソッドからは、特定のインスタンスが指定されていないため、インスタンスメンバーへ直接アクセスできません。

public class Counter{public int Value { get; set; }

public static void Show(){// インスタンスがないため直接アクセスできない// Console.WriteLine(Value);}

}

staticを使うときは、そのメンバーが個別のインスタンスに属するものか、クラス全体に属するものかを考えましょう。

11-5. NullReferenceExceptionが発生する原因

クラスは参照型であり、変数にnullが入ることがあります。nullは、参照先のインスタンスが存在しない状態です。

Person? person = null;

この状態でプロパティやメソッドへアクセスすると、NullReferenceExceptionが発生します。

// 実行時に例外が発生する// Console.WriteLine(person.Name);

nullの可能性がある場合は、アクセス前に確認します。

if (person is not null){Console.WriteLine(person.Name);}

null条件演算子を使う方法もあります。

Console.WriteLine(person?.Name);

personnullなら、Nameへアクセスせずにnullを返します。

既定値を表示したい場合は、null合体演算子を組み合わせられます。

Console.WriteLine(person?.Name ?? "名前未設定");

また、変数を宣言しただけでインスタンスを代入していないことも、エラーの原因になります。

Person? person = null;

// どこかでインスタンスを代入する必要があるperson = new Person("田中", 20);

NullReferenceExceptionを防ぐには、必要なインスタンスを確実に生成し、nullになる可能性がある値を適切に確認することが重要です。

まとめ

C#のクラスは、データと処理をひとつにまとめ、オブジェクトを作成するための設計図です。クラスから作成された実体をインスタンスと呼び、一般的にはnewキーワードを使って生成します。

クラスには、データを保持するフィールドやプロパティ、処理を表すメソッド、初期化を行うコンストラクタなどを定義できます。

C#クラスの基本的な流れは次のとおりです。

public class Person{public string Name { get; }

public Person(string name){Name = name;}public void Introduce(){Console.WriteLine($"私は{Name}です。");}

}

Person person = new Person("山田");person.Introduce();

クラスを設計するときは、外部に公開する必要があるメンバーだけをpublicにし、内部のデータや処理はprivateにすることが基本です。また、個別の状態を持たない処理にはstatic、共通機能を引き継ぐ場合には継承、共通の契約を定義する場合にはインターフェースを利用できます。

最初は、クラスとインスタンスの違いを理解し、プロパティ、コンストラクタ、メソッドを持つ簡単なクラスを実際に作成してみましょう。クラスを使ったコードを繰り返し書くことで、C#におけるオブジェクト指向の考え方も自然に身につきます。