C#のVector完全ガイド|配列・Listとの違いから高速なベクトル計算の実装までわかりやすく解説
はじめに
C#で「Vector」と検索すると、配列のようなデータ構造を知りたい人、ゲーム開発で使う座標ベクトルを知りたい人、SIMDを使った高速な数値計算を知りたい人が混在します。
特に混乱しやすいのは、C++のvector、C#のList<T>、System.Numerics.Vector2、System.Numerics.Vector3、System.Numerics.Vector<T>、UnityのVector3がそれぞれ別の意味を持つ点です。
この記事では、C#におけるVectorの意味を整理し、配列・Listとの違い、Vector2・Vector3・Vector4の基本的な使い方、さらにVector<T>を使った高速なベクトル計算までを順番に解説します。
1. C#のVectorとは?検索ユーザーが混同しやすい3つの意味を整理
C#で「Vector」と言う場合、主に次の3つの意味があります。
1つ目は、数学的なベクトルを表すVector2、Vector3、Vector4です。これは座標、方向、速度、法線などを表すときに使います。
2つ目は、SIMDによる高速な数値配列処理に使うVector<T>です。これは複数の数値をまとめて一度に計算するための型です。
3つ目は、C++のstd::vectorのような「可変長配列」をイメージしているケースです。ただし、C#でその用途に近いのは通常List<T>です。
1-1. C#における「Vector」はC++のvectorとは別物
C++のstd::vectorは、要素数を増減できる動的配列です。一方、C#でVectorという名前が出てきた場合、多くは数学的なベクトルやSIMD用の数値型を指します。
C#でC++のvectorに近いものを使いたいなら、基本的にはList<T>を使います。
C#List<int> numbers = new List<int>();
numbers.Add(10);
numbers.Add(20);
numbers.Add(30);
Console.WriteLine(numbers[0]); // 10
このように、要素を追加・削除したい場合はVectorではなくList<T>を選ぶのが自然です。
1-2. System.Numerics.Vector2・Vector3・Vector4とは
System.Numerics.Vector2、Vector3、Vector4は、それぞれ2次元、3次元、4次元のベクトルを表す構造体です。Microsoftのドキュメントでも、Vector2、Vector3、Vector4は2個・3個・4個のSingle、つまりfloat値を持つベクトル型として説明されています。
たとえば、2Dゲームの座標ならVector2、3D空間の位置や方向ならVector3、グラフィックス処理や同次座標のような4成分データならVector4を使います。
C#using System.Numerics;
Vector2 position2D = new Vector2(10f, 20f);
Vector3 position3D = new Vector3(10f, 20f, 30f);
Vector4 colorLikeData = new Vector4(1f, 0.5f, 0.2f, 1f);
1-3. System.Numerics.Vector<T>とは
System.Numerics.Vector<T>は、float、double、intなどの数値を複数まとめて扱い、SIMDによって高速化を狙うための型です。
たとえば、配列の要素を1つずつ足す代わりに、複数要素をまとめて足すことができます。Vector<T>.Countで一度に扱える要素数を確認できますが、この値は実行環境のCPUなどに依存します。MicrosoftのSIMD解説でも、Vector<T>の要素数はアプリケーション実行中は固定であり、その値はコードを実行するマシンのCPUに依存すると説明されています。
C#using System.Numerics;
Console.WriteLine(Vector<float>.Count);
Console.WriteLine(Vector<double>.Count);
1-4. UnityのVector3との違い
UnityにもVector3がありますが、これはUnityEngine.Vector3です。通常のC#で使うSystem.Numerics.Vector3とは名前が似ているだけで、別の型です。
UnityのVector3はUnityEngine名前空間にあり、Unity内で3Dの位置や方向を扱うために広く使われます。Unity公式ドキュメントでも、Vector3は3Dベクトルと点の表現であり、3Dの位置や方向を渡すために使われる構造体と説明されています。
C#// 通常のC#
using System.Numerics;
Vector3 v1 = new Vector3(1f, 2f, 3f);
// Unity
// using UnityEngine;
// Vector3 v2 = new Vector3(1f, 2f, 3f);
UnityプロジェクトではSystem.Numerics.Vector3とUnityEngine.Vector3が衝突しやすいため、どちらを使っているのかを明確にする必要があります。
1-5. 配列・List・Vectorのどれを使うべきかを最初に判断する
C#で数値や座標を扱うときは、最初に目的を整理すると迷いにくくなります。
要素数が固定で、単純にデータを並べたいなら配列を使います。要素数を増減したいならList<T>を使います。座標や方向を表したいならVector2やVector3を使います。大量の数値配列を高速に処理したいならVector<T>を検討します。
「Vector」という名前だけで判断するのではなく、「データの入れ物が欲しいのか」「数学的なベクトルを扱いたいのか」「高速な数値処理をしたいのか」を先に決めることが重要です。
2. C#のVectorと配列・Listの違い
2-1. 配列とは:固定長で高速に扱える基本データ構造
配列は、同じ型の値を連続して格納する基本的なデータ構造です。作成時に要素数が決まり、後から長さを変更することはできません。
C#int[] scores = new int[3];
scores[0] = 80;
scores[1] = 90;
scores[2] = 100;
配列は構造が単純で、インデックスによるアクセスも高速です。要素数が最初から決まっている場合や、パフォーマンスを重視したい場合に向いています。
2-2. List<T>とは:可変長で要素の追加・削除に向いたコレクション
List<T>は、要素数を動的に増減できるコレクションです。C++のvectorに近い使い方をしたい場合、C#ではList<T>を選ぶことが多いです。
C#List<string> names = new List<string>();
names.Add("Alice");
names.Add("Bob");
names.Remove("Alice");
要素の追加や削除が必要な場合は、配列よりもList<T>のほうが扱いやすくなります。
2-3. Vectorとは:数値計算や座標計算に向いた型
C#のVector2、Vector3、Vector4は、数値をまとめて持つだけでなく、ベクトル演算を自然に書ける型です。
C#Vector3 a = new Vector3(1f, 2f, 3f);
Vector3 b = new Vector3(4f, 5f, 6f);
Vector3 sum = a + b;
float dot = Vector3.Dot(a, b);
配列でも似たようなデータは表現できますが、ベクトル演算の意味がコード上で分かりにくくなります。
C#float[] a = { 1f, 2f, 3f };
float[] b = { 4f, 5f, 6f };
// 何をしているのかがコードだけでは分かりにくい
float dot = a[0] * b[0] + a[1] * b[1] + a[2] * b[2];
座標、方向、速度などを表すなら、配列よりVector2やVector3のほうが読みやすくなります。
2-4. 配列・List・Vectorの比較表
| 種類 | 主な用途 | 長さの変更 | 得意な処理 | 例 |
|---|---|---|---|---|
| 配列 | 固定長データの管理 | 不可 | 高速なインデックスアクセス | int[]、float[] |
List<T> | 可変長データの管理 | 可能 | 要素の追加・削除 | List<int> |
Vector2 / Vector3 / Vector4 | 座標・方向・速度 | 固定 | ベクトル演算 | Vector3 position |
Vector<T> | 数値配列の高速処理 | 固定 | SIMDによる一括計算 | Vector<float> |
この表から分かるように、Vectorは配列やList<T>の完全な代替ではありません。目的が違います。
2-5. 「データの入れ物」と「数学的なベクトル」の違い
配列やList<T>は、複数のデータを入れるための「入れ物」です。一方、Vector2やVector3は、方向や大きさを持つ数学的なベクトルを表すための型です。
たとえば、3人分の点数を入れるなら配列やList<int>が適しています。
C#int[] scores = { 80, 90, 100 };
一方、3D空間上の位置を表すならVector3が適しています。
C#Vector3 position = new Vector3(10f, 5f, 2f);
どちらも複数の値を持ちますが、意味がまったく違います。
2-6. 初心者が間違えやすい使い分け
初心者がよく間違えるのは、「複数の値を入れたいからVectorを使う」と考えてしまうことです。
複数のデータを並べたいだけなら、配列やList<T>を使うべきです。Vector2やVector3は、2次元・3次元の座標や方向など、値の組み合わせ自体に数学的な意味があるときに使います。
また、Vector<T>は通常のコレクションではありません。List<T>のようにAddで要素を増やす型ではなく、SIMD計算のために一定数の数値をまとめて扱う型です。
3. C#で使える主なVector型
3-1. Vector2:2次元座標や速度ベクトルに使う
Vector2は、XとYの2つの成分を持つベクトルです。2Dゲーム、UI座標、画面上の移動方向などに向いています。
C#using System.Numerics;
Vector2 position = new Vector2(100f, 50f);
Vector2 velocity = new Vector2(2f, 0f);
position += velocity;
2D空間で「位置」「速度」「方向」を扱うなら、まずVector2を考えるとよいでしょう。
3-2. Vector3:3D座標・方向・法線ベクトルに使う
Vector3は、X、Y、Zの3つの成分を持つベクトルです。3D空間の位置、向き、速度、法線などを表すときに使います。
Microsoftのドキュメントでも、System.Numerics.Vector3は3つの単精度浮動小数点値を持つベクターとして定義されています。
C#Vector3 position = new Vector3(0f, 1f, 5f);
Vector3 forward = new Vector3(0f, 0f, 1f);
3Dアプリケーション、物理計算、カメラ制御、3Dゲームの基礎になる型です。
3-3. Vector4:4次元データやグラフィックス処理に使う
Vector4は、X、Y、Z、Wの4つの成分を持つベクトルです。3Dグラフィックスでは、同次座標、色、シェーダー向けのデータなどで使われることがあります。
C#Vector4 color = new Vector4(1f, 0f, 0f, 1f); // R, G, B, A のように扱う例
Vector4 point = new Vector4(1f, 2f, 3f, 1f);
通常のアプリケーション開発ではVector2やVector3ほど頻繁には使いませんが、グラフィックス処理では重要です。
3-4. Vector<T>:SIMDによる高速な数値配列処理に使う
Vector<T>は、Vector2やVector3とは用途が大きく異なります。座標を表すためではなく、配列内の複数の数値をまとめて計算するために使います。
C#using System.Numerics;
float[] a = { 1, 2, 3, 4, 5, 6, 7, 8 };
float[] b = { 10, 20, 30, 40, 50, 60, 70, 80 };
var va = new Vector<float>(a);
var vb = new Vector<float>(b);
Vector<float> result = va + vb;
大量の数値データを処理する場面では、通常のfor文より速くなる可能性があります。
3-5. Matrix・Quaternionなど関連するSystem.Numerics型
System.Numericsには、Vector以外にも数学・グラフィックス向けの型があります。
代表的なものは次のとおりです。
| 型 | 用途 |
|---|---|
Matrix3x2 | 2D変換行列 |
Matrix4x4 | 3D変換行列 |
Quaternion | 3D回転 |
Plane | 3D空間内の平面 |
MicrosoftのSIMD関連ドキュメントでも、Vector2、Vector3、Vector4に加えて、Matrix3x2、Matrix4x4、Plane、QuaternionなどがSIMDアクセラレーション型として挙げられています。
3-6. 用途別に選ぶべきVector型
2Dの位置や速度を扱うならVector2、3D空間の位置や方向を扱うならVector3、4成分のグラフィックスデータを扱うならVector4を使います。
大量のfloatやdouble配列を高速に加算・乗算したい場合はVector<T>を使います。
つまり、同じVectorという名前でも、次のように使い分けます。
| やりたいこと | 選ぶ型 |
|---|---|
| 2D座標を表す | Vector2 |
| 3D座標を表す | Vector3 |
| 4成分データを表す | Vector4 |
| 大量の数値配列を高速処理する | Vector<T> |
| 要素を自由に追加したい | List<T> |
| 固定長の値を並べたい | 配列 |
4. C#でVectorを使う準備
4-1. System.Numericsをusingする
C#でVector2、Vector3、Vector4、Vector<T>を使うには、基本的にSystem.Numerics名前空間を使用します。
C#using System;
using System.Numerics;
class Program
{
static void Main()
{
Vector3 v = new Vector3(1f, 2f, 3f);
Console.WriteLine(v);
}
}
using System.Numerics;を書かない場合は、完全修飾名で書くこともできます。
C#System.Numerics.Vector3 v = new System.Numerics.Vector3(1f, 2f, 3f);
4-2. .NETのバージョンと対応環境を確認する
現在の.NETでは、System.Numericsの多くの型を標準的に利用できます。ただし、古いプロジェクトや.NET Frameworkのプロジェクトでは、参照設定やNuGetパッケージが必要になることがあります。
特にVector<T>については、MicrosoftのSIMDドキュメントで、.NET Frameworkには含まれないためSystem.Numerics.Vectors NuGetパッケージのインストールが必要と説明されています。
新しい.NETプロジェクトであれば、まずusing System.Numerics;を追加して使えるか確認しましょう。
4-3. NuGetパッケージが必要になるケース
古いプロジェクトでVector2やVector3が見つからない場合は、System.Numerics.Vectorsパッケージが必要になることがあります。
Visual Studioなら、次の手順で確認できます。
プロジェクトを右クリックする
「NuGet パッケージの管理」を開く
System.Numerics.Vectorsを検索する必要に応じてインストールする
.NET CLIを使う場合は、次のように追加できます。
Bashdotnet add package System.Numerics.Vectors
ただし、すべての環境で必ず必要というわけではありません。まずはプロジェクトのターゲットフレームワークを確認しましょう。
4-4. Visual StudioでVector型が使えないときの確認ポイント
Visual StudioでVector3などが使えない場合は、次の点を確認します。
まず、using System.Numerics;を書いているか確認します。次に、プロジェクトのターゲットフレームワークが古すぎないかを確認します。さらに、必要であればSystem.Numerics.Vectorsパッケージが入っているか確認します。
また、Unityプロジェクトの場合は、System.Numerics.Vector3ではなくUnityEngine.Vector3を使うケースが多いため、名前空間の衝突にも注意が必要です。
4-5. Unityで使うVectorと通常のC#で使うVectorの違い
Unityでは、基本的にUnityEngine.Vector2、UnityEngine.Vector3、UnityEngine.Vector4を使います。
通常のC#で使うSystem.Numerics.Vector3とは別物なので、次のように名前空間が異なります。
C#// 通常のC#
System.Numerics.Vector3 numericsVector;
// Unity
// UnityEngine.Vector3 unityVector;
Unity内のTransform.positionやRigidbody.velocityなどはUnityEngine.Vector3を使います。そのため、Unityで開発している場合は、まずUnity側のVectorを学ぶほうが実践的です。
5. Vector2・Vector3・Vector4の基本的な使い方
5-1. Vector2の生成方法
Vector2は、XとYの2つの値を渡して作成します。
C#using System.Numerics;
Vector2 position = new Vector2(10f, 20f);
Vector2 zero = Vector2.Zero;
Vector2 one = Vector2.One;
Vector2.Zeroは(0, 0)、Vector2.Oneは(1, 1)を表します。
5-2. Vector3の生成方法
Vector3は、X、Y、Zの3つの値を渡して作成します。
C#Vector3 position = new Vector3(10f, 20f, 30f);
Vector3 zero = Vector3.Zero;
Vector3 unitX = Vector3.UnitX;
Vector3 unitY = Vector3.UnitY;
Vector3 unitZ = Vector3.UnitZ;
UnitX、UnitY、UnitZは、それぞれX軸、Y軸、Z軸方向の単位ベクトルです。
5-3. X・Y・Z・W成分へのアクセス
各成分には、X、Y、Z、Wプロパティでアクセスします。
C#Vector4 v = new Vector4(1f, 2f, 3f, 4f);
Console.WriteLine(v.X); // 1
Console.WriteLine(v.Y); // 2
Console.WriteLine(v.Z); // 3
Console.WriteLine(v.W); // 4
v.X = 10f;
Vector2にはXとY、Vector3にはX、Y、Z、Vector4にはX、Y、Z、Wがあります。
5-4. 足し算・引き算・スカラー倍
ベクトル同士は、直感的に足し算や引き算ができます。
C#Vector3 a = new Vector3(1f, 2f, 3f);
Vector3 b = new Vector3(4f, 5f, 6f);
Vector3 sum = a + b; // (5, 7, 9)
Vector3 diff = b - a; // (3, 3, 3)
Vector3 scaled = a * 2f; // (2, 4, 6)
Vector3 divided = b / 2f; // (2, 2.5, 3)
移動量、速度、方向の計算では、このような演算を頻繁に使います。
5-5. 長さを求めるLengthとLengthSquared
ベクトルの長さはLength()で求められます。
C#Vector3 v = new Vector3(3f, 4f, 0f);
float length = v.Length(); // 5
長さの2乗はLengthSquared()で求められます。
C#float lengthSquared = v.LengthSquared(); // 25
距離の比較だけをしたい場合は、平方根計算を避けられるLengthSquared()のほうが効率的です。
5-6. 正規化Normalizeの使い方
正規化とは、ベクトルの向きを保ったまま長さを1にする処理です。方向だけが欲しいときに使います。
C#Vector3 v = new Vector3(10f, 0f, 0f);
Vector3 normalized = Vector3.Normalize(v);
Console.WriteLine(normalized); // <1, 0, 0>
ただし、長さが0のベクトルを正規化すると、計算結果が不正になることがあります。実装では、長さが十分に大きいか確認してから正規化するのが安全です。
C#static Vector3 SafeNormalize(Vector3 v)
{
if (v.LengthSquared() < 0.000001f)
{
return Vector3.Zero;
}
return Vector3.Normalize(v);
}
5-7. 距離を求めるDistanceの使い方
2点間の距離はVector3.Distanceで求められます。
C#Vector3 a = new Vector3(0f, 0f, 0f);
Vector3 b = new Vector3(3f, 4f, 0f);
float distance = Vector3.Distance(a, b); // 5
距離の比較だけなら、DistanceSquaredを使うと平方根計算を避けられます。
C#float distanceSquared = Vector3.DistanceSquared(a, b);
if (distanceSquared < 100f)
{
Console.WriteLine("距離10未満です");
}
6. ベクトル計算でよく使う演算
6-1. 内積Dotとは
内積は、2つのベクトルがどれくらい同じ方向を向いているかを調べるために使います。
C#Vector3 forward = new Vector3(0f, 0f, 1f);
Vector3 targetDirection = new Vector3(0f, 0f, 1f);
float dot = Vector3.Dot(forward, targetDirection);
正規化されたベクトル同士の内積は、向きの一致度として使えます。
| 内積の値 | 意味 |
|---|---|
| 1に近い | ほぼ同じ方向 |
| 0に近い | ほぼ直角 |
| -1に近い | ほぼ反対方向 |
視野判定や前方判定でよく使われます。
6-2. 外積Crossとは
外積は、2つのベクトルに垂直なベクトルを求める演算です。3D空間で法線ベクトルを求めたり、左右どちらにあるかを判定したりするときに使います。
C#Vector3 right = new Vector3(1f, 0f, 0f);
Vector3 up = new Vector3(0f, 1f, 0f);
Vector3 forward = Vector3.Cross(right, up);
Console.WriteLine(forward); // <0, 0, 1>
System.Numerics.Vector3には、ドット積を返すDotや、クロス積を計算するCrossなどのメソッドが用意されています。
6-3. 角度を求める計算
System.Numerics.Vector3にはUnityのVector3.Angleのような角度専用メソッドはありません。そのため、内積を使って角度を求めます。
C#static float AngleBetween(Vector3 a, Vector3 b)
{
Vector3 na = Vector3.Normalize(a);
Vector3 nb = Vector3.Normalize(b);
float dot = Vector3.Dot(na, nb);
dot = Math.Clamp(dot, -1f, 1f);
float radians = MathF.Acos(dot);
return radians * 180f / MathF.PI;
}
Math.Clampで値を-1から1に制限しているのは、浮動小数点誤差でAcosに不正な値が渡るのを防ぐためです。
6-4. 方向ベクトルを求める計算
ある点から別の点へ向かう方向は、移動先から現在位置を引いて求めます。
C#Vector3 current = new Vector3(0f, 0f, 0f);
Vector3 target = new Vector3(10f, 0f, 0f);
Vector3 direction = target - current;
direction = Vector3.Normalize(direction);
このdirectionは、現在位置から目標位置へ向かう単位ベクトルになります。
6-5. 単位ベクトルを使う場面
単位ベクトルとは、長さが1のベクトルです。方向だけを表したいときに使います。
たとえば、移動速度を一定にしたい場合、方向ベクトルを正規化してから速度を掛けます。
C#Vector3 direction = Vector3.Normalize(target - position);
float speed = 5f;
position += direction * speed * deltaTime;
正規化しないまま使うと、目標までの距離によって移動速度が変わってしまうことがあります。
6-6. ベクトルの補間Lerp
Lerpは、2つのベクトルの間を線形補間するために使います。
C#Vector3 start = new Vector3(0f, 0f, 0f);
Vector3 end = new Vector3(10f, 0f, 0f);
Vector3 middle = Vector3.Lerp(start, end, 0.5f);
Console.WriteLine(middle); // <5, 0, 0>
第3引数が0なら開始点、1なら終了点、0.5なら中間地点になります。
6-7. 座標変換に使うTransform
Transformは、ベクトルに行列などの変換を適用するときに使います。
C#Matrix4x4 matrix = Matrix4x4.CreateTranslation(10f, 0f, 0f);
Vector3 point = new Vector3(1f, 2f, 3f);
Vector3 transformed = Vector3.Transform(point, matrix);
Console.WriteLine(transformed); // <11, 2, 3>
平行移動、回転、拡大縮小などを扱う3D処理では、Vector3とMatrix4x4を組み合わせる場面が多くあります。
7. 実装例で学ぶC#のVector計算
7-1. 2点間の距離を求める
2点間の距離は、Vector2.DistanceやVector3.Distanceで簡単に求められます。
C#using System;
using System.Numerics;
class Program
{
static void Main()
{
Vector2 a = new Vector2(0f, 0f);
Vector2 b = new Vector2(3f, 4f);
float distance = Vector2.Distance(a, b);
Console.WriteLine(distance); // 5
}
}
距離判定だけならDistanceSquaredを使うと効率的です。
C#float range = 10f;
float rangeSquared = range * range;
if (Vector2.DistanceSquared(a, b) <= rangeSquared)
{
Console.WriteLine("範囲内です");
}
7-2. オブジェクトを指定方向に移動させる
方向ベクトル、速度、経過時間を使うと、一定速度で移動できます。
C#Vector2 position = new Vector2(0f, 0f);
Vector2 direction = new Vector2(1f, 1f);
direction = Vector2.Normalize(direction);
float speed = 3f;
float deltaTime = 0.016f;
position += direction * speed * deltaTime;
斜め移動で速度が速くなってしまう問題を避けるため、方向ベクトルを正規化してから使います。
7-3. 速度ベクトルから位置を更新する
位置と速度をベクトルで持つと、移動処理を簡潔に書けます。
C#Vector2 position = new Vector2(0f, 0f);
Vector2 velocity = new Vector2(5f, 0f);
float deltaTime = 0.016f;
position += velocity * deltaTime;
これは「位置 = 位置 + 速度 × 時間」という基本的な更新式です。
7-4. 2Dゲームの移動処理を実装する
入力方向をVector2で表すと、上下左右や斜め移動をまとめて扱えます。
C#static Vector2 Move(Vector2 position, bool up, bool down, bool left, bool right, float speed, float deltaTime)
{
Vector2 input = Vector2.Zero;
if (up) input.Y += 1f;
if (down) input.Y -= 1f;
if (right) input.X += 1f;
if (left) input.X -= 1f;
if (input.LengthSquared() > 0f)
{
input = Vector2.Normalize(input);
}
return position + input * speed * deltaTime;
}
入力がないときにNormalizeしないようにしている点が重要です。
7-5. 3D空間で向きベクトルを計算する
3D空間である位置からターゲット方向を向きたい場合は、次のように方向ベクトルを求めます。
C#Vector3 position = new Vector3(0f, 0f, 0f);
Vector3 target = new Vector3(10f, 2f, 5f);
Vector3 toTarget = target - position;
if (toTarget.LengthSquared() > 0.000001f)
{
Vector3 direction = Vector3.Normalize(toTarget);
Console.WriteLine(direction);
}
このdirectionを使えば、前方判定、弾の発射方向、カメラ方向などを実装できます。
7-6. 当たり判定にVectorを使う
円同士、または球同士の簡単な当たり判定は、距離を使って実装できます。
C#static bool IsCircleHit(Vector2 aPos, float aRadius, Vector2 bPos, float bRadius)
{
float radiusSum = aRadius + bRadius;
float distanceSquared = Vector2.DistanceSquared(aPos, bPos);
return distanceSquared <= radiusSum * radiusSum;
}
平方根を使うDistanceではなく、DistanceSquaredを使うことで無駄な計算を避けています。
3Dの球判定も同じ考え方です。
C#static bool IsSphereHit(Vector3 aPos, float aRadius, Vector3 bPos, float bRadius)
{
float radiusSum = aRadius + bRadius;
float distanceSquared = Vector3.DistanceSquared(aPos, bPos);
return distanceSquared <= radiusSum * radiusSum;
}
7-7. ベクトル演算を関数化して再利用する
よく使うベクトル計算は、関数化しておくと再利用しやすくなります。
C#static Vector3 DirectionTo(Vector3 from, Vector3 to)
{
Vector3 diff = to - from;
if (diff.LengthSquared() < 0.000001f)
{
return Vector3.Zero;
}
return Vector3.Normalize(diff);
}
static Vector3 MoveTowards(Vector3 current, Vector3 target, float maxDistanceDelta)
{
Vector3 diff = target - current;
float distance = diff.Length();
if (distance <= maxDistanceDelta || distance < 0.000001f)
{
return target;
}
return current + diff / distance * maxDistanceDelta;
}
このように関数化すると、ゲーム、シミュレーション、ツール開発などで同じ処理を安全に使い回せます。
8. System.Numerics.Vector<T>で高速なベクトル計算を行う
8-1. Vector<T>とは何か
Vector<T>は、複数の数値をまとめて保持し、同じ演算を一括で適用するための型です。
たとえば、配列Aと配列Bの各要素を足して配列Cに入れる処理を考えます。
C#for (int i = 0; i < a.Length; i++)
{
c[i] = a[i] + b[i];
}
Vector<T>を使うと、複数要素をまとめて処理できます。
C#Vector<float> va = new Vector<float>(a, i);
Vector<float> vb = new Vector<float>(b, i);
Vector<float> vc = va + vb;
vc.CopyTo(c, i);
8-2. SIMDとは何か
SIMDは、Single Instruction Multiple Dataの略です。1つの命令で複数のデータを同時に処理する仕組みです。
通常の処理では、配列の要素を1つずつ足します。一方、SIMDでは複数の要素をまとめて足せるため、大量の数値計算で高速化できる可能性があります。
.NETのSIMD型は、SIMD非対応のハードウェアやJITでも使えるように実装されています。実行時にSIMDアクセラレーションが利用できるかは、Vector.IsHardwareAcceleratedで確認できます。
C#Console.WriteLine(Vector.IsHardwareAccelerated);
8-3. Vector<T>で配列の加算を高速化する
次は、float配列同士を加算する例です。
C#using System;
using System.Numerics;
static void AddArrays(float[] left, float[] right, float[] result)
{
if (left.Length != right.Length || left.Length != result.Length)
{
throw new ArgumentException("配列の長さが一致していません。");
}
int count = Vector<float>.Count;
int i = 0;
for (; i <= left.Length - count; i += count)
{
var v1 = new Vector<float>(left, i);
var v2 = new Vector<float>(right, i);
(v1 + v2).CopyTo(result, i);
}
for (; i < left.Length; i++)
{
result[i] = left[i] + right[i];
}
}
前半のループではVector<float>.Count個ずつまとめて加算し、後半のループでは余った要素を1つずつ処理しています。
8-4. Vector<T>.Countで一度に処理できる要素数を確認する
Vector<T>.Countは、1つのVector<T>に入る要素数を表します。
C#Console.WriteLine(Vector<float>.Count);
Console.WriteLine(Vector<double>.Count);
Console.WriteLine(Vector<int>.Count);
この値は型や実行環境によって変わります。そのため、4や8のような固定値を前提にせず、必ずVector<T>.Countを使ってループを書くのが基本です。
8-5. for文による通常処理との違い
通常のfor文は、要素を1つずつ処理します。
C#for (int i = 0; i < length; i++)
{
result[i] = left[i] + right[i];
}
Vector<T>を使う処理では、複数要素をまとめて処理します。
C#for (int i = 0; i <= length - Vector<float>.Count; i += Vector<float>.Count)
{
var v1 = new Vector<float>(left, i);
var v2 = new Vector<float>(right, i);
(v1 + v2).CopyTo(result, i);
}
大量データで同じ演算を繰り返す場合、Vector<T>のほうが有利になる可能性があります。
8-6. 余った要素を処理する実装パターン
配列の長さがVector<T>.Countで割り切れるとは限りません。そのため、最後に余った要素を通常のfor文で処理します。
C#int count = Vector<float>.Count;
int i = 0;
for (; i <= values.Length - count; i += count)
{
var v = new Vector<float>(values, i);
var doubled = v * 2f;
doubled.CopyTo(values, i);
}
for (; i < values.Length; i++)
{
values[i] *= 2f;
}
このパターンは、Vector<T>を使うときの基本形です。
8-7. float・double・intで使う場合の注意点
Vector<T>は数値型に対して使いますが、型によって一度に処理できる要素数や演算の性質が変わります。
floatは1要素あたり4バイトなので、doubleより多くの要素を一度に処理できることがあります。doubleは精度が高い一方で、処理できる要素数は少なくなりやすいです。intは整数演算に使えますが、浮動小数点のような正規化や距離計算にはそのまま向きません。
また、型変換を暗黙に期待するとコンパイルエラーになることがあります。
C#Vector<float> vf = new Vector<float>(1f);
// Vector<double> vd = vf; // これはできない
型は明確にそろえる必要があります。
9. Vector<T>のパフォーマンスを最大化するコツ
9-1. 大量データ処理で効果が出やすい理由
Vector<T>は、同じ演算を大量の数値に対して繰り返す場面で効果が出やすいです。
たとえば、音声処理、画像処理、物理シミュレーション、機械学習前処理、数値解析などでは、配列の各要素に同じ計算を適用することが多くあります。
このような処理では、ループの回数を減らせるため、SIMDの恩恵を受けやすくなります。
9-2. 小さなデータでは速くならないことがある
Vector<T>を使ったからといって、必ず速くなるわけではありません。
データ件数が少ない場合、Vector<T>の生成やコピーのコストが相対的に大きくなり、通常のfor文のほうが速いこともあります。
C#// 要素数が数個しかないなら、単純なfor文で十分な場合が多い
for (int i = 0; i < values.Length; i++)
{
values[i] *= 2f;
}
最適化する前に、本当にボトルネックになっているかを測定することが大切です。
9-3. メモリアクセスと配列の連続性を意識する
Vector<T>は、配列の連続した要素をまとめて読み込む処理に向いています。
C#var v = new Vector<float>(values, i);
このように連続したメモリ上の値を読み込めると、効率よく処理できます。逆に、ランダムアクセスが多い処理や、複雑な条件分岐が多い処理では、SIMDの効果が出にくくなります。
9-4. DebugビルドではなくReleaseビルドで測定する
パフォーマンスを測定するときは、DebugビルドではなくReleaseビルドを使います。
Debugビルドでは最適化が抑制されるため、実際の性能とは大きく異なる結果になることがあります。
測定時は、次のような条件を意識します。
| 項目 | 推奨 |
|---|---|
| ビルド構成 | Release |
| デバッガ | できれば非接続 |
| データ件数 | 実運用に近い量 |
| 測定回数 | 複数回 |
| 比較対象 | 通常のfor文 |
9-5. BenchmarkDotNetで正しく計測する
C#で正確にベンチマークを取りたい場合は、BenchmarkDotNetを使うのが定番です。
簡易的なStopwatchでも傾向は見られますが、JITコンパイル、ウォームアップ、GCなどの影響を受けやすくなります。
C#// 例:実際のベンチマークではBenchmarkDotNetの属性を使って測定する
// [Benchmark]
// public void AddWithVector()
// {
// AddArrays(a, b, result);
// }
Vector<T>のような低レベル寄りの最適化では、体感ではなく数値で確認することが重要です。
9-6. LINQよりfor文が向いているケース
数値配列を高速処理したい場合、LINQよりfor文のほうが向いていることが多いです。
C#// 読みやすいが、大量データの高速処理には不利な場合がある
var result = values.Select(x => x * 2f).ToArray();
高速化を重視するなら、余計な列挙や一時オブジェクトを避けられるfor文やVector<T>を検討します。
C#for (int i = 0; i < values.Length; i++)
{
result[i] = values[i] * 2f;
}
9-7. 不要なインスタンス生成を避ける
Vector<T>を使っても、毎回不要な配列を作成したり、一時オブジェクトを増やしたりすると、パフォーマンスが落ちることがあります。
結果用の配列を毎回作るのではなく、呼び出し側から渡す形にすると、メモリ割り当てを減らせます。
C#static void Multiply(float[] source, float factor, float[] destination)
{
int count = Vector<float>.Count;
int i = 0;
var factorVector = new Vector<float>(factor);
for (; i <= source.Length - count; i += count)
{
var v = new Vector<float>(source, i);
(v * factorVector).CopyTo(destination, i);
}
for (; i < source.Length; i++)
{
destination[i] = source[i] * factor;
}
}
factorVectorをループの外で作っている点もポイントです。
10. 配列・List・Vector<T>の使い分け実践ガイド
10-1. 要素数が固定なら配列を使う
要素数が最初から決まっている場合は、配列がシンプルで高速です。
C#float[] samples = new float[1024];
配列は、数値計算、バッファ、固定長データの管理に向いています。
10-2. 要素数が増減するならList<T>を使う
要素を追加・削除したい場合はList<T>を使います。
C#List<int> ids = new List<int>();
ids.Add(1);
ids.Add(2);
ids.Remove(1);
C++のvectorのような使い方をC#でしたい場合、多くのケースではList<T>が適しています。
10-3. 座標や方向を表すならVector2・Vector3を使う
座標や方向を表すなら、配列ではなくVector2やVector3を使うと可読性が上がります。
C#Vector3 position = new Vector3(1f, 2f, 3f);
Vector3 velocity = new Vector3(0f, 0f, 5f);
position += velocity;
float[]で書くより、何を表しているのかが分かりやすくなります。
10-4. 大量の数値配列を高速処理するならVector<T>を使う
大量の数値配列に対して、加算、乗算、減算など同じ処理を繰り返す場合はVector<T>を検討します。
C#// 画像の明るさ調整、音声サンプルの増幅、数値配列の正規化など
ただし、必ず速くなるとは限らないため、測定とセットで導入するべきです。
10-5. 可読性を優先すべきケース
アプリケーション全体の多くのコードでは、過度な最適化より可読性が重要です。
たとえば、要素数が少ない処理や、頻繁に実行されない処理では、無理にVector<T>を使う必要はありません。
C#float total = 0f;
for (int i = 0; i < values.Length; i++)
{
total += values[i];
}
シンプルなコードで十分な場合は、シンプルに書くほうが保守しやすくなります。
10-6. パフォーマンスを優先すべきケース
パフォーマンスを優先すべきなのは、実際に処理時間の大部分を占めている箇所です。
たとえば、毎フレーム大量の数値を処理する、巨大な配列を何度も走査する、リアルタイム性が必要といった場合は、Vector<T>やメモリ効率を意識する価値があります。
最適化の流れは、次の順番がおすすめです。
まず正しく動くコードを書く
ベンチマークで遅い箇所を特定する
アルゴリズムを見直す
必要なら
Vector<T>などで高速化する
10-7. 初心者向けの判断フローチャート
初心者は、次のように判断すると迷いにくくなります。
複数の値を扱いたい
├─ 要素数を増減したい
│ └─ List<T>
├─ 要素数が固定
│ └─ 配列
├─ 座標・方向・速度を表したい
│ ├─ 2Dなら Vector2
│ ├─ 3Dなら Vector3
│ └─ 4成分なら Vector4
└─ 大量の数値配列を高速処理したい
└─ Vector<T>
最初からすべてを使い分けようとせず、まずは配列、List<T>、Vector2、Vector3の違いを押さえることが大切です。
11. C#のVectorでよくあるエラーと解決策
11-1. Vector型が見つからない
Vector3などが見つからない場合、まずusing System.Numerics;があるか確認します。
C#using System.Numerics;
それでも解決しない場合は、プロジェクトの参照設定やターゲットフレームワークを確認します。
11-2. System.Numericsをusingしても使えない
using System.Numerics;を書いても使えない場合は、必要なアセンブリやNuGetパッケージが不足している可能性があります。
特に古い.NET Frameworkプロジェクトでは、System.Numerics.Vectorsの追加が必要になる場合があります。
Bashdotnet add package System.Numerics.Vectors
Visual Studioを使っている場合は、NuGetパッケージマネージャーから追加できます。
11-3. Vector3とUnityEngine.Vector3を混同している
UnityではUnityEngine.Vector3、通常の.NETではSystem.Numerics.Vector3を使います。
両方の名前空間を同時に使うと、どちらのVector3か分からずエラーになることがあります。
C#using NumericsVector3 = System.Numerics.Vector3;
// using UnityVector3 = UnityEngine.Vector3;
NumericsVector3 v = new NumericsVector3(1f, 2f, 3f);
必要に応じて別名を使うと、混同を防げます。
11-4. int型とfloat型の変換でエラーになる
Vector3の成分は基本的にfloatです。数値リテラルを書くときは、fを付けると明確です。
C#Vector3 v = new Vector3(1f, 2f, 3f);
次のようにdoubleを渡すとエラーになる場合があります。
C#// Vector3 v = new Vector3(1.0, 2.0, 3.0); // doubleなので不適切
1.0fのように書きましょう。
11-5. NormalizeでNaNが発生する
長さが0のベクトルを正規化すると、計算結果がNaNになることがあります。
C#Vector3 zero = Vector3.Zero;
Vector3 normalized = Vector3.Normalize(zero);
安全に正規化するには、長さを確認してから処理します。
C#static Vector3 SafeNormalize(Vector3 v)
{
if (v.LengthSquared() < 0.000001f)
{
return Vector3.Zero;
}
return Vector3.Normalize(v);
}
11-6. Vector<T>で対応していない型を使っている
Vector<T>は数値計算用の型です。任意の型を入れられるコレクションではありません。
C#Vector<int> vi = new Vector<int>(1);
Vector<float> vf = new Vector<float>(1f);
Vector<double> vd = new Vector<double>(1.0);
stringや独自クラスを入れるためのものではありません。そうした用途ではList<T>や配列を使います。
11-7. 期待したほど高速化されない
Vector<T>を使っても、期待したほど速くならないことがあります。
主な原因は次のとおりです。
| 原因 | 対策 |
|---|---|
| データ量が少ない | 通常のfor文で十分か確認する |
| Debugビルドで測定している | Releaseビルドで測定する |
| SIMDが有効でない | Vector.IsHardwareAcceleratedを確認する |
| メモリアクセスが非効率 | 配列の連続アクセスにする |
| LINQや一時配列が多い | for文で単純化する |
高速化は、コードを書き換えるだけでなく、測定と原因分析が重要です。
12. C#のVectorを使うときの注意点
12-1. Vectorは万能な高速化手段ではない
Vector<T>は便利ですが、万能ではありません。
条件分岐が多い処理、データ量が少ない処理、メモリアクセスが複雑な処理では、思ったほど効果が出ないことがあります。
また、Vector2やVector3も、使えば自動的にすべてが高速化されるわけではありません。目的に合った型を選ぶことが大切です。
12-2. 数学的なベクトルの基礎理解が必要
Vector2やVector3を使いこなすには、最低限のベクトルの知識が必要です。
特に重要なのは、次の概念です。
| 概念 | 用途 |
|---|---|
| 長さ | 距離や速度の大きさ |
| 正規化 | 方向だけを取り出す |
| 内積 | 向きの一致度を調べる |
| 外積 | 垂直方向や法線を求める |
| 補間 | なめらかな移動や変化 |
プログラムの書き方だけでなく、何を計算しているのかを理解することが重要です。
12-3. 可読性が下がる実装を避ける
高速化を意識しすぎると、コードが読みにくくなることがあります。
特にVector<T>を使ったコードは、通常のfor文より複雑になりやすいです。
C#// 分かりやすい
result[i] = a[i] + b[i];
// 高速化できる可能性はあるが、コードは複雑になる
(varA + varB).CopyTo(result, i);
チーム開発では、読みやすさと速度のバランスを考えることが大切です。
12-4. プラットフォームやCPUによって性能が変わる
Vector<T>の性能は、CPU、JIT、ランタイム、OS、ビルド設定などの影響を受けます。
ある環境で速くても、別の環境で同じように速いとは限りません。
そのため、実際に動かす環境に近い条件で測定する必要があります。
12-5. Unity・OpenTK・System.Numerics間の型変換に注意する
C#で3D処理をしていると、複数のライブラリがそれぞれ独自のVector3を持っていることがあります。
たとえば、次のような型は別物です。
| ライブラリ | 型 |
|---|---|
| .NET | System.Numerics.Vector3 |
| Unity | UnityEngine.Vector3 |
| OpenTK | OpenTK.Mathematics.Vector3 |
名前が同じでも互換性があるとは限りません。必要に応じて明示的に変換します。
C#static System.Numerics.Vector3 ToNumerics(float x, float y, float z)
{
return new System.Numerics.Vector3(x, y, z);
}
12-6. 浮動小数点誤差を考慮する
Vector2やVector3はfloatを使うため、浮動小数点誤差が発生します。
そのため、完全一致で比較するのは避けたほうが安全です。
C#static bool NearlyEqual(Vector3 a, Vector3 b, float tolerance = 0.0001f)
{
return Vector3.DistanceSquared(a, b) <= tolerance * tolerance;
}
座標や方向を比較するときは、許容誤差を使うのが一般的です。
12-7. パフォーマンス測定なしに最適化しない
最適化は、測定してから行うべきです。
「Vector<T>を使えば速そう」という理由だけで書き換えると、コードが複雑になるだけで効果がないこともあります。
まずは分かりやすい実装を書き、必要になった段階でベンチマークを取り、効果がある箇所だけ最適化しましょう。
13. C#のVectorに関するよくある質問
13-1. C#にC++のvectorのような型はある?
C++のstd::vectorのように、要素を動的に追加・削除したい場合は、C#では通常List<T>を使います。
C#List<int> values = new List<int>();
values.Add(1);
values.Add(2);
C#のVectorは、C++のvectorとは別物です。
13-2. C#ではVectorとListのどちらを使えばいい?
要素を追加・削除したいならList<T>を使います。座標や方向を表したいならVector2やVector3を使います。大量の数値配列をSIMDで高速処理したいならVector<T>を検討します。
つまり、VectorとListは競合するものではなく、用途が違います。
13-3. Vector2とVector3は何が違う?
Vector2はXとYの2成分、Vector3はX、Y、Zの3成分を持ちます。
2D座標や画面上の移動にはVector2、3D空間の位置や方向にはVector3を使います。
C#Vector2 pos2D = new Vector2(10f, 20f);
Vector3 pos3D = new Vector3(10f, 20f, 30f);
13-4. System.Numerics.Vector3とUnityEngine.Vector3は同じ?
同じではありません。System.Numerics.Vector3は.NETのSystem.Numerics名前空間にある型で、UnityEngine.Vector3はUnityのUnityEngine名前空間にある型です。
UnityではTransform.positionなどがUnityEngine.Vector3を使うため、Unity開発では基本的にUnity側のVector3を使います。
13-5. Vector<T>を使えば必ず速くなる?
必ず速くなるわけではありません。
Vector<T>は大量の数値データに同じ処理を行う場合に効果が出やすいですが、データ量が少ない場合やメモリアクセスが複雑な場合は、通常のfor文とあまり変わらないこともあります。
必ずベンチマークで確認しましょう。
13-6. ベクトル計算には配列ではなくVectorを使うべき?
座標、方向、速度などを扱うなら、Vector2やVector3を使うのがおすすめです。
配列でも表現できますが、意味が分かりにくくなります。
C#// 意味が分かりやすい
Vector3 position = new Vector3(1f, 2f, 3f);
// 何を表す配列か分かりにくい
float[] positionArray = { 1f, 2f, 3f };
ただし、大量の数値データそのものを管理するなら配列のほうが適していることもあります。
13-7. 初心者はどのVectorから学べばいい?
初心者は、まずVector2とVector3から学ぶのがおすすめです。
2DならVector2、3DならVector3を使い、足し算、引き算、長さ、正規化、距離、内積を順番に理解するとよいでしょう。
Vector<T>は、配列処理やパフォーマンス最適化が必要になってから学べば十分です。
まとめ
C#の「Vector」は、文脈によって意味が大きく変わります。
C++のvectorのような可変長配列を使いたい場合は、C#ではList<T>を使います。固定長データを扱うなら配列が適しています。座標、方向、速度などの数学的なベクトルを扱うなら、System.Numerics.Vector2、Vector3、Vector4を使います。大量の数値配列を高速に処理したい場合は、System.Numerics.Vector<T>を検討します。
特に重要なのは、「データの入れ物」と「数学的なベクトル」と「SIMD用のベクトル」を混同しないことです。
実践では、次のように考えると迷いにくくなります。
| 目的 | 選ぶもの |
|---|---|
| 固定長のデータを扱う | 配列 |
| 要素を追加・削除する | List<T> |
| 2D座標や速度を扱う | Vector2 |
| 3D座標や方向を扱う | Vector3 |
| 4成分のデータを扱う | Vector4 |
| 大量の数値配列を高速処理する | Vector<T> |
C#のVectorを正しく使い分けられるようになると、座標計算やゲーム開発だけでなく、画像処理、音声処理、数値計算などにも応用できます。まずはVector2とVector3の基本演算から始め、必要に応じてVector<T>による高速化へ進むのが効率的です。

