しかしま〜なんですな〜
C#小技のページ、本日の話題はDictionaryやSortedDictionaryだ。
DictionaryやSortedDictionaryの使い方を覚えると、様々な場面で利用出来るのでとっても便利だ。
ワテも良く使う。
プログラミングの世界では複数のデータを保持する手法としては、配列やリストが一般的だ。
でも、当記事で紹介するDictionaryやSortedDictionaryの使い方を覚えると、とっても便利。
では、本題に入ろう。
配列 Array やリスト List の場合
C#でも複数のデータを保持する手法としては、配列やリストがある。
配列やリストの簡単な例。
static void test1() { // 要素数10個の整数型配列を作成する。 int[] arr = new int[10]; for (int i = 0; i < arr.Length; i++) { arr[i] = i; Console.WriteLine("i={0} arr[{0}]= ->{1}<-", i, arr[i]); } // 文字列型のリスト。要素数は自動で増えて行くので便利。 var lst = new List<string>(); for (int i = 0; i < arr.Length; i++) { lst.Add("要素" + i); Console.WriteLine("i={0} lst[{0}]= ->{1}<-", i, lst[i]); } }
コード1. 配列やリストの簡単な例(test1)
上のコードを実行すると以下の通り。
i=0 arr[0]= ->0<- i=1 arr[1]= ->1<- i=2 arr[2]= ->2<- i=3 arr[3]= ->3<- i=4 arr[4]= ->4<- i=5 arr[5]= ->5<- i=6 arr[6]= ->6<- i=7 arr[7]= ->7<- i=8 arr[8]= ->8<- i=9 arr[9]= ->9<- i=0 lst[0]= ->要素0<- i=1 lst[1]= ->要素1<- i=2 lst[2]= ->要素2<- i=3 lst[3]= ->要素3<- i=4 lst[4]= ->要素4<- i=5 lst[5]= ->要素5<- i=6 lst[6]= ->要素6<- i=7 lst[7]= ->要素7<- i=8 lst[8]= ->要素8<- i=9 lst[9]= ->要素9<-
実行結果1. 配列やリストの簡単な例(test1)の実行結果
う~ん、まあ、これは普通だな。
配列は宣言時に大きさを指定する必要がある。
int[] arr = new int[10];
配列の場合は、処理の途中で10要素を20要素に増やすなどは簡単には出来ない。もしやるなら新規に20要素の配列を宣言してコピーするなどの小細工が必要になる。
一方、リストは宣言時には大きさを指定しなくても良くて、動的に要素をどんどん追加出来るのだ。
var lst = new List<string>(); ・・・ lst.Add("要素" + i);
この場合はlstの末尾に新要素が追加されて行くが、insert(挿入位置, 挿入したい要素)メソッドを使えば任意の場所に新要素を追加出来る。
そう言う点ではC#では配列よりもリストのほうが便利なのだ。
でもワテの場合、C#を初めて覚え始めた当初は、newの使い方で混乱した。
C#のメモリ管理に付いて
つまりまあ、C++の場合ならnewで確保したメモリ領域は使い終わったらdeleteで開放する必要がある。あるいはC言語の場合なら、mallocで確保してfreeで開放する。
ところが、C#の場合には上例のコードのように配列でもリストでも宣言する時にはnewが必要なのだが、その後はdeleteやfreeと言った処理が不要なのだ。放っておけば.NET Frameworkが上手い具合にメモリ管理をしてくれているのだ。ああ便利。CやC++ならdeleteやfreeの実行忘れが無いか物凄く気になるが、そんな事は一切気にしなくても良いのがC#なのだ。
じゃあ、宣言する時にもnewも無しにすれば良いと思うのだが、それは何故かそうなっていない。まあC#にはC#の事情があるのだろう。
Disposeが必要になる場合がある
ところが、C#でプログラミングをしていると、使い終わったオブジェクトを破棄(dispose)する必要がある場合もあるのだ。
例えば、データベースにテーブルを作成する場合ならこんな感じのコマンドを実行する。
try { SqlConnection connection = new SqlConnection(connectionString); connection.Open(); SqlCommand command = new SqlCommand("create table WarekoTable", connection); try { command.ExecuteNonQuery(); } finally { command.Dispose(); } } finally { connection.Dispose(); }
この時に、データベースに接続するためのconnectionオブジェクトやSQLコマンドのcommandオブジェクトをnewで宣言するが、それらは使い終わったらDispose()関数を実行して破棄する必要があるのだ。上のコードではfinallyを使ってDisposeを確実に実行している。
あるいはusingを使うとusingブロックを抜ける時に自動的にDisposeしてくれるので、上のコードはusingを使って書くと以下のように簡略化出来る。
using (SqlConnection connection = new SqlConnection(connectionString)) { using (SqlCommand command = new SqlCommand("create table WarekoTable", connection)) { connection.Open(); command.ExecuteNonQuery(); } }
もしreturnで内側のusingブロックを抜けた場合でもcommandとconnectionのDisposeは自動的に実行されるのでusing構文はとっても便利なのだ。
う~ん、では、C#においてどんな場合にDisposeメソッドが必要になるのかはワテには解説出来るだけの知識は無い。
まあ、Array、List、Dictionaryなどの単純なオブジェクトを使う場合はDisposeは必要ない。
SqlConnection、StreamReader、MemoryStreamなどではDisposeが必要になるのでusing形式の構文で書くと良いだろう。
さて、少々脇道に逸れたが、では本題のDictionaryやSortedDictionaryを使ってみよう。
C#の Dictionary を使う
Dictionaryだから、日本語だと”辞書”と言うのかな?
でもC#の世界だとディクショナリーと発音するのが普通だと思う。
Dictionaryの基本は、KeyとValue をペアで与える事だ。
以下の例ではKeyもValueもstring型にしているので非常に簡単な例である。
一般には、KeyもValueもstring以外の他のデータ型にしても良い。
static void test2() { // Dictionaryの基本は KeyとValue をペアで与える。 // 三通りくらいの与え方がある。まあ好き好きかな。 var dic1 = new Dictionary<string, string>() { { "key1", "value1" }, { "key2", "value2" }, }; var dic2 = new Dictionary<string, string> { { "key1", "value1" }, // ()の有無は何が違うのか知らない { "key2", "value2" } }; var dic3 = new Dictionary<string, string> { ["key1"] = "value1", ["key2"] = "value2", }; dic3.Add("key3", "value3でんがな"); // 途中で辞書に値を追加する場合 dic3.Remove("key1"); // 要素を削除する場合 var f_hasKey3 = dic3.ContainsKey("key3"); // = true になる var f_hasVal3 = dic3.ContainsValue("value3でんがな");// = true になる int i = 0; foreach (var kvPair in dic3) { var key = kvPair.Key; var val = kvPair.Value; Console.WriteLine("i={0}, key= ->{1}<-, val= ->{2}<-", i, key, val); i++; } }
コード2. Dictionary<string, string>に初期値を与えて宣言する例(test2)
これを実行すると、以下の通り。
i=0, key= ->key2<-, val= ->value2<- i=1, key= ->key3<-, val= ->value3でんがな<-
key1の要素をremoveしたので実行結果にもそれは出て来ない。
なお、Dictionaryの場合にもnewで宣言してメモリ領域を確保するが、その後はdisposeなどの処理は不要だ。
Dictionaryに要素をAddとRemoveする
Dictionaryでよく使うのはAddで要素の追加や、Removeで要素の削除だ。
dic3.Add("key3", "value3でんがな"); // 途中で辞書に値を追加する場合 dic3.Remove("key1"); // 要素を削除する場合
Addすればいくらでも追加出来る。
要素数が今幾つあるのかなどは自分で管理する必要は無くてC#が上手く管理してくれる(.NET Frameworkがやってくれているのかな)。
注意事項としては、Dictionaryには同じ値のキーを重複して登録する事は出来ない。
例えば、”key3″ を二回登録すると例外が出る。
dic3.Add("key3", "value3でんがな"); // 途中で辞書に値を追加する場合 dic3.Add("key3", "二個目でんがな"); // ここで例外が出る。
キーの重複登録を防止する為の便利な機能がある。
それが↓だ。
ContainsKey() や ContainsValue() も便利
特定のキーが既にDictionaryに存在するかどうか、
特定の値が既にDictionaryに存在するかどうか
などを知りたい場合はこんな感じ。
var f_hasKey3 = dic3.ContainsKey("key3"); // = true になる var f_hasVal3 = dic3.ContainsValue("value3でんがな");// = true になる
ContainsKey()を使って事前にキーが存在しない事を確認してからAdd()を実行すると安全だ。
ディクショナリー変数から値を取り出す方法
ディクショナリー変数 dic3に対して、dic3[キー] を指定するとその中身のvalueが取り出せる。
今の場合は、キーが文字列型なので dic3[“key”] のようにすると良い。
もしキーが整数型なら dic3[100] などになる。
Dictionary<Key, Value>のValueにClassを入れた例
Valueには色んな物が入れられる。
配列、List<T>、Dictionary<Key, Value>、Class、、、などなど
入れて良いものと入れてはいけないものの区別はワテには分からない。
取り合えず入れてみて(つまり Dictionary<string, 入れたい型> で変数宣言してみて、エラーが出なければ入れられる)試すと良いだろう。
以下のような簡単なクラスを定義して、
class Person { public string Name; public int Age; }
そのクラスを使って Dictionary<string, Person> を定義してみた。
static void test3() { var dic4 = new Dictionary<string, Person>() { { "ワテです", new Person { Name = "ワテでんがな", Age = 00 } }, { "新垣結衣", new Person { Name = "ガッキーさん", Age = 28 } }, { "ピコ太郎", new Person { Name = "パイナップル", Age = 53 } }, }; var watePerson = dic4["ワテです"]; var wateName1 = dic4["ワテです"].Name; var wateName2 = watePerson.Name; Console.WriteLine("wateName1 = ->{0}<-", wateName1); Console.WriteLine("wateName2 = ->{0}<-", wateName2); }
コード3. Dictionary<string, Person>に初期値を与えて宣言する例(test3)
これを実行すると以下の通り。
wateName1 = ->ワテでんがな<- wateName2 = ->ワテでんがな<-
まあ、こんな感じかな。
ちなみにワテは、ガッキーファンだ!!
来年のカレンダーや、早速買おう!!
SortedDictionary<int, string> を使ってみる
文字通り、要素を自動的に並べ替えてくれるDictionaryだ。
要素を追加した時点で並び替えてくれる。
static void test4() { var sortDic1 = new SortedDictionary<int, string> { [3] = "333", [1] = "111", [0] = "000", [4] = "444", [2] = "222", }; PrintDic(sortDic1, GETNAME(new { sortDic1 })); var dic1 = new Dictionary<int, string> { [3] = "333", [1] = "111", [0] = "000", [4] = "444", [2] = "222", }; PrintDic(dic1, GETNAME(new { dic1 })); } static void PrintDic(IDictionary<int, string> anyDic, string nameDic) { Console.WriteLine("変数 {0} の中身は以下の通り", nameDic); int i = 0; foreach (var kvPair in anyDic) { var key = kvPair.Key; var val = kvPair.Value; Console.WriteLine("i={0}, key= ->{1}<-, val= ->{2}<-", i, key, val); i++; } Console.WriteLine(); } public static string GETNAME<T>(T myInput) where T : class { // 変数の名前を文字列で取得する // 詳しい事はよく知らない。まあこれで行けるようだ。 if (myInput == null) return string.Empty; return typeof(T).GetProperties()[0].Name; }
コード4. DictionaryとSortedDictionaryの例(test4)
ちょっと長くなってしまったが、test4()の部分だけで十分だ。
PrintDic() は、Dictionary, SortedDictionary変数の中身をコンソールに出力する関数
GETNAME() は変数の名前を文字列で取得する関数
などは思い付きで追加した機能だ。
これを実行すると以下の通り。
前者のsortDic1のほうはkeyが自動的に昇順(ascending)に並んでいる事が分かる。
変数 sortDic1 の中身は以下の通り i=0, key= ->0<-, val= ->000<- i=1, key= ->1<-, val= ->111<- i=2, key= ->2<-, val= ->222<- i=3, key= ->3<-, val= ->333<- i=4, key= ->4<-, val= ->444<- 変数 dic1 の中身は以下の通り i=0, key= ->3<-, val= ->333<- i=1, key= ->1<-, val= ->111<- i=2, key= ->0<-, val= ->000<- i=3, key= ->4<-, val= ->444<- i=4, key= ->2<-, val= ->222<-
もし降順(descending)に並べ替えたい場合には、簡単なやり方が有るのだが、それは別の機会に紹介したい。
まとめ
C# の Dictionary や SortedDictionary の使い方を覚えると、ドンドン使いたくなるくらい便利だ。
C/C++の連想配列クラス std::map に相当するのかな。
あるいは、JavaScriptならObjectかな。
var obj = { key1: 'value1', key2: 'value2' };
こんな感じ。
ワテも覚え立ての頃には、ディクショナリーを使うのが楽しくて使いまくった。
でも、よく考えて使うようにしないと、ヘンテコなデータ構造を作成してしまう危険性がある。
var dic = new Dictionary<string, Dictionary<string, List<string>>> ();
こんなヘンテコなデータ構造にすると、訳分からなくなると思うので、注意が必要だ。
C#の本を買う
やっぱりガッキーがいいな。
コメント