🚀

LINQ の遅延評価わかってなかった

2023/05/23に公開

ちょっと複雑なオブジェクトを弄っているときに、え?なんでこれ値が入らないんだ?
ってなったので、調査したら、全然自分の認識と違ったのでメモです。

検証で使った環境

  • LinqPad7
  • .NET : 3.1~7.0

TL;DR

LINQ、必要な場合はToArray()とかして、よきタイミングで明示的に評価しろ。

Selectの途中で、値吸い出そうとしたらできなかった

「なんでそんなことした??」ってのは置いておいて(やらないと大変な場合があったんです)、
以下のコードをご覧ください。
実際に悩んでたコードと似たことをするコードです。
コロン区切りの文字列配列を突っ込んだら、Key==Cの値と、KeyとValueのペアを得るコードです。

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,
		keyValues,
	};
}

void Main()
{
	var dataStrings = new string[]
	{
		"a:aaaaaa",
		"b:bbbbbb",
		"c:cccccc",
		"d:dddddd",
		"e:eeeeee",
	};
	
	SplitColonKV(dataStrings)
	.Dump();
}

では、これを実行してみましょう。
理想というか、想像はこうなりますね。

しかしどうでしょう、実際の実行結果はこうでした。

は??
cText おまえなんで値入ってないん???

遅延評価やで

なんかいろいろ調べて、冷静になったら、そらそうやろってなりました。
keyValues の値は return new するときまで評価されません。
ということは、return new するまで cText には何も代入されないので、cText は宣言されたときの値のままです。

なので、return new する前にToArrayとかして明示的に評価してあげます。

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
			};
		})
		.ToArray();
	
	return new
	{
		cText,
		keyValues,
	};
}

void Main()
{
	var dataStrings = new string[]
	{
		"a:aaaaaa",
		"b:bbbbbb",
		"c:cccccc",
		"d:dddddd",
		"e:eeeeee",
	};
	
	SplitColonKV(dataStrings)
	.Dump();
}

結果↓

まとめ

雰囲気でC#やってることがバレました。精進します。

Discussion