やってみる

アウトプットすべく己を導くためのブログ。その試行錯誤すらたれ流す。

C#チュートリアル(LINQ)

 これは抑えておきたい。

成果物

コード

Code0.cs

using System;
using System.Collections.Generic;
using System.Linq;

namespace Tutorial_Linq
{
    class Code1
    {
        public void Run()
        {
            var startingDeck = from s in Suits()
                               from r in Ranks()
                               select new { Suit = s, Rank = r };
//          var startingDeck = Suits().SelectMany(suit => Ranks().Select(rank => new { Suit = suit, Rank = rank }));

            // Display each card that we've generated and placed in startingDeck in the console
            foreach (var card in startingDeck)
            {
                Console.WriteLine(card);
            }
        }
        public IEnumerable<string> Suits()
        {
            yield return "clubs";
            yield return "diamonds";
            yield return "hearts";
            yield return "spades";
        }
        public IEnumerable<string> Ranks()
        {
            yield return "two";
            yield return "three";
            yield return "four";
            yield return "five";
            yield return "six";
            yield return "seven";
            yield return "eight";
            yield return "nine";
            yield return "ten";
            yield return "jack";
            yield return "queen";
            yield return "king";
            yield return "ace";
        }
            }
}
$ dotnet run
{ Suit = clubs, Rank = two }
{ Suit = clubs, Rank = three }
{ Suit = clubs, Rank = four }
{ Suit = clubs, Rank = five }
{ Suit = clubs, Rank = six }
{ Suit = clubs, Rank = seven }
{ Suit = clubs, Rank = eight }
{ Suit = clubs, Rank = nine }
{ Suit = clubs, Rank = ten }
{ Suit = clubs, Rank = jack }
{ Suit = clubs, Rank = queen }
{ Suit = clubs, Rank = king }
{ Suit = clubs, Rank = ace }
{ Suit = diamonds, Rank = two }
{ Suit = diamonds, Rank = three }
{ Suit = diamonds, Rank = four }
{ Suit = diamonds, Rank = five }
{ Suit = diamonds, Rank = six }
{ Suit = diamonds, Rank = seven }
{ Suit = diamonds, Rank = eight }
{ Suit = diamonds, Rank = nine }
{ Suit = diamonds, Rank = ten }
{ Suit = diamonds, Rank = jack }
{ Suit = diamonds, Rank = queen }
{ Suit = diamonds, Rank = king }
{ Suit = diamonds, Rank = ace }
{ Suit = hearts, Rank = two }
{ Suit = hearts, Rank = three }
{ Suit = hearts, Rank = four }
{ Suit = hearts, Rank = five }
{ Suit = hearts, Rank = six }
{ Suit = hearts, Rank = seven }
{ Suit = hearts, Rank = eight }
{ Suit = hearts, Rank = nine }
{ Suit = hearts, Rank = ten }
{ Suit = hearts, Rank = jack }
{ Suit = hearts, Rank = queen }
{ Suit = hearts, Rank = king }
{ Suit = hearts, Rank = ace }
{ Suit = spades, Rank = two }
{ Suit = spades, Rank = three }
{ Suit = spades, Rank = four }
{ Suit = spades, Rank = five }
{ Suit = spades, Rank = six }
{ Suit = spades, Rank = seven }
{ Suit = spades, Rank = eight }
{ Suit = spades, Rank = nine }
{ Suit = spades, Rank = ten }
{ Suit = spades, Rank = jack }
{ Suit = spades, Rank = queen }
{ Suit = spades, Rank = king }
{ Suit = spades, Rank = ace }

要点

IEnumerable型のLINQ

 IEnumerable型をクロス結合する。SQL文でいうとcross join。全パターン網羅。

var startingDeck = from s in Suits()
                   from r in Ranks()
                   select new { Suit = s, Rank = r };

 同様のことを以下のメソットで記述できる。

var startingDeck = Suits().SelectMany(suit => Ranks().Select(rank => new { Suit = suit, Rank = rank }));

IEnumerableメソッド

  • SelectMany()
  • Select()
  • Take()
  • Skip()

バグ

匿名クラスの型はdynamicである

 LINQのコードでIEnumerable<T>型を受け取るとき、しばしばvar型を使う。だが、var型はローカル変数にしか使えない。var型はメソッドの戻り値に使えない。そこでdynamic型を使う。

private System.Collections.Generic.IEnumerable<dynamic> Create() {
    var startingDeck = from s in Suits()
                       from r in Ranks()
                       select new { Suit = s, Rank = r };
    return startingDeck;
}

エラーログ

 IEnumerable<T>には変換できない。以下のように怒られる。

private System.Collections.Generic.IEnumerable<string> Create() {
Code2.cs(22,20): error CS0266: 型 'System.Collections.Generic.IEnumerable<<anonymous type: string Suit, string Rank>>''System.Collections.Generic.IEnumerable<string>' に暗黙的に変換できません。明示的な変換が存在します (cast が不足していないかどうかを確認してください) [/tmp/work/Tutorial_Linq/Tutorial_Linq.csproj]

 IEnumerable<T, T>も使えない。以下のように怒られる。

private System.Collections.Generic.IEnumerable<string, string> Create() {
Code2.cs(17,44): error CS0305: ジェネリック 種類 'IEnumerable<T>' を使用するには、1 型引数が必要です。 [/tmp/work/Tutorial_Linq/Tutorial_Linq.csproj]

 どうやらIEnumerable<T>しか使えないらしい。では、IEnumerable<T>Tに匿名クラスnew { Suit = s, Rank = r }を含めたいときはどうすればいいのか? 「C# 無名クラスを返す メソッド シグネチャ」でググると以下がヒットした。

 匿名クラスに対応する型はdynamicらしい。というわけで、IEnumerable<dynamic>とした。エラーが消えた。

 ちなみに、from ... select new { Suit = s, Rank = r }の戻り値の型を調べてみた。

Console.WriteLine(startingDeck.GetType());
System.Linq.Enumerable+<SelectManyIterator>d__180`3[System.String,System.String,<>f__AnonymousType0`2[System.String,System.String]]

 AnonymousTypeとかいうのが匿名クラスらしい。Anonymousの意味を調べるとそのまま「匿名」だった。

対象環境

$ uname -a
Linux raspberrypi 4.19.42-v7+ #1218 SMP Tue May 14 00:48:17 BST 2019 armv7l GNU/Linux