この記事は
C# Advent Calendar 2025 14日目の記事です。
Advent Calendar は久々に参加している最中のミズサワキヌコです。
普段はUnityを使って個人でゲーム制作もしています。よろしくお願いします。
※本記事は Chat GPT から得られた情報、構成案、本文を精査、リライトして制作しています
1. はじめに
Unity で C# を使っていると、
Transform や GameObject のように「class で作られたデータ」 と、
Vector3 や Color のように「struct で作られたデータ」 を
毎日のように扱うことになります。
なんとなく使い分けてはいるものの、
- なぜこれは class で、あれは struct なのか
- 自分でデータ型を作るとき、どちらを選ぶのが正解なのか
と聞かれると、少し言葉に詰まってしまうことはありませんか。
正直に言うと、私自身も
「値型・参照型」「コピーされる/されない」
といった断片的な知識はあっても、
実際にどういう使い分けをすべきかを説明するのは難しいと感じていました。
そこで本記事では、
私自身が理解を整理するために公式ドキュメントを読み直しつつ、
どんな考え方で class と struct を選べばよさそうか
Unity のゲーム開発という文脈でまとめてみたいと思います。
「完全な正解」を示すというよりは、
迷ったときの判断材料を増やすことを目的とした“自分用の理解メモ”です。
2. C# 公式ドキュメントから見る class と struct の違い
■違いを表でまとめてみると
Chat GPT に聞きながら公式ドキュメントを読み直してみると、
class と struct には色々な違いが書かれているものの、
「結局どこを判断軸にすればいいのか」は
最初は少し分かりにくく感じました。
そこで一度、
重要な観点になりうると思われた
違いを整理してみました。
それが以下の表になります。
| 観点 | struct | class |
|---|---|---|
| データ型 | 値型 | 参照型 |
| 代入動作 | 中身がコピーされる | 参照がコピーされる(実体共有) |
| メモリ配置 | その場で配置(連続しやすい) | ヒープ配置(参照でアクセス) |
| ライフサイクル | スコープで完結(GC 管理外) | GC による寿命管理 |
| 可変性 / 設計 | 小さく不変な値に向いてそう | 状態や振る舞いを持つ実体に向いてそう |
| 継承 | 不可 | 可能 |
以降では、それぞれ個別に見て行きます。
■ class(参照型)
class は普段何気なく使っていましたが、
「なぜ class なのか」「struct ではダメなのか」を
改めて説明しようとすると、意外と曖昧だなと感じました。
そこでまずは、C# における class の前提を整理してみます。
class として定義された型は、 "参照型" です。
実行時には、参照型の変数を宣言すると、
new 演算子を使用してクラスのインスタンスを明示的に作成するまで、
変数には値 null が格納されています。
...
オブジェクトが作成されると、その特定のオブジェクトに対してマネージド
ヒープ上で十分なメモリが割り当てられ、変数にはそのオブジェクトの場所への
参照のみが格納されます。 オブジェクトによって使用されるメモリは、
ガベージ コレクションと呼ばれる共通言語ランタイム (CLR) の自動メモリ
管理機能によって再利用されます。
参照型の変数にはデータ (オブジェクト) への参照が格納され、
値型の変数にはデータが直接含まれます。
参照型の場合、複数の変数が同じオブジェクトを参照できるため、
ある変数に対する演算によって、他の変数が参照しているオブジェクトが
影響を受ける可能性があります。
この公式ドキュメントの記載から、
参照型である C# の class の特徴は以下だと考えられそうです。
- データ(オブジェクト)を共有できる(変数は値ではなく参照のため)
- GC の管理下にある(データはGCの影響を考慮したコーディングをする必要がある)
- 可変状態を持ちやすい(データは定数のように扱わなくてもよい)
- ヒープに配置される(結果として、データは飛び飛びになりやすい)
整理してみると、
「同じデータを共有したい/状態を持ちたいもの」
は class で扱うのが自然そうだと感じました。
※Unity エンジンでの class の具体例
GameObject, MonoBehavior, Transformについて書かれた
Unity のマニュアルを見ると、 class in Unity と書いてあることから
クラスであることが分かります。
文章だけだと分かりにくいので、
「代入した時に何が起こるのか」をコードで確認してみます。
var a = someGameObject;
var b = a;
b.name = "ChangedObject";
// a.name も "ChangedObject" になる(参照型)
Debug.Log(a.name); // => "ChangedObject"
aにGameObjectを代入して、そのままbにaを代入していますが、
b.nameを変更するとa.nameも変更されます。
参照型なので、異なる変数が同じオブジェクトを指していることが分かると思います。
■ struct(値型)
struct についても、
「軽い」「高速」という何となくのイメージだけで使っていた部分があり、
class との本質的な違いを言語化できていませんでした。
そこで、値型としての struct を改めて整理してみます。
"構造体型" (または "構造体型") とは、
データおよび関連する機能をカプセル化できる値の型です。
構造体型を定義するには、struct キーワードを使用します。
値型の変数には、その型のインスタンスが含まれます。
これは、その型のインスタンスへの参照を含む参照型の変数とは異なります。
既定では、代入時、引数がメソッドに渡され、メソッドの結果が返され、
変数値がコピーされます。 値型の変数の場合、対応する型のインスタンスが
コピーされます。
この公式ドキュメントの記載から、
値型のデータである C# の構造体の特徴は以下だと考えられそうです。
- データは直接その場で配置される(変数には参照ではなく値が入るため)
- メソッドの引数や代入ではコピーして渡される(既定では、それぞれコピーで渡されるため)
- 不変として扱う前提だと使い勝手が良い(可変の場合コピーによるバグの問題があるため)
- 小さいデータほど有利(サイズが大きい場合だとコピーによるパフォーマンス問題があるため)
このあたりを踏まえると、
「軽くて、コピーされても困らないデータ」
は struct に向いていそうです。
※Unity エンジンでの struct の具体例
Unity では、Vector3 / Quaternion / Color などは struct です。
Vector3, Quaternion, Colorについて書かれた Unity のマニュアルを見ると、
struct in Unity と書いてあることから構造体であることが分かります。
struct の場合も、同じように代入して挙動を見てみます。
var a = transform.position;
var b = a;
b.x = 10;
// a は変わらない(値型)
aに代入したtransform.positionをそのままbに代入して
b.xを直接変更していますが、
a.xは変更されません。
値型なので、異なる変数が異なるオブジェクトを指していることが分かると思います。
3. パフォーマンスとメモリの観点から見た具体例
「違いはわかったけど、自作のオブジェクトを作りたい時、どう選べばいいの?」
はい。ここが本題です。
これは、Unity が提供している class や struct の
実例ベースで見るとわかりやすいようでした。
■ ケース1:Transform を struct にしてみたら?
「値の集まりなら struct でもいいのでは?」と最初は考えたのですが、
実際に中身を整理してみると、
パフォーマンスやバグの面で破綻する場面が多くなりそうだと感じました。
Transform は
- Position(位置)
- Rotation(回転)
- Scale(スケール)
- Matrix4x4(変換行列)
- 親子関係
といった「データの塊」で、軽く見積もっても100バイトを超える大きなデータです。
大きなデータは小さなデータに比べ、コピーに時間がかかります。
また、コピーが発生すると、
「どれが正しい Transform なのか」が分かりにくくなり、
意図しない更新漏れやバグの原因にもなりえます。
そもそも、オブジェクトの位置や回転、スケールといったデータは
ひとつあればそれを参照するのが自然です。
こうした理由から、Unity では Transform を参照型で定義されていると考えられます。
■ ケース2:Vector3 を class にしてみたら?
class にしても機能的には問題なさそうですが、
Unity での使われ方を考えてみると、
パフォーマンス面で不利になりそうだと分かってきました。
まず、Unity ではたくさんの Vector3 を使っていることは、
Unity を使っている方なら理解して頂けるのではないかと思います。
ここで、もしその Vector3 のデータが
ヒープ領域に細切れに点在しやすいとしたらどうなるでしょうか。
CPU はメモリを連続したまとまりとして読むのが得意ですが、
飛び飛びの位置にあるデータへアクセスする状態(ランダムアクセス)になると、
処理効率が落ちてしまうと言われています。
その結果、パフォーマンスが低下します。
C# の struct は値型であり、Microsoft の公式ドキュメントでも
「値型はデータを直接保持する」「構造体型の変数はインスタンスを含む」
と説明されています。
この性質から、struct は配列やフィールドとして使われた場合に、
参照を介さず実体が並ぶ形になりやすいと考えられます。
その結果、CPU が得意とする連続メモリアクセスになりやすく、
パフォーマンスが出やすい場面があります。
また、問題はランダムアクセスだけではありません。
ヒープのアロケーションや GC も比較的重い処理だと言われています。
こうした点を踏まえると、
「だから Vector3 は struct なのか」と自分の中では腑に落ちました。
「大量かつ軽量な値を高速に扱いたい」場合、
struct は非常に相性の良い選択肢だと言えそうです。
4. 実装で使える class / struct のチェックリスト
ここまでの内容から、class / struct の使い分けで悩んだ時に
使えるチェックリストをまとめてみます。
struct が適しているか?
- データは小さく、大量か(目安として16バイト程度12に収まり、速度を求めたいか)
- コピーされても問題ないか(変更がないか、小さいデータか)
- JobSystem や Burst, DOTSで扱いたいか3(扱いたい場合、struct にする工夫が必要)
→ 私の場合は、Yes が多いと「まず struct を疑ってみる」ようになりました。
class が適しているか?
- サイズが大きいか(16バイトを大きく超えるか)
- コピーを持ちたくないデータか(複数のオブジェクトから参照されるか)
- 設計で継承が必要か(メソッド呼び出しの単純化や処理順の固定化が求められるか)
- 変更が多いデータか(定数として扱うことは困難か)
- 大量のデータであっても、速度は妥協できるか(ヒープの飛び飛びのデータとなっても支障はないか)
- GC の対象でも構わないか(GC アロケーションの影響を考慮したコーディングが可能か)
→ 私の場合は、Yes が多いと「まず class を疑ってみる」ようになりました。
5. おわりに:結論、“コピーされても問題ないか”が大きな判断軸になりそう
本記事を執筆してみてわかったのは、 class と struct の判断でまず重視すべきは
「コピーされても問題がないかどうか」なのではないかということです。
軽い・不変・値として自然
→ コピーされても問題がないので struct の方が扱いやすそう
状態・参照共有・振る舞いを持つ
→ コピーされると問題があるので class の方が自然
一方で、 継承が必要かどうか も大きな分岐点ではないかと思います。
struct は継承ができないため、例えばメソッド呼び出しの単純化や処理順の
固定化をしたい時には少なくとも struct では難しいために
class を選ぶ場面が多くなりそうです。
一方で、自己完結した小さな値のまとまりであり、
コピーが自然で高速に扱いたいデータであれば
struct は非常に理にかなっていると言えそうです。
なお、JobSystem / Burst / DOTS のようにパフォーマンスが優先される文脈では、
「コピーしていいかどうか」よりもメモリの配置やアクセスパターンの設計そのものが
重要になりそうです。
そうした領域では、本記事のチェックリストは
「入口としての判断材料」として捉えるのが適切かもしれません。
...
と、以上が今回整理してみての結論です。
もちろん、用途や規模によって例外はいくらでもありそうですが、
少なくとも自分が迷っていたポイントについては、これらの考え方で整理できました。
本記事は前記事同様、ChatGPT と相談しながら
(少しのめり込み過ぎたかな?というくらい)書き進めましたが、
公式ドキュメントともしっかり向き合い執筆してみたおかげで、
Unity, C# における class / struct の判断に大分自信が持てるようになった気がします。
本記事が、ゲーム開発における class / struct の判断に少しでも役立てばさいわいです。
-
16バイトのデータサイズの目安として、int/float(それぞれ4バイト)だけなら4個、char(UTF-16 / 2バイト)なら8個フィールドを持てます ↩
-
https://www.slideshare.net/slideshow/unite-tokyo-2019understanding-c-struct-all-things/176223456#slide29) ↩
-
Job System, Burst, DOTSについては詳しくは触れませんが、これらは全て、メモリを効率よく使う事で高速化する技術になるようです ↩