C#の匿名型について調べてみた
匿名型,便利ですよね。
LINQを使う時などによく使いますよね。
今回は,匿名型についてまとめてみました。
何気なく使っていたけれど,実は知らないこと,無意識に使っていたこと,より短い匿名型オブジェクトの生成記述法がありました。
匿名型を使ってみる
var person = new { Name = "Taro", Id = 0 };
Console.WriteLine (person.Name); // Taroと表示
Console.WriteLine (person.Id == 0); // Trueと表示
このように匿名型を使うことはあまり多くないと思います。
上記のように単体で使うよりも,LINQと一緒に使うことが多いのではないでしょうか。
次の例は,Selectメソッド(のインデックスも扱う方のオーバーロード)で匿名型を使ったサンプルです。
List<string> names = new List<string> {
"Taro",
"Jiro",
"Saburo",
"Shiro",
};
foreach (var element in names
.Select ((name, index) => new {Name = name, Index = index})) {
string output = string.Format ("No.{0} {1}", element.Index, element.Name);
Console.WriteLine (output);
}
foreach文を用いてListや配列の要素を列挙する際,その要素だけでなくその要素が何番目かという情報が必要な場合があると思います。上記のサンプルのように,Selectメソッドと匿名型を用いればforeach文で要素を列挙し,かつインデックスも利用することが可能です。
匿名型って何ができる?
プロパティ
var person = new { Name = "Taro", Id = 0 };
person.Id = 1; // ここがコンパイルエラー
上記のサンプルはコンパイルエラーになります。
Idというプロパティは持っていますが,これはgetアクセサしかもたない読み取り専用プロパティだからです。
上記の匿名型は,二つのプロパティを持っています。
- string(System.String)型のNameという名前の読み取り専用なプロパティ
- int(System.Int32)型のIdという名前の読み取り専用なプロパティ
です。
プロパティ名は匿名型のオブジェクトを生成する式の中で宣言した名前に,プロパティの型は代入したオブジェクトの型になります。
下記のような,プロパティの型名を明示した書き方はコンパイルエラーになります。
var person = new { string Name = "Taro", int Id = 0 }; //コンパイルエラー
さて匿名型はプロパティだけではありません。
他にも便利なメソッドが定義(オーバーライド)されています。
ToStringメソッド
var person = new { Name = "Taro", Id = 0 };
Console.WriteLine (person); // { Name = Taro, Id = 0 } と表示
上記のようにToStringメソッドは,オブジェクトの中身が非常に見やすい・分かりやすい形式の文字列を出力してくれます。
EqualsメソッドとGetHashCodeメソッド
匿名型はコンパイラが自動でプロパティを用いたEqualsメソッドとGetHashCodeメソッドを実装してくれます。
var taro = new { Name = "Taro", Id = 0 };
var jiro = new { Name = "Jiro", Id = 1 };
var otherTaro = new { Name = "Taro", Id = 100 };
Console.WriteLine (taro.Equals(taro)); // True
Console.WriteLine (taro.Equals(new { Name = "Taro", Id = 0 })); // True
Console.WriteLine (taro.Equals(jiro)); // False
Console.WriteLine (taro.Equals(otherTaro)); // False
Console.WriteLine (taro.Equals(new object())); // False
Console.WriteLine (taro.Equals(null)); // False
上記の用に自分では実装していませんが,プロパティを用いたEqualsメソッドがコンパイラにより実装されています。
またGetHashCodeメソッドが実装されているので,各種コレクションクラスとも安心して匿名型を利用できます。
もっと短くできた匿名型のオブジェクトの生成
new { Name = "Taro", Id = 0 }
という書き方で匿名型を利用してきました。実は別の書き方もできます。
string Name = "Taro";
int Id = 0;
var person = new {Name, Id};
Console.WriteLine (person.Name); // Taroと表示
Console.WriteLine (person.Id); // 0と表示
こんな感じです。
一度ローカル変数でNameとIdを定義して,それを利用してプロパティを定義しています。
プロパティ名は,それぞれローカル変数名のName,Idに,型はstring,intになっています。
この書き方,事前にローカル変数を作る必要があるので,匿名型のオブジェクトを作るのは短くなりますが,あまり嬉しくは思えません。この例だと。
しかしLINQとともに匿名型を用いる場合とても便利です。
前述のSelectメソッドと匿名型のオブジェクトの一部分だけ再掲します。
names.Select ((name, index) => new {Name = name, Index = index})
この短いコード内に,(大文字小文字を区別しなければ)3回ずつ,name・indexって書いていますね。
くどいですね。
この部分を次のように書き換えることができます。
names.Select ((Name, Index) => new {Name, Index})
すっきりしましたね。
LINQと一緒に匿名型を使うと.どうしても1行が長くなってしまいます。
その際このような短い書き方でインスタンスを生成できる場合,1行が短くなり,読み安くなりますね。
似ているとしても実は違う型になる場合がある
var person = new { Id = 0, Name = "Taro"};
今まで何回かNameがTaroでIdが0な匿名型のオブジェクトを作ってきましたが,何かが違います。気づいたでしょうか?
実は今までは,
var person = new { Name = "Taro", Id = 0 };
としていました。そうです,実は2つあるプロパティの順序が違います。
次のサンプルを見てください。
var nameIdTaro = new { Name = "Taro", Id = 0 };
var idNameTaro = new { Id = 0, Name = "Taro"};
Console.WriteLine (nameIdTaro.Equals (idNameTaro)); // Falseと出力
Console.WriteLine (idNameTaro.Equals (nameIdTaro)); // Falseと出力
結果は2つともFalseと出力されてしまいます。
nameIdTaroが参照するオブジェクトとidNameTaroが参照する匿名型のオブジェクトは,2つのプロパティを持ちます。
2つのプロパティは名前も型もその値も同じです。しかし型でプロパティが定義されている順番が違います。
この場合,二つは全く別の型として扱われます。
匿名型はプロパティの型,名前,そしてそれらプロパティの定義されている順序が同じ場合,同じ型とされます。
また,
var personA = new { Name = "Taro", Id = 0 };
var personB = new { Name = "Taro", Id = 0L };
次の二つは違う匿名型になります。EqualsをしでもFalseになります。
IdのプロパティがpersonAはint(System.Int32),personBはlong(System.Int64)になっているためです。
上記のように生成の際に0L
と書いてあると,「ここでlong型になっているからだ」と分かるのですが,LINQの中で用いていて,他のオブジェクトのプロパティから匿名型を生成する場合,注意が必要です。
おまけ 匿名型 -> ユーザー定義型を作った場合の落とし穴
最初は匿名型を使っていたけれど,匿名型で表現していたオブジェクトをメソッドの返り値にしたくなったから,ユーザー定義型として型を定義する,ということもあるのではないでしょうか。
この投稿でよく使っていた型だとこんなクラスですかね。
class Person {
public string Name{get; set;}
public int Id{get; set;}
}
こんなコードがあったとしましょう。
var persons = new []{
new {Name = "Taro", Id = 0},
new {Name = "Jiro", Id = 1},
new {Name = "Saburo", Id = 2},
new {Name = "Shiro", Id = 3},
};
Console.WriteLine (persons.Any (person => person.Name == "Taro" && person.Id == 0)); // True
Console.WriteLine (persons.Contains (new { Name = "Taro", Id = 0})); // True
先ほど定義したPersonクラスを利用します。
そうすると,AnyはTrueのままですがContainsは結果が変わってFalseになってしまいます。ここは注意が必要です。
var persons = new Person[]{
new Person{Name = "Taro", Id = 0},
new Person{Name = "Jiro", Id = 1},
new Person{Name = "Saburo", Id = 2},
new Person{Name = "Shiro", Id = 3},
};
Console.WriteLine (persons.Any (person => person.Name == "Taro" && person.Id == 0)); // True
Console.WriteLine (persons.Contains (new Person { Name = "Taro", Id = 0})); // False
参考書籍
- Jeffrey Richter (著), 藤原 雄介 (翻訳) (2013) プログラミング.NET FRAMEWORK 第4版.
- 川俣 晶 (2009) [完全版] 究極のC#プログラミング ~新スタイルによる実践的コーディング.
参考にさせていただいたページ・匿名型について掲載されているページ
http://csharp.keicode.com/basic/anonymous-types.php
http://csharp.keicode.com/basic/implicitly-typed-local-var.php
http://ufcpp.net/study/csharp/oo_class.html#anonymous
http://www.atmarkit.co.jp/fdotnet/extremecs/extremecs_13/extremecs_13_05.html