【C#】【book】「実戦で役立つ C# プログラミングのイディオム/定石&パターン」の学習記録(3)

下記の「実戦で役立つ Cプログラミングのイディオム/定石&パターン」を使った学習記録のページです。
本ページのプログラムは「実戦で役立つ Cプログラミングのイディオム/定石&パターン」をもとにして作成したものも多数あります。

3.1 ラムダ式以前

3.1.1 メソッドを引数に渡したい

  • 下記 Program.cs の Count1 関数を参照

3.1.2 デリゲートによる実現

  • 下記 Program.cs の Count2 関数を参照

3.1.3 匿名メソッドの利用

  • 下記 Program.cs の Count3 関数を参照

3.2 ラムダ式

3.2.1 ラムダ式とは?

  • 下記 Program.cs の Count4 関数を参照

3.2.2 ラムダ式を使った例

  • 下記 Program.cs の Count5 関数を参照
CountNum.cs
using System;
using System.Reflection;

namespace Count
{
  class Program
  {
    static void Main( string[] args )
    {
      var numbers = new[] { 5, 3, 9, 6, 7, 5, 8, 1, 0, 5, 10, 4 };
      Count1( numbers ); // 3.1.1 配列と数値を引数に取る
      Count2( numbers ); // 3.1.2 配列とデリゲートを引数に取る
      Count3( numbers ); // 3.1.3 匿名メソッドの利用
      Count4( numbers ); // 3.2 ラムダ式 (次の [1]~[4] のように形態が変わる)
      Count5( numbers ); // さまざまなパターン
    }

    //--------------------------------------
    // 3.1.1 配列と数値を引数に取る
    //--------------------------------------
    static void Count1( int[] numbers )
    {
      var methodName = MethodBase.GetCurrentMethod().Name;
      Console.WriteLine( "\n********* {0} *********", methodName );

      var count = CountNum.Count( numbers, 5 );
      Console.WriteLine( count );
    }

    //--------------------------------------
    // 3.1.2 配列とデリゲートを引数に取る
    //--------------------------------------
    static void Count2( int[] numbers )
    {
      var methodName = MethodBase.GetCurrentMethod().Name;
      Console.WriteLine( "\n********* {0} *********", methodName );

      var count = CountNum.Count( numbers, IsEven );
      Console.WriteLine( count );
    }
    // n が偶数かどうか判定する
    public static bool IsEven( int n )
    {
      return n % 2 == 0;
    }

    //--------------------------------------
    // 3.1.3 匿名メソッドの利用
    //--------------------------------------
    // デリゲートのジェネリック版である Predicate を持つ Count メソッドを呼び出す。
    // わざわざ IsEven() のような判定処理をするメソッドの作成が不要になる。
    static void Count3( int[] numbers )
    {
      var methodName = MethodBase.GetCurrentMethod().Name;
      Console.WriteLine( "\n********* {0} *********", methodName );

      var count = CountNum.CountAnon( numbers, delegate ( int n )
      {
        return n % 2 == 0;
      } );
      Console.WriteLine( count );
    }

    //--------------------------------------
    // 3.2 ラムダ式 (次の [1]~[4] のように形態が変わる)
    //--------------------------------------
    // [1]. デフォルト状態
    // var count = CountNum.Count( numbers, (int n) => { return n % 2 == 0; } );
    // [2] ラムダ式の中の処理が 1つのみの場合は {} と return を省略できる
    // var count = CountNum.Count( numbers, (int n) => n % 2 == 0 );
    // [3] ラムダ式では引数の型を省略することができる (型はコンパイラが判断してくれる)
    // var count = CountNum.Count( numbers, (n) => n % 2 == 0 );
    // [4] 引数が1つの場合はカッコ () を省略することができる
    // var count = CountNum.Count( numbers, (n) => n % 2 == 0 );
    static void Count4( int[] numbers )
    {
      var methodName = MethodBase.GetCurrentMethod().Name;
      Console.WriteLine( "\n********* {0} *********", methodName );

      var count = CountNum.Count( numbers, n => n % 2 == 0 );
      Console.WriteLine( count );
    }

    //--------------------------------------
    // 3.2.2 ラムダ式を使った例
    //--------------------------------------
    static void Count5( int[] numbers )
    {
      var methodName = MethodBase.GetCurrentMethod().Name;
      Console.WriteLine( "\n********* {0} *********", methodName );

      // 奇数の数を算出する
      var count = CountNum.Count( numbers, n => n % 2 == 1 );
      Console.WriteLine( count );

      // 偶数の数を表示する
      Console.Write( "\n**** find the even => " );
      count = 0;
      count = CountNum.Count( numbers, (int n) =>
      {
        if ( (n % 2) == 0 )
        {
          Console.Write( "{0} ", n );
          return true;
        }
        return false;
      } );
      Console.Write( "\n" );
      Console.WriteLine( "Number of even: {0}", count);

      // 5以上の数をカウントする
      Console.WriteLine( "\n------------------------" );
      Console.WriteLine( "5以上の数値の個数は {0}個です",
        CountNum.Count( numbers, n => n >= 5 ) );

      // 5以上 10未満の数をカウントする
      Console.WriteLine( "\n------------------------" );
      Console.WriteLine( "5以上 10未満の数は {0}個です",
        CountNum.Count( numbers, n => 5 <= n && n < 10 ) );

      // 数字の 1 が含まれている数をカウントする
      Console.WriteLine( "\n------------------------" );
      count = 0;
      count = CountNum.Count( numbers, n => n.ToString().Contains( '1' ) );
    }
  }
}

 

Program.cs
using System;
using System.Reflection;

namespace Count
{
  class Program
  {
    static void Main( string[] args )
    {
      var numbers = new[] { 5, 3, 9, 6, 7, 5, 8, 1, 0, 5, 10, 4 };
      Count1( numbers ); // 3.1.1 配列と数値を引数に取る
      Count2( numbers ); // 3.1.2 配列とデリゲートを引数に取る
      Count3( numbers ); // 3.1.3 匿名メソッドの利用
      Count4( numbers ); // 3.2 ラムダ式 (次の [1]~[4] のように形態が変わる)
      Count5( numbers ); // さまざまなパターン
    }

    //--------------------------------------
    // 3.1.1 配列と数値を引数に取る
    //--------------------------------------
    static void Count1( int[] numbers )
    {
      var methodName = MethodBase.GetCurrentMethod().Name;
      Console.WriteLine( "\n********* {0} *********", methodName );

      var count = CountNum.Count( numbers, 5 );
      Console.WriteLine( count );
    }

    //--------------------------------------
    // 3.1.2 配列とデリゲートを引数に取る
    //--------------------------------------
    static void Count2( int[] numbers )
    {
      var methodName = MethodBase.GetCurrentMethod().Name;
      Console.WriteLine( "\n********* {0} *********", methodName );

      var count = CountNum.Count( numbers, IsEven );
      Console.WriteLine( count );
    }
    // n が偶数かどうか判定する
    public static bool IsEven( int n )
    {
      return n % 2 == 0;
    }

    //--------------------------------------
    // 3.1.3 匿名メソッドの利用
    //--------------------------------------
    // デリゲートのジェネリック版である Predicate を持つ Count メソッドを呼び出す。
    // わざわざ IsEven() のような判定処理をするメソッドの作成が不要になる。
    static void Count3( int[] numbers )
    {
      var methodName = MethodBase.GetCurrentMethod().Name;
      Console.WriteLine( "\n********* {0} *********", methodName );

      var count = CountNum.CountAnon( numbers, delegate ( int n )
      {
        return n % 2 == 0;
      } );
      Console.WriteLine( count );
    }

    //--------------------------------------
    // 3.2 ラムダ式 (次の [1]~[4] のように形態が変わる)
    //--------------------------------------
    // [1]. デフォルト状態
    // var count = CountNum.Count( numbers, (int n) => { return n % 2 == 0; } );
    // [2] ラムダ式の中の処理が 1つのみの場合は {} と return を省略できる
    // var count = CountNum.Count( numbers, (int n) => n % 2 == 0 );
    // [3] ラムダ式では引数の型を省略することができる (型はコンパイラが判断してくれる)
    // var count = CountNum.Count( numbers, (n) => n % 2 == 0 );
    // [4] 引数が1つの場合はカッコ () を省略することができる
    // var count = CountNum.Count( numbers, (n) => n % 2 == 0 );
    static void Count4( int[] numbers )
    {
      var methodName = MethodBase.GetCurrentMethod().Name;
      Console.WriteLine( "\n********* {0} *********", methodName );

      var count = CountNum.Count( numbers, n => n % 2 == 0 );
      Console.WriteLine( count );
    }

    //--------------------------------------
    // 3.2.2 ラムダ式を使った例
    //--------------------------------------
    static void Count5( int[] numbers )
    {
      var methodName = MethodBase.GetCurrentMethod().Name;
      Console.WriteLine( "\n********* {0} *********", methodName );

      // 奇数の数を算出する
      var count = CountNum.Count( numbers, n => n % 2 == 1 );
      Console.WriteLine( count );

      // 偶数の数を表示する
      Console.Write( "\n**** find the even => " );
      count = 0;
      count = CountNum.Count( numbers, (int n) =>
      {
        if ( (n % 2) == 0 )
        {
          Console.Write( "{0} ", n );
          return true;
        }
        return false;
      } );
      Console.Write( "\n" );
      Console.WriteLine( "Number of even: {0}", count);

      // 5以上の数をカウントする
      Console.WriteLine( "\n------------------------" );
      Console.WriteLine( "5以上の数値の個数は {0}個です",
        CountNum.Count( numbers, n => n >= 5 ) );

      // 5以上 10未満の数をカウントする
      Console.WriteLine( "\n------------------------" );
      Console.WriteLine( "5以上 10未満の数は {0}個です",
        CountNum.Count( numbers, n => 5 <= n && n < 10 ) );

      // 数字の 1 が含まれている数をカウントする
      Console.WriteLine( "\n------------------------" );
      count = 0;
      count = CountNum.Count( numbers, n => n.ToString().Contains( '1' ) );
    }
  }
}

 

3.3 List クラスとラムダ式の組み合わせ

3.3.1 Exists メソッド

3.3.2 Find メソッド

3.3.3 FindIndex メソッド

3.3.4 FindAll メソッド

3.3.5 RemovedAll

3.3.6 ForEach メソッド

3.3.7 ConvertAll メソッド

下記 Program.cs を参照。

Program.cs
using System;
using System.Collections.Generic;
using System.Reflection;

namespace ListLambda
{
  class Program
  {
    static void Main( string[] args )
    {
      var list = new List<string>
      {
        "Tokyo", "New Delhi", "Bangkok", "London","Paris","Berlin","Canberra","Hong Kong",
      };
      ListExist( list );
      ListFind( list );
      ListFindIndex( list );
      ListFindAll( list );
      ListRemoveAll( list );
      ListForEach( list );
      ListConvertAll( list );
    }

    // 3.3.1 Exists メソッド
    // 条件に一致した Key が存在するか判定し、true か false を返す
    static void ListExist( List<string> list )
    {
      var methodName = MethodBase.GetCurrentMethod().Name;
      Console.WriteLine( "\n********* {0} *********", methodName );

      // 1文字目が 'A' で始まる要素が存在するか?
      var exists = list.Exists( s => s[0] == 'A' );
      Console.WriteLine( exists );

      // 1文字目が 'B' で始まる要素が存在するか?
      exists = list.Exists( s => s[0] == 'B' );
      Console.WriteLine( exists );
    }

    // 3.3.2 Find メソッド
    // 見付かった要素を返す
    static void ListFind( List<string> list )
    {
      var methodName = MethodBase.GetCurrentMethod().Name;
      Console.WriteLine( "\n********* {0} *********", methodName );

      var name = list.Find( s => s.Length == 6 );
      Console.WriteLine( name );
    }

    // 3.3.3 FindIndex メソッド
    // 見付かったインデックスを返す
    static void ListFindIndex( List<string> list )
    {
      var methodName = MethodBase.GetCurrentMethod().Name;
      Console.WriteLine( "\n********* {0} *********", methodName );

      int index = list.FindIndex( s => s == "Berlin" );
      Console.WriteLine( index );
    }

    // 3.3.4 FindAll メソッド
    // 引数で指定した条件と一致する全ての要素を取得する
    static void ListFindAll( List<string> list )
    {
      var methodName = MethodBase.GetCurrentMethod().Name;
      Console.WriteLine( "\n********* {0} *********", methodName );

      var names = list.FindAll( s => s.Length <= 5 );
      foreach ( var s in names )
        Console.WriteLine( s );
    }

    // 3.3.5 RemovedAll
    // 条件に一致する要素をリストから削除する
    static void ListRemoveAll( List<string> list )
    {
      var methodName = MethodBase.GetCurrentMethod().Name;
      Console.WriteLine( "\n********* {0} *********", methodName );

      var removedCount = list.RemoveAll( s => s.Contains( "on" ) );
      Console.WriteLine( removedCount );
    }

    // 3.3.6 ForEach メソッド
    static void ListForEach( List<string> list )
    {
      var methodName = MethodBase.GetCurrentMethod().Name;
      Console.WriteLine( "\n********* {0} *********", methodName );

      list.ForEach( s => Console.WriteLine( s ) );
      // ----------------------------------
      // 上記のコードは次の処理と同じである
      // ----------------------------------
      // foreach ( var s in list )
      // {
      //   Console.WriteLine( s );
      // }
    }

    // 3.3.7 ConvertAll メソッド
    // リスト内の要素を別の型に変換し、変換された要素が格納されたリストを返す
    static void ListConvertAll( List<string> list )
    {
      var methodName = MethodBase.GetCurrentMethod().Name;
      Console.WriteLine( "\n********* {0} *********", methodName );

      // 要素の文字列を小文字にしたリストを返す
      var lowerList = list.ConvertAll( s => s.ToLower() );
      lowerList.ForEach( s => Console.WriteLine( s ) );
    }
  }
}

 

3.4 LINQ to Objects の基礎

  • LINQ とは「Language Integrated Query」の略
    • 日本語に訳せば「言語に統合されたクエリ」
  • LINQ を使うと下記のようなさまざまなデータに対して、標準化された方法で問い合わせ処理が可能になる
    • オブジェクト
    • データベース
    • XML

 

3.4.1 LINQ to Objects の簡単な例

下記 Program.cs の次のメソッドを参照

  • LINQ2Obj_1
  • LINQ2Obj_2
  • LINQ2Obj_3
  • LINQ2Obj_4

3.4.2 クエリ演算子

f:id:dnkrnka:20181103010203p:plain:w900

3.4.3 シーケンス

  • 標準クエリ演算子の操作対象のデータを、シーケンス(「連続するもの」のという意味)と呼ぶ

3.4.4 遅延実行

下記 Program.cs の次のメソッドを参照

  • LINQ2Obj_5
  • LINQ2Obj_6
Program.cs
using System;
using System.Collections.Generic;
using System.Linq; // LINQ を使うための定義
using System.Reflection;

namespace LINQ2Obj
{
  class Program
  {
    static void Main( string[] args )
    {
      var names = new List<string>
      {
        "Tokyo", "New Delhi", "Bangkok", "London","Paris","Berlin","Canberra","Hong Kong",
      };
      LINQ2Obj_1( names );
      LINQ2Obj_2( names );
      LINQ2Obj_3( names );
      LINQ2Obj_4( names );
      LINQ2Obj_5( names.ToArray<string>() );
      LINQ2Obj_6( names.ToArray<string>() );
    }

    // 3.4.1 LINQ to Objects の簡単な例
    static void LINQ2Obj_1( List<string> names )
    {
      var methodName = MethodBase.GetCurrentMethod().Name;
      Console.WriteLine( "\n********* {0} *********", methodName );

      // - Where メソッドはシーケンスから条件を満したものだけを抽出する。
      // - IEnumerable を使うことで IEnumerable を実装した 配列, List<T>, Dictionary<TKey, TValue>
      //   などから Where メソッドを使うことが可能である。
      // - 1つの API で 配列, List, Dictionary に対応できるので型ごとに API を覚える必要が無い
      IEnumerable<string> query = names.Where( s => s.Length <= 5 );
      foreach ( string s in query )
      {
        Console.WriteLine( s );
      }
    }

    // 3.4.1 メソッドチェーン
    // Where, Select と並べている。
    static void LINQ2Obj_2( List<string> names )
    {
      var methodName = MethodBase.GetCurrentMethod().Name;
      Console.WriteLine( "\n********* {0} *********", methodName );

      IEnumerable<string> query = names.Where( s => s.Length <= 5 )
                                       .Select( s => s.ToLower() ); // シーケンスの各要素を新しい型に射影する
      foreach ( string s in query )
      {
        Console.WriteLine( s );
      }
    }

    // 3.4.1 実践的な表記方法
    // 上記コードより、var を使うように変更した
    static void LINQ2Obj_3( List<string> names )
    {
      var methodName = MethodBase.GetCurrentMethod().Name;
      Console.WriteLine( "\n********* {0} *********", methodName );

      var query = names.Where( s => s.Length <= 5 )
                       .Select( s => s.ToLower() ); // シーケンスの各要素を新しい型に射影する
      foreach ( string s in query )
      {
        Console.WriteLine( s );
      }
    }

    // 3.4.1 Select メソッドのみを使用した例
    static void LINQ2Obj_4( List<string> names )
    {
      var methodName = MethodBase.GetCurrentMethod().Name;
      Console.WriteLine( "\n********* {0} *********", methodName );

      var query = names.Select( s => s.Length ); // 文字列長を返す。int型になる。
      foreach ( var n in query ) // int型配列である query の要素を1つずつ取り出す
      {
        Console.Write("{0} ", n );
      }
      Console.WriteLine();
    }

    // 3.4.4 遅延実行
    // C言語のポインタと同じこと
    static void LINQ2Obj_5( string[] names)
    {
      var methodName = MethodBase.GetCurrentMethod().Name;
      Console.WriteLine( "\n********* {0} *********", methodName );

      var query = names.Where( s => s.Length <= 5 );

      foreach ( var item in query )
      {
        Console.WriteLine( item );
      }
      Console.WriteLine( "---------" );

      names[0] = "Osaka"; // names[0] はポインタなので、"Osaka" を格納したメモリアドレスが入る
      foreach ( var item in query )
      {
        Console.WriteLine( item ); // query[0] は names[] のへのポインタなので Osaka と表示される
      }
    }

    // 3.4.4 ToArrayメソッドによる即時実行
    static void LINQ2Obj_6( string[] names )
    {
      var methodName = MethodBase.GetCurrentMethod().Name;
      Console.WriteLine( "\n********* {0} *********", methodName );

      var query = names.Where( s => s.Length <= 5 )
                       .ToArray(); // 配列に変換する (C言語で言えば別のメモリが割り当てられた)
                                   // つまり、names と query は同じアドレスを指さなくなった
      foreach ( var item in query )
      {
        Console.WriteLine( item );
      }
      Console.WriteLine( "---------" );

      names[0] = "Osaka"; // names[0] はポインタなので、"Osaka" を格納したメモリアドレスが入る
      foreach ( var item in query )
      {
        Console.WriteLine( item ); // query[0] は names[] のへのポインタなので Osaka と表示される
      }
    }
  }
}