C#には、添え字で各要素にアクセスできる配列(Array、Listなど)や辞書(Dictionary)があります。
自作のクラスにおいても、これらと同様に添え字でクラス内部のデータにアクセスできる仕組み=インデクサが用意されています。
知っていると便利な機能なので、今回はインデクサの作り方、使い方、注意点について解説したいと思います。
インデクサとは
通常の配列 ( int[] )や、リスト(List)、辞書(Dictionary)などのクラスは、内部に保持しているデータを添え字で指定することが出来ます。
この添え字の事を「インデックス」と呼んでいますが、配列やListの場合は先頭0から始まる数字、辞書の場合は文字列(Key)でアクセスしたい要素の場所を指定します。
この機能を独自のクラスで使えるようにすること、言い換えると独自クラスの中に保持しているデータを、外部から添え字でアクセスさせる方法を「インデクサ」と呼びます。
インデクサの作り方
インデクサは一か所を除いて通常のプロパティと全く同じ記述で作成することが出来ます。
違いは、変数名の部分を this[型 変数名] という具合に記述するところです。
下記のソースコードは MyClass というクラスにインデクサを作った例です。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
//数値型のインデクサ例 public class MyClass { //要素を保持するLIST public List<string> Values { get; set; } = new List<string>(); //インデクサ public string this[int index] { get { return Values[index]; } set { Values[index] = value; } } //要素を追加する メソッド public void Add(string str) { Values.Add(str); } } |
この例では、内部では Values というList型の変数に文字列列が保持されています。
仮に、先頭から4番目(添え字=3)の要素にアクセスしたい場合、通常のプロパティでは Values[3]という記述をしますが、インデクサを作成していれば、クラス名[3] という記述ができるようになります。
1 2 3 4 5 6 7 |
//従来のプロパティとして要素にアクセスする例 MyClass my = new MyClass(); string str = my.Values[3]; //インデクサを作った場合のアクセス玲 MyClass my = new MyClass(); string str = my[3] |
インデクサの添え字は文字、数字に限らない
先ほどの例では、配列と同じく数値でクラス内部のデータにアクセスしましたが、添え字は数値だけという制限はありません。
クラス内部に辞書(Dictionary)を保持している場合は、インデクサとして文字列型を使うケースの方が多と思います。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
//文字列型のインデクサ例 public class MyClass { //要素を保持する辞書 private Dictionary<string, int> Values { get; set; } = new Dictionary<string, int>(); //インデクサ public int this[string name] { get { return Values[name]; } set { Values[name] = value; } } //要素を追加するメソッド public void Add(string str, int value) { Values.Add(str, value); } } |
文字列、数値以外に、オブジェクト型や任意のクラスをインデクサの添え字で使う事もできます。
次のソースは、オブジェクト型のインデクサのサンプルになります。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
public class MyClass3 { //要素を保持する辞書 private Dictionary<object, int> Values { get; set; } = new Dictionary<object, int>(); //インデクサ public int this[object obj] { get { return Values[obj]; } set { Values[obj] = value; } } //要素を追加するメソッド public void Add(object str, int value) { Values.Add(str, value); } } |
オブジェクトのインデクサを使う場合のサンプルソースを掲載しておきます。
1 2 3 4 5 6 7 |
MyClass my = new MyClass(); my.Add("osaka", 100); my.Add(1234, 200); my.Add(3.14, 300); Console.WriteLine(my[1234]); |
インデクサをオブジェクト型にすると、添え字として整数や文字列、文字列などが使える様になりますが、逆に添え字として何を与えたらよいのかが不明確なってしまいます。
このように、あまり複雑な事をすると訳が分からなくなってしまいますので、特に必要が無い限り数値や文字列くらいに留めておく方が無難です。
インデクサの添え字は2個でも3個でも可能
インデクサの添え字は1つだけではなく、複数指定することが出来ます。
例えば、次のサンプルはインデクサの引数を2つ持たせた例です。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
public class MyClass { //要素を保持する辞書 private Dictionary<string, Dictionary<object,int>> Values { get; set; } = new Dictionary<string, Dictionary<object, int>>(); //インデクサ public int this[string name,object obj] { get { return Values[name][obj]; } set { Values[name][obj] = value; } } //要素を追加するメソッド public void Add(string str, Dictionary<object, int> value) { Values.Add(str, value); } } |
このインデクサは次の様にして使うことが出来ます。
1 2 3 4 5 6 7 |
MyClass my = new MyClass(); my.Add("osaka", new Dictionary<object, int>() { { 100, 101 },{ "abc", 201 },{ 3.14, 301 } }); my.Add("tokyo", new Dictionary<object, int>() { { 100, 101 }, { "abc", 202 }, { 3.14, 302 } }); my.Add("kyoto", new Dictionary<object, int>() { { 100, 101 }, { "abc", 203 }, { 3.14, 303 } }); Console.WriteLine(my["tokyo",3.14]); |
添え字が2個以上になってしまうと、やはり何を添え字に使えばよいのか直感的に分からなくなるので、あまり多用はしない方がよさそうです。
インデクサには独自の処理が記述できる
ここまでに提示したサンプルは、クラス内部に保持しているリストや辞書の内容を、インデクサを使ってアクセスするという例でした。
しかし、インデクサの仕様としては、指定されたインデクサとして指定された変数の値に対応した結果を返したり、受け取ったりすれば良いだけなので、必ずしもリストや辞書の結果を返す必要はありません。
例えば、下記のインデクサは、添え字として指定された値を二乗して、結果を文字列として返しています。
内部にデータを保持する必要が無いため、set には何も記述していません。
1 2 3 4 |
public class MyClass { public string this[int index] { get { return (index * index).ToString(); } set { } } } |
このインデクサは次の様に利用できますが、この様に添え字に対応する結果を返せば中身は何でもよいことがお分かりいただけるかと思います。
1 2 |
MyClass5 my5 = new MyClass5(); Console.WriteLine(my5[1234]); |
インデクサの注意点
インデクサは独自のクラス内部にあるデータに対して、外部から配列と同じ様にアクセスさせるための方法です。
あたかも配列と同じ様に扱えるという事は、使う側も配列と同じ様に結果が返されることを期待しているわけです。
つまり、添え字に5を入れると、先頭から数えて6番目の要素が取り出せることを期待しますし、文字列であれば辞書の様にキーが一致した時の値が取り出せることを期待します。
インデクサを作る場合も、使う側の期待を裏切らないような結果を返す処理にすべきです。
また、配列の様にアクセスすることは可能なものの、配列と同じという事ではありません。
例えば、配列が持っているLength等のプロパティやSelectやToList、SumなどのLinqは使えませんし、配列を処理する場合に使う Arrayクラスを使ってコピーやソートをすることも出来ません。
配列の様に添え字にアクセスできますが、あくまでも疑似的な物で配列とは別物であるという事は意識しておいて下さい。
まとめ
インデクサは独自クラスの内部に保持している値を、外部から添え字でアクセスするための方法です。
プロパティの型指定の部分が this[型 変数名] という記述になる点を除けば、通常のプロパティと同じ記述で作ることが出来ます。
インデクサは、渡された添え字に対応する値を受け取ったり、返したりするものなので、中身はリストや辞書のように値を保持する代わりに、ロジックを記述することも可能です。
インデクサの添え字の型や個数に制限は無く、オブジェクト型の添え字や、添え字の数を2以上作ることも可能ですが、分かり難くなるという弊害もあります。
あくまでも配列と同じ様にクラス内部のデータにアクセスできますが、配列が持つLengthやLinqが使は使えません。
以上の事を踏まえておけば、あとは実戦あるのみです。
使いこなせば便利な機能なので、是非ご活用ください。