Unityでのゲーム開発において、データを効率的に管理する方法の一つがListです。
Listは配列と似ていますが、配列と違い動的にサイズが変更されるため、要素の追加や削除を頻繁に行う際でも簡単に使用することが可能です。
Listとは
C#のList<T>
は、可変サイズの配列です。T
はジェネリック型で、格納する要素の型を示します。System.Collections.Generic
ネームスペースに属し、動的に要素を追加、削除できることが特徴です。
要素の検索やソート、逆順表示などは提供されているメソッドを使用するだけで簡単に行えます。
使い方
宣言と初期化
using System.Collections.Generic;
List<int> myList = new List<int>();
List<int> myList2 = new List<int>(){1, 2, 3};
List<int> myList3 = new List<int>(100);
List<int> myList4 = new List<int>(myList2);
Listはnew演算子でインスタンスを生成して初期化します。
使用する際はSystem.Collections.Genericをusingに含む必要があります。
List型の宣言ではサイズを指定せずに宣言することも可能ですが、
myList3のように予めサイズを指定することも可能です。
予め要素数が決まっている場合はサイズを指定した方がパフォーマンス効率がよくなります。
要素の追加(Add, AddRange)
Add
myList.Add(1);
要素を1つ追加します。
AddRange
myList.AddRange(myList2);
Listもしくは配列を追加できます。
要素の削除 (Remove, RemoveAt, RemoveAll, Clear)
Remove
myList.Remove(1);
値が一致する要素を一つ削除します。
RemoveAt
myList.RemoveAt(1);
インデックスを指定して削除します。
インデックスは0から数えるため、
例えば{1,2,3,4}のリストならインデックス0の要素は1となります。
RemoveAll
myList.RemoveAll(num => num > 1);
指定した条件を満たすものを全て削除します。
numは要素内の各要素を表していて、条件文は上記のようにラムダ式で記述することも可能ですが、
bool IsOverOne(int num)
{
return num > 1;
}
myList.RemoveAll(IsOverOne);
このように戻り値がboolで引数がList<T>のTと同じ型のメソッドを使用することも可能です。
Clear
myList.Clear();
要素を全て削除します。
要素の取得 (インデックス指定, Find, FindAll)
インデックス指定
int num = myList[0];
インデックスを指定して、そのインデックスの要素を取得します。
Find
int num = myList.Find(num => num > 1);
指定した条件を満たす最初の要素を取得します。
FindAll
List<int> numList = myList.FindAll(num => num > 1);
指定した条件を満たす全ての要素のリストを取得します。
要素数の取得 (Count)
Count
int count = myList.Count;
List内の要素数をそのまま返します。
また、Linqメソッドを使うことで下記のように条件を満たしている要素数を数えることも可能です。
using System.Linq;
int count = myList.Count(num => num > 1);
要素の存在判定 (Contains, Any)
Contains
bool isExist = myList.Contains(1);
指定した要素がList内にあるかどうかを判定します。
Any
bool isExist = myList.Any(num => num > 1);
指定した条件を満たす要素がList内に1つでもあるかどうかを判定します。
並べ替え (Sort)
Sort
List<int> myList = new List<int>(){4,1,5,3,2};
myList.Sort(); //{1,2,3,4,5}になる
myList.Sort((a, b) => b.CompareTo(a)); //{5,4,3,2,1}になる
昇順の場合はシンプルにSortを呼ぶだけでいいですが、降順にしたい場合は要素の比較が必要になります。
UnityでのList
インスペクター
UnityのインスペクターではデフォルトでList型の操作が可能です。
要素の追加や削除、要素数の指定、要素内容の変更、要素の順番を操作することが出来ます。
Listの中身の確認
publicもしくはSerializeFieldのメンバ変数であれば、実行中でもインスペクター上から中身を確認できます。
ただ、多くの場合はDebug.Logなどを使用してConsole出力することが多いと思います。
その際、Debgu.Log(myList);のようにそのままList型を出力しようとしても要素の中身は表示されません。
Debug.Logを使用して中身を出力したいときは
foreach (var num in myList)
{
Debug.Log(num);
}
のようにforeach文などで中身を一つずつ出力するか
Debug.Log(string.Join(",", myList));
のようにstring.Joinで中身を文字列結合させて出力させることで確認できます。
ただ、毎回このように記載するのはめんどくさいので、
よく使う処理は拡張メソッドを定義してあげるといいでしょう。
public static class ListExtentions
{
public static void Log<T>(this List<T> list, string separator)
{
Debug.Log(string.Join(separator, list));
}
}
このように定義すると
myList.Log(",");
のようにList型のメソッドとして使えるようになります。
Listを使う際の注意点
キャパシティの無視
Listの内部では配列が使用されています。
この配列のサイズ(キャパシティ)がリストに格納される要素数を超えると、自動的により大きな新しい配列にコピーされます。
このプロセスはコストがかかるため、予測される要素数がある程度分かっている場合は、Listの初期キャパシティを適切に設定することでパフォーマンスを向上させることができます。
ループ中の要素の削除
var myList = new List<int>(){0,1,2,3};
foreach (var num in myList)
{
if (num > 1)
{
myList.Remove(num);
}
}
このプログラムを実行するとエラーになります。
理由としては、myListのループ中にmyListの要素数に変更がかかるためです。
そのため、ループ中にリスト操作をしたい場合は
var myList = new List<int>(){0,1,2,3};
var copyMyList = new List<int>(myList);
foreach (var num in copyMyList )
{
if (num > 1)
{
myList.Remove(num);
}
}
このようにコピーしたListでループ文を回すか、
var myList = new List<int>(){0,1,2,3};
for (var i = myList.Count - 1; i >= 0; i++)
{
var num = myList[i];
if (num > 1)
{
myList.Remove(num);
}
}
このようにリストの最後尾から逆順にループを回すことで対策が可能です。
参照型としての扱い
Listは参照型の変数のため、扱い方を間違えると想定していない変更が加えられます。
List<int> myList = new List<int>(){1,2,3,4};
List<int> copyList = myList;
copyList.Add(5); //myListにも5が追加される
要素の値だけをコピーしたListを作りたい場合は
List<int> myList = new List<int>(){1,2,3,4};
List<int> copyList = new List<int>(myList);
copyList.Add(5); //myListには追加されない
このようにnew演算子で新しいListを生成する必要があります。
また、参照型のオブジェクトのリストの場合、更に注意が必要です。
例えば次のような独自クラスのリストの場合
class MyClass()
{
public string Text = "";
}
List<MyClass> myList = new List<MyClass> { new MyClass(), new MyClass() };
List<MyClass> copyList = new List<MyClass>(myList);
copyList[0].Text = "変更";
// myList[0].Textも"変更"になる
となり、リスト内の各オブジェクトは参照のコピーになります。
もし各オブジェクト自体をコピーしたい場合は、各オブジェクトを個別にコピーできる仕組みが必要になります。
FindやContainsなどの過剰使用
FindやContainsなどの条件を指定して検索する処理はList型では線形探索で行われます。
そのため要素数がかなり多い場合、これらの操作は非効率なためパフォーマンスに影響を及ぼす可能性があります。
要素数がかなり多い配列で特定の検索操作を頻繁に行う場合は、より適切なデータ構造(DictionaryやHashSetなど)の使用を検討するようにしましょう。
コメント