LINQ の記事読み下し
これはなに
が分からなかったので考えた。
まとめ
- string がキャプチャされてる
-
return new
ってなに?-
return new
しても評価されてない
-
以下、おもに return new 問題を考える
return new
したら評価される、とは。
keyValues の値は return new するときまで評価されません。
ということは、return new するまで cText には何も代入されないので、cText は宣言されたときの値のままです。
なので、return new する前に ToArray とかして明示的に評価してあげます。
とあるが、サンプルコードには 2 つの return new
があって、一つは Select 内の Func での return new
でもう一つが SplitColonKV
の値を決める return new
結論として keyValues
の右辺を .ToArray()
しているので、筆者がいう return new
は SplitColonKV
の最終行の return new
だと解釈した。
ただどちらの return new
だとしても return new
で値の評価は当然には起きない。
Enumerable.Select
の return
まず Select の return new
は、LINQ の評価が起きたときに、元の IEnumerable<T> のソースを何にマップするかなので、return new
が書いてあるからといって評価はされない。
SplitColonKV
の return
つぎに SplitColonKV
の末尾の return new
でも評価は、当然には起きない。
簡単には SharpLab で Dump した結果の型で確認できる。
{ cText = , keyValues = System.Linq.Enumerable+SelectArrayIterator`2[System.String,<>f__AnonymousTy…
となっており keyValues
は Iterator
として保持されている。
これに対して評価された場合の型は
using System;
using System.Linq;
object SplitColonKV(string [] dataStrings)
{
string cText = "";
var keyValues = dataStrings
.Select(x => {
var keyValue = x.Split(':');
var key = keyValue.FirstOrDefault();
var value = keyValue.ElementAtOrDefault(1);
if(key == "c")
{
cText = value;
}
return new
{
Key = key,
Value = value
};
});
return new
{
cText = cText,
keyValues = keyValues.ToArray(),
};
}
void Main()
{
var dataStrings = new string[]
{
"a:aaaaaa",
"b:bbbbbb",
"c:cccccc",
"d:dddddd",
"e:eeeeee",
};
SplitColonKV(dataStrings)
.Dump();
}
Main();
{ cText = , keyValues = <>f__AnonymousType0`2[System.String,System.String][] }
である。
ただ、このときですら、cText は string.Empty となっている。
つまり、本質的には cText がキャプチャされる前に評価を起こせばよいので必要があるので、return new
の順序を入れ換えるだけで動作がかわる。
using System;
using System.Linq;
object SplitColonKV(string [] dataStrings)
{
string cText = "";
var keyValues = dataStrings
.Select(x => {
var keyValue = x.Split(':');
var key = keyValue.FirstOrDefault();
var value = keyValue.ElementAtOrDefault(1);
if(key == "c")
{
cText = value;
}
return new
{
Key = key,
Value = value
};
});
return new
{
keyValues = keyValues.ToArray(),
cText = cText,
};
}
void Main()
{
var dataStrings = new string[]
{
"a:aaaaaa",
"b:bbbbbb",
"c:cccccc",
"d:dddddd",
"e:eeeeee",
};
SplitColonKV(dataStrings)
.Dump();
}
Main();
{ keyValues = <>f__AnonymousType0`2[System.String,System.String][], cText = cccccc }
従って return new
したからといって評価されているわけではないし、return new
の前に評価しても意味がなく cText
が固定するまでに評価を起こす必要がある。
あるいは、また cText
を参照型にすれば、事後的に keyValues
を評価することで、return new
のさらに後から cText
を書き換えることも可能となる。
using System;
using System.Linq;
using System.Collections.Generic;
object SplitColonKV(string [] dataStrings)
{
var cText = new string[1]{""};
var keyValues = dataStrings
.Select(x => {
var keyValue = x.Split(':');
var key = keyValue.FirstOrDefault();
var value = keyValue.ElementAtOrDefault(1);
if(key == "c")
{
cText[0] = value;
}
return new
{
Key = key,
Value = value
};
});
return new
{
cText,
keyValues,
};
}
void Main()
{
var dataStrings = new string[]
{
"a:aaaaaa",
"b:bbbbbb",
"c:cccccc",
"d:dddddd",
"e:eeeeee",
};
var obj = SplitColonKV(dataStrings);
foreach(var prop in obj.GetType().GetProperties())
{
var content = prop.GetValue(obj) as IEnumerable<object>;
if(content != null)
{
foreach(var i in content) {
Console.WriteLine(prop.Name);
Console.WriteLine(i);
}
}
}
foreach(var prop in obj.GetType().GetProperties())
{
var content = prop.GetValue(obj) as IEnumerable<object>;
if(content != null)
{
foreach(var i in content) {
Console.WriteLine(prop.Name);
Console.WriteLine(i);
}
}
}
}
Main();
- 出力
cText
keyValues
{ Key = a, Value = aaaaaa }
keyValues
{ Key = b, Value = bbbbbb }
keyValues
{ Key = c, Value = cccccc }
keyValues
{ Key = d, Value = dddddd }
keyValues
{ Key = e, Value = eeeeee }
cText
cccccc
keyValues
{ Key = a, Value = aaaaaa }
keyValues
{ Key = b, Value = bbbbbb }
keyValues
{ Key = c, Value = cccccc }
keyValues
{ Key = d, Value = dddddd }
keyValues
{ Key = e, Value = eeeeee }
1 度評価が起こった後のループでは cText に値が入っている。
事前にプロパティが分からない object で確実に列挙を起こす方法がこれくらいしか方法が思いつかなかったが、要は cText のデータを利用するより先に keyValues の結果を取得していれば問題ない。
さいごに
-
return new
によっても LINQ の評価は当然には起こらない。 - 関数のスコープを
return
で抜けたさらにその後で LINQ の評価を起こして、値の変化を他の変数に波及させることは可能。
結局変数のスコープの問題であり、プリミティブ型である string が return のときに、値を固定されたことがキモ。LINQ じゃなく string がわかってなかったと見るべき。
- Select で副作用を起こすコードはやめたほうが無難。
Discussion