プロパティの値を変更したけど変わらない・・・
職場においてC#を利用して自社サービスを実装しているのですが、
IEnumerable
ではまったことをちょっとまとめておきたいと思います。
下記は起こった事象を簡単にしたものとなります。下記の例は、IEnumerable
に入ったユーザ情報の名前が大文字になることを期待したものとなります。
// このようなクラスを読み込んでいます
//class User {
// public string Name { get; set; }
//}
IEnumerable<User> users = new[] { "kato", "saito", "kondo" }
.Select(n => {
return new User { Name = n };
});
foreach (User user in users) {
// 大文字に変換したはずなのに・・・
user.Name = user.Name.ToUpper();
}
foreach (User user in users) {
Console.WriteLine(user.Name);
}
出力結果
kato
saito
kondo
なぜか最初の値のままでした・・・
そうなんです。参照が違っておりました。
Enumerable.Repeat
とかEnumerable.Select
とかが遅延評価のIEnumerable
を返すので、foreach
やToList
で評価された際に初期化されてしまいます。
修正パターン1
IEnumerable<User> users = new[] { "kato", "saito", "kondo" }
.Select(n => {
return new User { Name = n };
}).ToList();
foreach (User user in users) {
// 大文字に変換
user.Name = user.Name.ToUpper();
}
foreach (User user in users) {
Console.WriteLine(user.Name);
}
ToList()することで再度評価されても同じ参照が返るので無事、大文字にできました。
KATO
SAITO
KONDO
修正パターン2
修正パターン1だと、2ループしてしまうのは性能上問題があると思いますので、下記の方法を考えました。
IEnumerable<User> users = new[] { "kato", "saito", "kondo" }
.Select(n => {
return new User { Name = n };
});
users = users.Select(u => {
u.Name = u.Name.ToUpper();
return u;
});
foreach (User user in users) {
Console.WriteLine(user.Name);
}
初期化の後にSelect()に修正して、無事、
KATO
SAITO
KONDO
大文字に置換することができました!
実行環境
- C# 7.3
- Windows 10