🍊

LINQ の記事読み下し

2023/05/24に公開

これはなに

https://zenn.dev/tessecraft_r/articles/22ec87f2c1ea9c

が分からなかったので考えた。

まとめ

  • 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 newSplitColonKV の最終行の return new だと解釈した。

ただどちらの return new だとしても return new で値の評価は当然には起きない。

Enumerable.Selectreturn

まず Select の return new は、LINQ の評価が起きたときに、元の IEnumerable<T> のソースを何にマップするかなので、return new が書いてあるからといって評価はされない。

SplitColonKVreturn

つぎに SplitColonKV の末尾の return new でも評価は、当然には起きない。

簡単には SharpLab で Dump した結果の型で確認できる。

https://sharplab.io/#v2:C4LgTgrgdgNAJiA1AHwAICYCMBYAUKgBgAJVMA6AGQEsoBHAbjzwHsAjAKwFMBjYIgZQAOAGyrAAws2HMoAaQBqAClLEA2gF0icAIbBt/YGBoBzAM4BKPAG88ASBVFuAFU4APPgF4iAIm+NctgBu2mBEANacAJ7y2sIQnKZEXjp6BkZQZna2ZPycwjzAiq5JAHxENgG2QSHhUTFxnElErjkiYooA5CAd5v5V1aERkU1D9fFkAGJUYKbAAPJgACKcAGbaEMKFvVkDRMENI3Wx4wCi+QC2nFDAAILzS6vrm4qY25W2VCuKQ0le3tzeSzvCr9WzONyePbHTh9KoAXx2qAA7EQoJwAO47EH9WRRQ6RGA7WxjRpefbxHZw2Fwt62OzI1EYuzY8HuQmVUbQ0zs2xUvAI3B4VAAFiIAFltDRFECQcFQil9IYTIkvGj0SRMAQNMyst5tCBtIajd4ebZvKwQKwrdaTbruCBuI6nbbKt4EHAPZ6XVVvJwQJwA4HvXyAnYhKIJFIZApFAq0sqgdlFhBzoJpf4BXgJVLekA=

{ cText = , keyValues = System.Linq.Enumerable+SelectArrayIterator`2[System.String,<>f__AnonymousTy…

となっており keyValuesIterator として保持されている。

これに対して評価された場合の型は

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();

https://sharplab.io/#v2:C4LgTgrgdgNAJiA1AHwAICYCMBYAUKgBgAJVMA6AGQEsoBHAbjzwHsAjAKwFMBjYIgZQAOAGyrAAws2HMoAaQBqAClLEA2gF0icAIbBt/YGBoBzAM4BKPAG88ASBVFuAFU4APPgF4iAIm+NctgBu2mBEANacAJ7y2sIQnKZEXjp6BkZQZna2ZPycwjzAiq5JAHxENgG2QSHhUTFxnElErjkiYooA5CAd5v5V1aERkU1D9fFkAGJUYKbAAPJgACKcAGbaEMKFvVkDRMENI3Wx4wCi+QC2nFDAAILzS6vrm4qY25W2VCuKQ0le3tzeSzvCr9WzONyePbHTh9KoAXx2qAA7EQoJwAO47EH9WRRQ6RGA7WxjRpefbxHZw2Fwt62OzI1EYuzY8HuJqs4CEyqjaGJLw8hqmMhOZg3MBgbSRRTmLm2Kl4BG4PCoAAsRAAstoaNLrHZgqEUvpDCY+Yz0SRMAQNMyst5tCBtI6nd5ZbZvKwQKwvd6XbbuCBuIGg77Kt4EHAI5GQ1VvJwQJwE4no/KAnYhKIJFIZApFIa0iagdlFhBzoJpf5FXhNdrekA=

{ 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();

https://sharplab.io/#v2:C4LgTgrgdgNAJiA1AHwAICYCMBYAUKgBgAJVMA6AGQEsoBHAbjzwHsAjAKwFMBjYIgZQAOAGyrAAws2HMoAaQBqAClLEA2gF0icAIbBt/YGBoBzAM4BKPAG88ASBVFuAFU4APPgF4iAIm+NctgBu2mBEANacAJ7y2sIQnKZEXjp6BkZQZna2ZPycwjzAiq5JAHxENgG2QSHhUTFxnElErjkiYooA5CAd5v5V1aERkU1D9fFkAGJUYKbAAPJgACKcAGbaEMKFvVkDRMENI3Wx4wCi+QC2nFDAAILzS6vrm4qY25W2VCuKQ0le3tzeSzvCr9WzONyePbHTh9KoAXx2qAA7EQoJwAO47EH9WRRQ6RGA7WxjRpefbxHZw2Fwt62OzI1EYuzY0bQxJeVkNUxkJzMG5gMDaSKKcyEyrg9xNCXAMVEOVEKl4BG4PCoAAsRAAstoaCLrHZgqEUvpDCZ2Yz0SRMAQNMyst5tCBtM6Xd4xVVvKwQKwfb63fbuCBuMGQ/7Kt4EHAo9Gwx7OCBOImk7HFQE7EJRBIpDIFIpjWkzUDsosIOdBCL/Mq8Nrdb0gA===

{ 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();

https://sharplab.io/#v2:C4LgTgrgdgNAJiA1AHwAICYCMBYAUKgBgAJVMA6AGQEsoBHAbj0JMwBZHc8B7AIwCsApgGNgRAMoAHADZVgAYS5SuUANIA1ABSliAbQC6ROAENgRscDA0A5gGcAlHgDeeAJAA3I2CJCAKgIAeogC8RFACAO4sBDqYeo4ARPEAvhzunkQA1gIAnmpGUhACNkQhxqbmllC2ri5kYgJSwsAa/iUAfETOuC49Hl5ZufmFJUT+ddKyGgDkIFN2qb3pAyMDeQUCZABiVGA2wADyYAAiAgBmRhBSzfM1aV4e6ys5a4VkAKKNALYCUMAAggdjmcLlcNJgbt0elRThplkEQvEhPEHJCXF0ej1fAFgDoCAYQg9CgsXElbqgAOyhCK3dEYlQ5J7ZGC3FwvAQjQkCW4pGpJCEuVwUqnhVy0rGBZmQ1ZDIqSojyog83Ckzj4VhEACyRhoGhR6L6hhMZgs1mKITCkW0+lFNXiRhARkdTvikp68R4IB4Xu9LttQhAQkDQd9kPiCDgEcjIbdAhAAnjCejSoF3QNvD4I0kMnkimU6g0ZWNlVsEIVRDwZdOXDAAiMQgAFhoDRIwFwJEQaER02QAOICYA+bISAS63v9gAKreHYGAVCKupRZa6ZbLBqEymAP2CRBbbbHwDZGnTdiIRmKpAAzAAedNNNocFcK6Eade/LdEACE5suUkXj86CoVv+lbVrWDZNukVAdlA3gbluJ7LsB/6kAAnBou4SGQAByRjfKWSGPqhGhUPhBEqsB5EKpR8pVjWdaNs2U7QV2/D7oOw6jn2wCTm2AgznONgLkB8qIY+a5wb8IwYfuh7Hqe56YNet4iPewkrs+r6bpJX6hD+f6Po4gG4ARNGgfREFeFBnaafBnRqSZLBodJOF4Q+DkKkRJFuWR9lUWpKoqngWo6vMQA

  • 出力
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 で副作用を起こすコードはやめたほうが無難。
GitHubで編集を提案

Discussion