複数のIEnumerable<T>型をまとめて廻す【Zip】

アドベントカレンダーに書くほどの内容でもないですが、空いていたので書いてみます。
実は初アドベントカレンダーです。
お手柔らかに。

# 複数のIEnumerable<T>型をまとめて廻す

IEnumerable<T>及びその派生型がいくつかあって、それらの数が一緒だとわかっている時同時にまわしたいと思いますよね。

```csharp

// こういうことをしたい（が、こんな書き方はできない）
IEnumerable<Person> person = xxx;
IEnumerable<Creature> creature = yyy;
foreach((x, y) in (person, creature))
{
	Console.writeLine($"{x} vs {y} の対決！");
}

// ネストさせてもダメ。x^2対決するハメになる
foreach(var x in person)
{
	foreach(var y in creature)
	Console.writeLine($"{x} vs {y} の対決！");
}

```

# Enumerable.Zip()
そこでEnumerable.[Zip()](https://docs.microsoft.com/ja-jp/dotnet/api/system.linq.enumerable.zip?view=net-5.0)を使う
Zipを使うと２つのIEnumerable<T>型をそれぞれイテレートしてタプルで返してくれる。
オーバーロードでイテレートした値をFunc<T, TResult>で返すこともできる。

```csharp
/// とりあえずクラス準備（Personクラス、CreatureクラスのIEnumerable<T>を廻したりする）
// 人間
public class Person
{
    public string Name {get;}
    public string Skill { get; }
    public Person(string x)
    {
        Name = $"{x}";
        Skill = $"{x}の放つ必殺技";
    }
}

// バケモノ
public class Creature
{
        public string Species { get; }
        public string Item { get; }
        public Creature(string x)
        {
            Species = x;
            Item = $"{x}のツメ";
        }
}

// 調合屋さん
public class Shop
{
    public string MixSkillAndItem(string skill, string item, string name)
    {
        return $"{skill}で焼いた${item} ~${name}は見ているだけ~";
    }
}

```

上のクラスをEnumerable.Zipで結合して廻します。

```csharp
public class Quest
{
    // ヒーローとヴィランの激アツトーナメント作成！
    // 同じ型のIEnumerabl<T> をそれぞれ対のモノとしてイテレーション
    public void MakeTournament()
    {
        var villains = Enumerable.Range(0, 10).Select(x=>new Person($"{x}番目に強いのヴィラン")).ToList();
        var heroes = Enumerable.Range(10, 20).Select(x=>new Person($"{x}番目に強いのヒーロー")).ToList();

        villains.Zip(heroes, (vi, he)=>(vi, he))
            .Select(x => $"{x.vi.Name} vs {x.he.Name}") // N番目のheroとN番目のヴィランが対決！
            .ToList()
            .ForEach(Console.WriteLine);

    }
	
    // ヒーローがクリーチャーを狩ってショップに売る
    // 違う型のIEumerable<T>もまとめて一度にイテレーション
    public void Battle()
    {
        var heroes = Enumerable.Range(10, 20).Select(x => new Person($"{x}番目に強いのヒーロー")).ToList();
        var goblins = Enumerable.Range(0, 10).Select(x => new Creature($"{x}番目に強いのゴブリン")).ToList();
        var gargoyle = Enumerable.Range(0, 10).Select(x => new Creature($"{x}番目に強いのガーゴイル")).ToList();

        heroes
        	.Zip(goblins, (hero, goblin) => (hero, goblin))
        	.Zip(gargoyle, (x, gargoyle) => (x.hero, x.goblin, gargoyle))
        	.Select(universe =>
        		new Shop().MixSkillAndItem(
        			universe.hero.Skill,
        			universe.gargoyle.Item,
        			universe.goblin.Species)) // N番目ヒーローSkill、N番目ガーゴイルのItem、N番目ゴブリンは見ているだけ。
        	.ToList()
        	.ForEach(Console.WriteLine); 
    }
}
```

結果は・・・

```csharp
new Quest().MakeTournament()
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/634043/da6e358a-0358-52e9-17ae-b44f5fb6e5e0.png)
対戦カードが決まったぜ！


```csharp
new Quest().Battle()
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/634043/b8a63c8b-514d-a8d2-8f6f-217e4777b40f.png)
ユーザビリティの低いアイテムを手に入れたぜ！




ちゃんちゃん

# おまけ
ちなみに`Zip()`するコレクションの長さが違った場合はどうなるのか。
インデックスで指定すると`IndexOoutOfRangeException`を投げるけど、First()やLast()でとると例外にならない。
~~遅延実行だからなのかな？~~

これは[結合する`IEnumerable<T>`型の短い方の長さまでしか処理をしない](https://source.dot.net/#System.Linq/System/Linq/Zip.cs,2b8d0f02389aab71)ためでした。
今回の場合、要素数がevenは3つ、oddは2つなので`Zip`で処理される要素数は2つになります。
よってインデックス2を指定しても`IndexOutOfRangeException`例外が投げられてしまうわけです。

@skitoy4321 様コメントありがとうございました！

```csharp
var even = new List<int>{2,4,6};
var odd = new List<int>{1,3};

var num = even.Zip(odd).Select(x => (x.First, x.Second));　// Select()で廻しただけではエラーにならない
var first = num.First(); 
var last = num.Last();    // First(), Last()でもエラーにならない
var assign = num.ToArray()[2];  // インデックスを指定するとエラー(IndexOutOfRangeException)になる
```
https://sharplab.io/#v2:C4LgTgrgdgNAJiA1AHwAICYAMBYAUBgRj1UwAJUCA6AGQEsoBHAbmLIoBYX8CBOACgBEgHg3AIPsCAlFzwA3AIZhSAU2mKopALykoigO6k6AZ2AAeesAB8Ab3Qx2MAGwBfLnIUB7OHA1bd+2kdMoC0sCGABmZzwZeS0IAFtvZVVKAC1aAAc+DzhxSgBlRQAbRQBjYD4ADw1zUkrKADFaMCMYUgr80rcoHMlohQAzJqNvKHiGofLe3FdSQtlhzVG4mnnJlxj5g1oAczVFsYAVNwBBMDBZAE8+cQBtdABdKVwgA===