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

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

4.1 初期化に関するイディオム

4.1.2 配列とリストの初期化

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

4.1.3 Dictionary の初期化

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

4.1.4 オブジェクトの初期化

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

4.2 判定と分岐に関するイディオム

4.2.5 bool値の値が真かどうかを判断する

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

4.3 繰り返しのイディオム

4.3.3 List のすべての要素に対して処理をする

下記 Program.cs の Idiom_5 メソッドを参照。
ループ処理は LINQ, foreach, for の順番で使用を検討する。
ただし、List の場合のみ LINQ, ForEach, foreach, for が良いとのこと。

4.4 条件演算子、null合体演算子によるイディオム

4.4.1 条件により代入する値を変更する

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

4.5 プロパティに関するイディオム

4.5.1 プロパティの初期化

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

Person.cs
using System;
using System.Collections.Generic;
using System.Text;

namespace Idiom
{
  public class Person
  {
    public string Name
    {
      get; set;
    }
    public DateTime Birthday
    {
      get; set;
    }
    public string PhoneNumber
    {
      get; set;
    }
    public int GetAge()
    {
      DateTime today = DateTime.Today;
      int age = today.Year - Birthday.Year;
      if ( today < Birthday.AddYears( age ) )
      {
        age--;
      }
      return age;
    }
  }
}
Program.cs
using System;
using System.Collections.Generic;
using System.Reflection;

namespace Idiom
{
  class Program
  {
    static void Main( string[] args )
    {
      Idiom_1();
      Idiom_2();
      Idiom_3();
      Idiom_4();
      Idiom_5();
      Idiom_6();
      Idiom_7();
    }

    static void Idiom_1()
    {
      // 4.1.2 配列とリストの初期化
      var methodName = MethodBase.GetCurrentMethod().Name;
      Console.WriteLine( "\n********* {0} *********", methodName );

      var langs = new string[] { "C#", "VB", "C++", };
      var nums = new List<int> { 10, 20, 30, 40, };

      foreach ( var lang in langs )
      {
        Console.Write( "{0} ", lang );
      }
      Console.Write( "\n" );

      foreach ( var num in nums )
      {
        Console.Write( "{0} ", num );
      }
      Console.Write( "\n" );
    }

    static void Idiom_2()
    {
      // 4.1.3 Dictionary の初期化
      var methodName = MethodBase.GetCurrentMethod().Name;
      Console.WriteLine( "\n********* {0} *********", methodName );

      var dict = new Dictionary<string, string>()
      {
        {"ja","日本語" },
        {"en","英語" },
        {"es","スペイン語" },
        {"de","ドイツ語" },
      };

      foreach ( var item in dict )
      {
        Console.WriteLine( $"{item.Key} {item.Value}" );
      }

      Console.WriteLine( "------------" );

      // C# 6.0 以降の記法
      var dictBest = new Dictionary<string, string>()
      {
        ["ja"] = "日本語",
        ["en"] = "英語",
        ["es"] = "スペイン語",
        ["de"] = "ドイツ語",
      };

      foreach ( var item in dictBest )
      {
        Console.WriteLine( $"{item.Key} {item.Value}" );
      }
    }

    static void Idiom_3()
    {
      // 4.1.4 オブジェクトの初期化
      var methodName = MethodBase.GetCurrentMethod().Name;
      Console.WriteLine( "\n********* {0} *********", methodName );

      var person = new Person
      {
        Name = "新井遥菜",
        Birthday = new DateTime( 1995, 11, 23 ),
        PhoneNumber = "012-3456-7890",
      };

      Console.WriteLine( $"{person.Name} {person.Birthday} {person.PhoneNumber}" );
    }

    static void Idiom_4()
    {
      // 4.2.5 bool値の値が真かどうかを判断する
      var methodName = MethodBase.GetCurrentMethod().Name;
      Console.WriteLine( "\n********* {0} *********", methodName );

      int? num = 10;
      if ( num.HasValue )
      {
        Console.WriteLine( $"num ={num} は整数値を持っています" );
      }
    }

    static void Idiom_5()
    {
      // 4.3.3 List<T> のすべての要素に対して処理をする
      var methodName = MethodBase.GetCurrentMethod().Name;
      Console.WriteLine( "\n********* {0} *********", methodName );

      var nums = new List<int> { 1, 2, 3, 4, 5 };
      nums.ForEach( n => Console.Write( "[{0}]", n ) );

      Console.Write( "\n" );
    }

    static void Idiom_6()
    {
      // 4.4.1 条件により代入する値を変更する
      var methodName = MethodBase.GetCurrentMethod().Name;
      Console.WriteLine( "\n********* {0} *********", methodName );

      // 変数(list) に値(key)が存在するか?
      var list = new List<int> { 10, 20, 30, 40, 50, };
      var key = 40;
      var num = list.Contains( key ) ? 1 : 0;
      Console.WriteLine( num );
    }

    // 4.5.1 プロパティの初期化
    static void Idiom_7()
    {
      var methodName = MethodBase.GetCurrentMethod().Name;
      Console.WriteLine( "\n********* {0} *********", methodName );

      var sc = new SampleCode();
      Console.WriteLine($"{sc.MinimumLength}");
      Console.WriteLine($"{sc.DefaultUrl}");
    }
  }

  class SampleCode
  {
    // 4.5.1 プロパティの初期化
    // C# 6.0 以降はプロパティの初期化をコンストラクタなしで実施可能である。

    public int MinimumLength { get; set; } = 6; // コンストラクタなしで初期化する

    public string DefaultUrl { get; set; } = GetDefaultUrl();

    private static string GetDefaultUrl()
    {
      return "http://www.msn.com/ja-jp/news/";
    }

  }
}

 

4.5.2 読み取り専用プロパティ

  • ここでは以下の2通りによる読み取り専用プロパティを作成する
    • set を private にする
    • set を作らない

詳細は Person.cs を参照すること。

Program.cs
using System;

namespace ReadOnly
{
  class Program
  {
    static void Main( string[] args )
    {
      var person = new Person( "Yamada", "Taro" );
      Console.WriteLine( $"{person.FamilyName} {person.GivenName}" );

      person.GivenName = "Suzuki"; // GivenName の Setter は塞いでいるので
                                   // 有効にするとコンパイルエラーになる
    }
  }
}

 

Person.cs
using System;
using System.Collections.Generic;
using System.Text;

namespace ReadOnly
{
  public class Person
  {
    public string GivenName
    {
      get; private set; // setter は外部に公開しないので読み取り専用となる
    }

    public string FamilyName
    {
      get; // setter 自体を定義しないので読み取り専用となる
    }

    public string Name
    {
      get
      {
        return FamilyName + " " + GivenName;
      }
    }

    // C# 6.0 では Getter 自体の定義も不要になり、Getter 処理をラムダ式にすることが可能である
    public string FullName => FamilyName + " " + GivenName;

    public string Name2 => FamilyName + " " + GivenName;

    // コンストラクタ
    public Person( string familyName, string givenName )
    {
      FamilyName = familyName;
      GivenName = givenName;
    }
  }
}

 

4.6 メソッドに関するイディオム

4.6.1 可変長引数

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

namespace Vargs
{
  class Program
  {
    static void Main( string[] args )
    {
      Vargs_1();
      Vargs_2();
    }

    // リスト4.36
    static void Vargs_1()
    {
      var methodName = MethodBase.GetCurrentMethod().Name;
      Console.WriteLine( "\n********* {0} *********", methodName );

      var median = new List<double> { };

      median.Add(Median( 1.0, 2.0, 3.0 ));
      median.Add(Median( 1.0, 2.0, 3.0, 4.0, 5.0 ));
      Console.WriteLine( $"median[0] = {median[0]}" );
      Console.WriteLine( $"median[1] = {median[1]}" );
    }

    // 中央値を求めるメソッド
    static double Median( params double[] args )
    {
      var sorted = args.OrderBy( n => n ).ToArray();
      int index = sorted.Length / 2;
      if ( sorted.Length % 2 == 0 )
      {
        return (sorted[index] + sorted[index - 1]) / 2;
      }
      else
      {
        return sorted[index];
      }
    }

    // リスト4.37
    static void Vargs_2()
    {
      var methodName = MethodBase.GetCurrentMethod().Name;
      Console.WriteLine( "\n********* {0} *********", methodName );

      var time = DateTime.Now;
      var user = "neko";
      var message = "テストです";
      WriteLog( "Time:{0:f} User:{1} Message:{2}", time, user, message );

    }

    // 可変長引数を使ったメソッドの定義
    static void WriteLog( string format, params object[] args )
    {
      var s = String.Format( format, args );
      Console.WriteLine( s );
    }
  }
}

 

4.7 その他のイディオム

4.7.2 逐語的リテラルを使う

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

4.7.4 文字列を数値に変換する

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

4.7.5 参照型のキャスト

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

4.7.6 例外の再スロー

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

4.7.7 using を使ったリソースの破棄

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

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

namespace IdiomMisc
{
  class Program
  {
    static void Main( string[] args )
    {
      IdiomMisc_1();
      IdiomMisc_2();
      IdiomMisc_3();
      // IdiomMisc_4();
      IdiomMisc_5();
      IdiomMisc_5_plus();
    }

    // 4.7.2 逐語的リテラルを使う
    // - \ がエスケープされないので、パスをそのまま書ける( \\ と並べる必要なし)
    // - ただし " を表現する場合は "" と並べる必要がある
    static void IdiomMisc_1()
    {
      var methodName = MethodBase.GetCurrentMethod().Name;
      Console.WriteLine( "\n********* {0} *********", methodName );

      var path1 = @"C:\Example\Greeting.txt"; // 逐語的リテラル使用時
      var path2 = "C:\\Example\\Greeting.txt"; // 逐語的リテラル不使用時

      Console.WriteLine( path1 );
      Console.WriteLine( path2 );
    }

    // 4.7.4 文字列を数値に変換する
    static void IdiomMisc_2()
    {
      var methodName = MethodBase.GetCurrentMethod().Name;
      Console.WriteLine( "\n********* {0} *********", methodName );

      var str = "123";
      int height;

      // TryParse メソッドでは out を使って良い。
      // out は参照渡しをするためのキーワードである。
      if ( int.TryParse( str, out height ) ) // 成功すると True を返す
      {
        Console.WriteLine( $"height = {height}" ); // heightには変換された値が入っている。
      }
      else
      {
        Console.WriteLine( "変換できません" );
      }

      //-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-
      //
      // 以下のようなコードは書いてはいけないとのこと。
      // - 面倒な記述かつ例外にコストが掛かるため
      //
      //-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-
      try
      {
        int retryCount = int.Parse( str ); // 変換に失敗すると例外が発生する
        Console.WriteLine( $"retryCount = {retryCount}" );
      }
      catch ( ArgumentNullException ex )
      {
        Console.WriteLine( "変換できません" );
      }
      catch ( FormatException ex )
      {
        Console.WriteLine( "変換できません" );
      }
    }

    // 4.7.5 参照型のキャスト
    static void IdiomMisc_3()
    {
      var methodName = MethodBase.GetCurrentMethod().Name;
      Console.WriteLine( "\n********* {0} *********", methodName );

      var Session = new Dictionary<string, object>(); // Session は値として object 型を返す辞書である
      Session["MyProduct"] = null;
      //Session["MyProduct"] = "a";

      var product = Session["MyProduct"] as Product; // object型を Product型にキャストする
      if ( product == null )
      {
        Console.WriteLine( "NG: product が取得できませんでした" );
      }
      else
      {
        Console.WriteLine( "OK: product が取得できました" );
      }

      //-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-
      //
      // 次のように is 演算子を使った書き方も可能であるが、
      // 上記のように as を使う方が簡潔で良い。
      //
      //-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-
      if ( Session["MyProduct"] is Product )
      {
        var prd = Session["MyProduct"] as Product;
      }
      else
      {
        Console.WriteLine( "NG-2: Product が取得できませんでした" );
      }
    }

    internal class Product
    {
    }

    // 4.7.6 例外の再スロー
    static void IdiomMisc_4()
    {
      var methodName = MethodBase.GetCurrentMethod().Name;
      Console.WriteLine( "\n********* {0} *********", methodName );

      try
      {
        // 存在しないファイルを読み込もうとした場合
        var lines = File.ReadAllLines( "xadfad40--adf--.txt" );
      }
      catch ( FileNotFoundException ex )
      {
        Console.WriteLine( "\n\n!!!!!!!!! 例外が発生しました !!!!!!!!!" );
        throw;        // ex を再スローする。
        // throw ex;  // なお、左記のように throw に ex を指定するのは誤りである
      }
    }

    // 4.7.7 using を使ったリソースの破棄
    // .NET Framework のクラスの中には明示的にメモリ解放させる必要があるクラスが存在する。
    // そして、IDisposable インタフェースクラスがこれに該当する。
    // 解放するためには Dispose メソッドの呼び出しを忘れずに行う必要があり、このために
    // using を使ってリソース破棄までをする。
    // なお、IDisposable インタフェースを実装していないクラスでは using は使えない。
    static void IdiomMisc_5()
    {
      var methodName = MethodBase.GetCurrentMethod().Name;
      Console.WriteLine( "\n********* {0} *********", methodName );

      var filePath = "C:/Users/neko/cs/IdiomMisc/IdiomMisc/obj/Debug/netcoreapp2.1/IdiomMisc.csproj.FileListAbsolute.txt";
      using ( var stream = new StreamReader( filePath ) ) // using を使う
      {
        var texts = stream.ReadToEnd();
        Console.WriteLine( $"{texts}" );
      }
    }

    // 4.7.7 using を使ったリソースの破棄
    // - try-finally を使ったリソース破棄(従来の方法)
    static void IdiomMisc_5_plus()
    {
      var methodName = MethodBase.GetCurrentMethod().Name;
      Console.WriteLine( "\n********* {0} *********", methodName );

      var filePath = "C:/Users/neko/cs/IdiomMisc/IdiomMisc/obj/Debug/netcoreapp2.1/IdiomMisc.csproj.FileListAbsolute.txt";

      StreamReader stream = new StreamReader( filePath );
      try
      {
        string texts = stream.ReadToEnd();
        Console.WriteLine( $"{texts}" );
      }
      finally
      {
        Console.WriteLine( "stream.Dispose()" );
        stream.Dispose(); // 最後に Dispose を呼び出してリソースを破棄する
      }
    }
  }
}