C#を用いたオブジェクト指向プログラミングにおいて、クラスや構造体の内部で保持されるデータは、アプリケーションの「状態」を管理する極めて重要な要素です。
これらのデータは一般にメンバ変数(フィールド)と呼ばれますが、C#にはメンバ変数の他に「プロパティ」という概念が存在するため、初心者から中級者にかけてその使い分けや正しい書き方に悩むケースが少なくありません。
本記事では、C#におけるメンバ変数の基礎知識から、推奨される命名規則、プロパティとの明確な違い、そして保守性の高いコードを書くための実践的な活用法までをプロの視点で詳しく解説します。
最新のC#の仕様に基づいたベストプラクティスを学び、堅牢なプログラム開発に役立ててください。
メンバ変数(フィールド)の基礎知識
C#におけるメンバ変数とは、クラスや構造体の直下で宣言される変数のことを指し、言語仕様上は「フィールド」と呼ばれます。
これに対し、メソッドの中で宣言される変数は「ローカル変数」と呼ばれ、そのメソッドの実行が終了するとメモリから解放されますが、メンバ変数はそのインスタンス(オブジェクト)が存在する限り保持され続けます。
メンバ変数の役割
メンバ変数の主な役割は、オブジェクトの状態を保持することです。
例えば、「車」をクラスとして定義する場合、現在の速度、燃料の残量、走行距離などはメンバ変数として定義されます。
これらの値は、クラス内のどのメソッドからでもアクセス(アクセス修飾子による制限がない限り)でき、オブジェクトのライフサイクル全体を通じて共有されます。
基本的な宣言方法
メンバ変数は通常、クラスの先頭付近で宣言されます。
以下に最もシンプルな宣言の例を示します。
using System;
namespace MemberVariableSample
{
public class Player
{
// メンバ変数(フィールド)の宣言
private string name;
private int level = 1;
public void DisplayStatus()
{
// クラス内のメソッドからアクセス可能
Console.WriteLine($"Player: {name}, Level: {level}");
}
public void SetName(string newName)
{
name = newName;
}
}
class Program
{
static void Main(string[] args)
{
Player player = new Player();
player.SetName("アルス");
player.DisplayStatus();
}
}
}
Player: アルス, Level: 1
アクセス修飾子とカプセル化
メンバ変数を扱う上で、最も重要な概念の一つがカプセル化です。
カプセル化とは、オブジェクトの内部データに外部から直接干渉させず、整合性を保つための仕組みです。
C#ではアクセス修飾子を使用して、メンバ変数の公開範囲を制御します。
主要なアクセス修飾子
C#で頻繁に使用されるアクセス修飾子を以下の表にまとめました。
| 修飾子 | 説明 |
|---|---|
private | 同一クラス内からのみアクセス可能。原則としてフィールドはこの設定にする。 |
public | どこからでもアクセス可能。定数(const)以外では推奨されない。 |
protected | 同一クラス内および派生クラス(継承先)からアクセス可能。 |
internal | 同一アセンブリ(プロジェクト)内からアクセス可能。 |
なぜフィールドを public にしてはいけないのか
初心者のうちは、利便性を求めてメンバ変数を public にしてしまいがちです。
しかし、これはオブジェクト指向において極めて危険な設計とされます。
外部から自由に値を書き換えられると、例えば「HPがマイナスになる」「年齢に負の数が代入される」といった、クラスのロジックが想定していない不正な状態を許容してしまうからです。
そのため、メンバ変数は基本的に private で隠蔽し、外部とのやり取りは後述する「プロパティ」を通じて行うのが鉄則です。
C#における命名規則のベストプラクティス
コードの可読性を高めるためには、一貫した命名規則が必要です。
Microsoftが推奨するガイドラインに加え、多くの現場で採用されている慣習を解説します。
プライベートフィールドの命名
C#のプライベートなメンバ変数には、アンダースコア(_)で始まるキャメルケース(camelCase)を使用するのが一般的です。
- 良い例:
private int _healthPoint; - 避けるべき例:
private int m_healthPoint;(古いスタイル) - 避けるべき例:
private int HealthPoint;(プロパティと混同する)
アンダースコアを付ける最大の利点は、メソッド内で「ローカル変数」と「メンバ変数」を瞬時に見分けられることです。
また、コンストラクタで引数を受け取る際、thisキーワードを使わずに区別できるという実用的なメリットもあります。
パブリックな要素の命名
もし例外的にパブリックなフィールド(定数など)を作る場合や、プロパティを定義する場合は、パスカルケース(PascalCase)を使用します。
- 良い例:
public const int MaxLevel = 99; - 良い例:
public string PlayerName { get; set; }
メンバ変数とプロパティの違い
C#を特徴づける機能の一つが「プロパティ」です。
多くの開発者が「メンバ変数とプロパティはどう使い分けるべきか」という疑問を持ちます。
結論から言えば、外部に公開するデータはすべてプロパティにすべきです。
プロパティの仕組み
プロパティは、内部的には「アクセサ」と呼ばれるメソッド(get/set)の組み合わせです。
見た目は変数のように扱えますが、その実体は関数であるため、値の読み書き時にロジックを挟むことができます。
private int _age;
// プロパティによるカプセル化
public int Age
{
get { return _age; }
set
{
// 負の数が代入されないようバリデーションを行う
if (value < 0)
{
throw new ArgumentOutOfRangeException("年齢は0以上でなければなりません。");
}
_age = value;
}
}
自動実装プロパティの活用
特別なロジックが必要ない場合、C#では「自動実装プロパティ」を使用することで、バッキングフィールド(裏側のメンバ変数)を明示的に書く手間を省けます。
// 内部的に private なメンバ変数が自動生成される
public string Nickname { get; set; }
現代のC#開発では、ロジックが不要な場合でも将来の拡張性を考慮し、public フィールドではなく自動実装プロパティを使用することが標準となっています。
特殊な修飾子の活用:readonly と const
メンバ変数の中には、一度値を決めたら変更したくないものがあります。
これらを制御するために readonly と const を使い分けます。
readonly(読取専用フィールド)
readonly 修飾子を付けたメンバ変数は、宣言時またはコンストラクタ内でのみ値を代入できます。
実行時に値が決まるもの(例えば、インスタンス生成時の時刻や設定ファイルから読み込んだ値など)を固定したい場合に適しています。
const(定数)
const はコンパイル時に値が確定している必要があります。
静的な値(消費税率や数学的な定数など)に使用されます。
public class Configuration
{
// コンパイル時に決まる定数
public const string AppName = "MyCSharpApp";
// 実行時(コンストラクタ)に決まる読取専用変数
private readonly DateTime _createdAt;
public Configuration()
{
_createdAt = DateTime.Now;
}
}
静的メンバ変数(static フィールド)
通常、メンバ変数はインスタンスごとに独立したメモリ領域を持ちますが、static 修飾子を付与することで、クラス全体で共有される変数を作成できます。
static の特徴
- インスタンスを生成(new)しなくてもアクセス可能。
- すべてのインスタンスで同じ値を参照・変更する。
- アプリケーションの実行中、メモリ上に一つだけ存在する。
public class Counter
{
// すべてのインスタンスで共有されるカウント数
private static int _totalCount = 0;
public Counter()
{
_totalCount++;
}
public static int GetTotalCount() => _totalCount;
}
静的メンバ変数は便利ですが、グローバル変数に近い性質を持つため、多用するとプログラムの結合度が高まり、ユニットテストが困難になるという側面もあります。
慎重な設計が求められる要素です。
実践例:メンバ変数とプロパティを組み合わせたクラス設計
これまでの知識を統合し、実務で役立つクラス設計の例を見てみましょう。
以下のコードは、銀行口座をモデル化したものです。
using System;
namespace BankSystem
{
public class BankAccount
{
// 1. プライベートなメンバ変数(状態の保持)
private decimal _balance;
private readonly string _accountNumber;
// 2. 静的なメンバ変数(全体管理)
private static int _totalAccountsCreated;
// 3. プロパティ(外部への公開と制御)
public string AccountNumber => _accountNumber; // 読取専用プロパティ
public decimal Balance
{
get { return _balance; }
// 外部からは直接セットさせず、入出金メソッドを通させる
private set { _balance = value; }
}
// コンストラクタ
public BankAccount(string accountNumber, decimal initialBalance)
{
_accountNumber = accountNumber;
_balance = initialBalance;
_totalAccountsCreated++;
}
// メソッドを通じた安全なデータ操作
public void Deposit(decimal amount)
{
if (amount <= 0) return;
Balance += amount;
Console.WriteLine($"{amount}円入金しました。現在の残高: {Balance}円");
}
public static int GetGlobalStatistics()
{
return _totalAccountsCreated;
}
}
class Program
{
static void Main()
{
var myAccount = new BankAccount("A101", 1000);
myAccount.Deposit(500);
Console.WriteLine($"口座番号: {myAccount.AccountNumber}");
Console.WriteLine($"総口座数: {BankAccount.GetGlobalStatistics()}");
}
}
}
500円入金しました。現在の残高: 1500円
口座番号: A101
総口座数: 1
この例では、口座残高(_balance)を直接書き換えられないよう private にし、入金メソッドを経由させることでデータの整合性を守っています。
これがプロフェッショナルなメンバ変数の扱い方です。
まとめ
C#におけるメンバ変数は、単にデータを格納する箱ではなく、オブジェクトの「振る舞い」を支える重要な基盤です。
本記事で解説したポイントを改めて整理します。
- カプセル化の徹底:フィールドは原則として
privateにし、外部への公開はプロパティを利用する。 - 命名規則の遵守:プライベートなメンバ変数には
_camelCaseを使用し、可読性を高める。 - 適切な修飾子の選択:変更不要な値には
readonlyを、共有データにはstaticを適切に付与する。 - プロパティの活用:バリデーションが必要な場合はバッキングフィールドを伴うプロパティを、不要な場合は自動実装プロパティを選択する。
これらのルールを守ることで、バグが少なくメンテナンスのしやすい、高品質なC#プログラムを記述できるようになります。
メンバ変数の正しい書き方をマスターすることは、C#エンジニアとしてのステップアップにおいて欠かせないプロセスです。
日々のコーディングの中で、常に「この変数のスコープと公開範囲は適切か」を意識する習慣をつけましょう。






