はじめに
C#の修飾子について調べた内容の備忘録です。
学習中に調べた内容を自分用にまとめます。
internalって範囲どこまでだっけ?
overrideする時ってvirtualじゃなきゃダメなんだっけ?
と記憶が定着していなかったため、修飾子についてまとめて一覧を作成する。
修飾子クイックリファレンス
| 修飾子 | 目的 | 実務での使用例 |
|---|---|---|
public |
外部公開 | UseCase の Execute、公開 API |
private |
カプセル化 | フィールド・内部メソッド |
protected |
継承用公開 | 基底クラスのフック |
internal |
アセンブリ限定 | 同一アセンブリのユーティリティ |
sealed |
継承禁止 | Entity・UseCase 全般 |
static |
インスタンス不要 | Factory・Helper メソッド |
readonly |
不変フィールド | DI注入値・依存オブジェクト |
const |
コンパイル時定数 | アプリ設定・定数値 |
virtual |
オーバーライド許可(実装あり) | 共通処理+カスタマイズ余地がある基底クラス |
abstract |
実装強制(実装なし) | 基底 UseCase・テンプレートメソッド |
override |
親メソッドを置き換え | 継承先での実装・上書き |
async |
非同期処理 | DB・ファイル IO |
partial |
クラス分割 | WinForms Designer ファイル |
ref |
双方向の参照渡し | 呼び出し元変数を直接変更したい場面 |
out |
出力専用の参照渡し | TryXxx パターン・複数の戻り値 |
in |
読み取り専用参照渡し | 大きな struct の効率渡し |
分類と詳細
C# の修飾子は以下の 4 種類に分類できる。
| 分類 | 修飾子 |
|---|---|
| アクセス修飾子 |
public / private / protected / internal
|
| メンバー修飾子 |
static / const / readonly / async / partial
|
| 継承修飾子 |
virtual / abstract / override / sealed
|
| パラメータ修飾子 |
ref / out / in
|
アクセス修飾子
「どこからアクセスできるか」を制御する。
基本は private とし、必要なものだけ公開範囲を広げる。
| 修飾子 | 意味 | 実務用途 |
|---|---|---|
public |
どこからでもアクセス可能 | 外部公開API |
private |
同一クラスのみ | フィールド・実装 |
protected |
継承クラスのみ | 継承用メンバー |
internal |
同一アセンブリのみ | 同一アセンブリ内限定 |
protected internal |
同一アセンブリ OR 継承先 | フレームワーク拡張ポイント |
private protected |
同一アセンブリ AND 継承先 | 基底クラスの内部拡張 |
補足
protected internal = protected OR internal
private protected = protected AND internal
実務の基本ルール
基本は private
同一アセンブリ内限定は internal
外部公開するものだけ public
継承用は protected
省略時のデフォルト
修飾子を省略した場合、最も制限の強いアクセス修飾子が自動的に適用される。
| 対象 | 省略時のデフォルト |
|---|---|
| クラス・インターフェース(トップレベル) | internal |
| メンバー(フィールド・メソッド等) | private |
class Counter { } // → internal class Counter と同じ
internal class Counter { } // → 明示的に書いた場合(推奨)
internal class Product
{
int _price; // → private int _price と同じ
}
実務原則: 省略より明示的に書くほうが意図が伝わりやすため、推奨
記述例
public class Product
{
public string Name; // 外部からアクセス可能
private int _price; // クラス内部のみ
protected int Id; // 継承先のみ
internal string Code; // 同一アセンブリのみ
}
メンバー修飾子
クラス、メンバーの振る舞いや性質を制御する。
static 修飾子
インスタンスを生成せずに使用できるメンバー・クラスを定義する。
static クラス
// static クラス(インスタンス化不可)
static class MathHelper
{
public static double Pi = 3.14;
public static double Calc() => Pi;
}
static フィールド(全インスタンス共有)
static フィールドはクラスに属し、全インスタンスで値を共有する。
class Counter
{
public static int Total = 0; // 全インスタンス共有
public int Id; // インスタンスごと
public Counter()
{
Total++; // インスタンスが生成されるたびに共有カウントが増える
Id = Total; // 自分の ID は生成時の Total を使う
}
}
// 動作確認
var a = new Counter(); // Total=1, a.Id=1
var b = new Counter(); // Total=2, b.Id=2
var c = new Counter(); // Total=3, c.Id=3
Console.WriteLine(Counter.Total); // → 3(クラス名でアクセス)
Console.WriteLine(a.Id); // → 1(インスタンスごとに異なる)
Console.WriteLine(b.Id); // → 2
ポイント
Totalはどのインスタンスからアクセスしても同じ値。Idはインスタンスごとに独立。
実務用途
Utility クラス、Factory メソッド、Helper クラスに使用する。
// Entity の Factoryメソッド
public static Product Create(ProductId id, ProductName name, Money price)
{
return new Product(id, name, price);
}
const / readonly 修飾子
| 項目 | const |
readonly |
|---|---|---|
| 決定タイミング | コンパイル時(固定値のみ) | 実行時でも OK |
| 代入できる場所 | 宣言時のみ | 宣言時 + コンストラクタ内 |
暗黙的 static
|
○(インスタンス不要) | ×(インスタンスに属する) |
| 使える型 | プリミティブ・string のみ | 任意の型(クラス・構造体も可) |
| 推奨用途 | 固定の文字列・数値定数 | DI注入値・依存オブジェクト |
const の使用例
固定値が決まっている定数に使用する。コンパイル時に値が埋め込まれるため、参照アセンブリ側に変更が伝わらない点に注意。
public static class AppConstants
{
public const string AppName = "QualitySystem"; // コンパイル時定数
public const int MaxRetry = 3;
public const double Pi = 3.14159;
}
// 使用:インスタンス不要
Console.WriteLine(AppConstants.AppName);
readonly の使用例
実行時に値が確定する場合やクラス・構造体を不変で持ちたい場合、コンストラクタで依存を受け取る場合に使用する。
public class ProductService
{
private readonly ProductRepository _repository;
public ProductService(ProductRepository repository)
{
_repository = repository;
}
}
使い分けの判断基準
コンパイル時に値が確定する固定値 → const
例)ステータスコード、アプリ名、上限値の定数
実行時に確定する・クラス型を使う → readonly
例)Entity の ID、コンストラクタで受け取る依存オブジェクト
❌ const では使えないケース
public const DateTime Limit = DateTime.Now; // NG: 実行時値は使えない
public const ProductId Id = new ProductId(); // NG: クラス型は使えない
async 修飾子
DB アクセス・ファイル処理などの I/O 待機処理に使用する。
async と await は常にセットで使用する。(await が無い場合は警告になる)
await は演算子(operator)。非同期処理の完了を待機して結果を取得するためのキーワード。
public async Task<string> FetchAsync()
{
// await で結果を直接受け取る
return await httpClient.GetStringAsync(url);
}
public async Task Execute()
{
// 完了を待つ
await Task.Delay(1000);
}
partial 修飾子
クラスを複数ファイルに分割できる。
WinForms の Designer ファイルで自動使用されている。
// FrmMain.cs(ロジック)
public partial class FrmMain : Form
{
// 自分で書くコード
}
// FrmMain.Designer.cs(自動生成)
public partial class FrmMain
{
// Visual Studio が自動生成するコード
}
継承修飾子
クラスやメソッドの継承・拡張方法を制御する。
virtual(親クラス)
サブクラスでオーバーライド可能なメソッドを定義する。
デフォルト実装を持ち、override は任意。
public class ReportBase
{
public virtual void Print()
{
Console.WriteLine("基本レポート"); // 実装あり
}
}
// 子クラスは override してもしなくても OK
public class DetailReport : ReportBase
{
public override void Print()
{
Console.WriteLine("詳細レポート"); // 上書き
}
}
abstract(抽象)
実装なし、override を強制する。
クラス、メソッド共に abstract にしなければならない。
インスタンス化不可。継承先で実装しないとコンパイルエラーになる。
public abstract class UseCase
{
public abstract void Execute(); // 実装なし、継承先で必ず実装
}
public class SaveProductUseCase : UseCase
{
public override void Execute() // 実装しないとコンパイルエラー
{
}
}
virtual abstract比較
virtual |
abstract |
|
|---|---|---|
| 実装 | あり(デフォルト動作) | なし |
| override | 任意 | 強制(しないとエラー) |
| インスタンス化 | ○ | × |
| 用途 | 共通処理+カスタマイズ余地 | 実装を強制したいテンプレート |
override(子クラス)
親クラスの virtual または abstract メソッドを置き換える。
親の実装は完全に上書きされるが、base.メソッド() で親の処理を呼び出すことも可能。
public class DetailReport : ReportBase
{
public override void Print()
{
base.Print(); // 親の処理を呼ぶ(任意)
Console.WriteLine("詳細レポート追加"); // 追加処理
}
}
var report = new DetailReport();
report.Print();
// → 基本レポート
// → 詳細レポート追加
継承した場合 と override した場合のコード例
// 親クラス
public class ReportBase
{
//virtualでoverride可能にする
public virtual void Print()
{
Console.WriteLine("基本レポート");
}
}
// override なし(親のメソッドをそのまま使う)
public class ReportA : ReportBase { }
// override あり(親のメソッドを上書き)
public class ReportB : ReportBase
{
public override void Print()
{
Console.WriteLine("ReportB のレポート");
}
}
// base あり(親の処理を呼んだうえで追加)
public class ReportC : ReportBase
{
public override void Print()
{
base.Print();
Console.WriteLine("ReportC の追加情報");
}
}
new ReportA().Print(); // → 基本レポート (親のまま)
new ReportB().Print(); // → ReportB のレポート (完全に上書き)
new ReportC().Print(); // → 基本レポート (親 → 子の順)
// → ReportC の追加情報
sealed(継承禁止)
クラスやメソッドの 継承・オーバーライドを禁止する。
設計上の意図しない拡張を防ぎ、クラスの振る舞いを固定する目的で使用する。
Entity・UseCase への使用を推奨。
public sealed class SaveProductUseCase // このクラスは継承できない
{
// ...
}
// override メソッドに sealed を付けると、それ以上のメソッドの継承だけ禁止
public class Base
{
public virtual void Execute() { }
}
public class Child : Base
{
public sealed override void Execute() { } // これ以上のオーバーライド不可
}
パラメータ修飾子
メソッド引数の値の渡し方を制御する。
通常のメソッド引数は値渡し(コピーが渡される)だが、ref / out / in を使うと参照渡しになる。
比較表
ref |
out |
in |
|
|---|---|---|---|
| 渡す前の初期化 | 必須 | 不要 | 必須 |
| メソッド内での代入 | 任意 | 必須 | NG(読み取り専用) |
| 読み取り | ○ | ○(代入後) | ○ |
| 書き込み | ○ | ○ | × |
| 主な用途 | 双方向の値変更 | 複数の戻り値 | 大きな構造体の効率渡し |
ref(読み書き可能)
呼び出し元の変数を直接変更します。渡す前に必ず初期化が必要です。
void Swap(ref int a, ref int b)
{
int tmp = a;
a = b;
b = tmp;
}
// 呼び出し側にも ref が必要
int x = 10, y = 20;
Swap(ref x, ref y);
Console.WriteLine(x); // → 20
Console.WriteLine(y); // → 10
使う場面
使う場面は限定的。戻り値で返せるなら戻り値を優先する。
out(出力専用)
メソッドから複数の値を返すために使います。渡す前の初期化は不要ですが、メソッド内で必ず代入する必要があります。
// 標準ライブラリの典型例
bool ok = int.TryParse("123", out int result);
// ok → true
// result → 123
// 自分で定義する場合
bool TryGetUser(int id, out string name, out string email)
{
if (id == 1)
{
name = "田中";
email = "tanaka@example.com";
return true;
}
name = ""; // out は必ず代入が必要
email = "";
return false;
}
// 使用
if (TryGetUser(1, out string name, out string email))
{
Console.WriteLine(name); // → 田中
}
// 不要な out 引数はディスカード(_)で無視できる
bool ok2 = int.TryParse("abc", out _);
使う場面
TryXxxパターンで処理の成否(bool)+結果値を同時に返したい場面で使う。
Resultでいいような気がする。
in(読み取り専用参照)
参照渡しだが変更不可。
大きな struct をコピーせずに渡す際のパフォーマンス最適化が主目的。
struct BigData
{
public double A, B, C, D; // 大きな構造体
}
void Process(in BigData data)
{
Console.WriteLine(data.A); // 読み取りのみ OK
// data.A = 1.0; // NG: コンパイルエラー
}
var d = new BigData { A = 1.0 };
Process(in d); // コピーなしで渡せる(呼び出し側の in は省略可)
使う場面
通常のクラス(参照型)には効果なし。大きなstruct(値型)を扱う場面で検討する。
まとめ
C# の修飾子について整理しました。
適切な修飾子を付けることで、コードの公開範囲や設計意図が明確になり、コードを読む人にも意図が伝わりやすくなると思います。
修飾子に迷ったときは「何のために使うか」を意識すると整理しやすい。
- 公開範囲を絞る → アクセス修飾子(基本は
private、必要な分だけ広げる) - 値を守る →
readonly/const - 継承を制御する →
virtual/abstract/sealed - 振る舞いを変える →
static/async/partial
実務ではまず private / internal / public を使い分け、
継承が必要な場合のみ protected を検討するのが基本。
private protected は基底クラスの拡張でたまに使用し、
protected internal は実務ではほとんど使わない。
DIで受け取る依存オブジェクトなど、値を変更させたくないフィールドには readonly を使用し、固定値には const を使用する。
オーバーライド可能にしたい場合は virtual、
オーバーライドを強制したい場合は abstract、
継承させたくないクラスには sealed を使用する。
この記事は自分用の備忘録ですが、同じく C# を学習している方の参考になれば嬉しいです。