■C#

※本ページのサンプルコードは Visual Studio 2017 ( C# 7 )で実装している。
したがってC# または .NET Framework のバージョンが古い環境ではサンプルコードがコンパイルエラーとなる場合があるので注意が必要である。
バージョンによる差異については++c++; // 未確認飛行 Cというサイトで詳しく解説されているので確認すること。

Top


1.文字列

2.null許容型

3.型推論

4.匿名型

5.列挙型

6.共用体

7.operator

8.型テストとキャスト演算子

9.ユーザー定義の変換演算子

10.配列

11.Collections

12.クラス

13.IComparable

14.IEnumerableとIEnumerator

15.IDispose

16.メソッド

17.ラムダ式

18.デリゲート

19.ジェネリック

20.イベント

21.yield

22.LINQ

23.正規表現

24.XML ドキュメント コメント

25.Trace

26.デバッグ情報出力

27.Obsolete

29.例外処理

30.キュー

31.スタック

32.非同期処理

33.並列処理

34.Thread

35.#pragma

36.ログ出力

37.動的型付け

38.プロセス管理

39.拡張機能

40.NuGet

41.暗号化と復号化

42.処理時間を計測する

43.パフォーマンスカウンターを使用する

44.ファイルアップロード

45.ZIP圧縮

46.アセンブリ


■文字列

文字列型の宣言
stringString がある。
stringSystem.String のエイリアス(別名定義)なためどちらを使用しても問題ない。
どちらを使用するかはコーディングルールに従うことになるかと思うが、通常変数定義としては string を使用し、 文字列クラスのスタティックメソッドを使用する場合は String を使用するらしい。

宣言と同時に初期化可能。


string str = "";

文字列長
現在の文字列内の文字数を取得する。
string str = "abc";
int cnt1 = str.Length;   // 3

string str2 = "あいう";
int cnt2 = str2.Length;  // 3

MSDN

文字コードを指定してバイト単位で桁数を取得する。

string str3 = "あいう";

// Shift_JISでのバイト数を取得する
int cnt3 = Encoding.GetEncoding("Shift_JIS").GetByteCount(str3);   // 6

MSDN

文字列の連結
+ で連結する
string str = "abc" + "def";

指定した文字列配列の要素のメンバーを連結する。各要素の間には、指定した区切り記号が挿入される。
string[] str = new string[3]{ "abc", "def", "ghi" };

string str1 = String.Join( ",", str );   // "abc,def,ghi"
       

MSDN


    
    
    
  


  
文字列の分割
引数の配列内の文字に基づいて文字列を部分文字列に分割する。区切り文字が見つからない場合は元の文字列を返す。

string str = "abc def/ghe.";

string[] arr = str.Split(' ');   // "abc"、"def/ghe."

// 半角スペースとスラッシュで分割
string[] arr2 = str.Split(new char[2] { ' ', '/' });   // "abc"、"def"、"ghe."

// 文字が見つからない場合
string[] arr3 = str.Split('w');   // "abc def/ghe."

// "def"で分割
string[] arr4 = str.Split(new string[1]{ "def" }, StringSplitOptions.None);   // "abc "、"/ghe."

// 'd'、'e'、'f'で分割し、空白含む
string[] arr5 = str.Split("def".ToCharArray(), StringSplitOptions.None);   // "abc "、、""、""、"/gh"、"."

// 'd'、'e'、'f'で分割し、空白含まない
string[] arr6 = str.Split("def".ToCharArray(), StringSplitOptions.RemoveEmptyEntries);   // "abc "、"/gh"、"."
       

MSDN

文字列の置換
現在の文字列に出現する指定した Unicode 文字または文字列をすべて、別の指定した Unicode 文字または文字列に置換した新しい文字列を返す。
string str = "abc/def/abc/def";

string str2 = str.Replace('d', 'D');   // "abc/Def/abc/Def"

string str3 = str.Replace("def", "DEF");   // "abc/DEF/abc/DEF"

// 文字列が見つからない場合
string str4 = str.Replace("xyz", "XYZ");   // "abc/def/abc/def"
       

MSDN

文字列の挿入
現在の文字列内の指定したインデックス位置( 0開始 )に指定した文字列を挿入した、新しい文字列を返す。

なお引数値に文字列の範囲を超えるような値を指定した場合は例外が発生するので注意すること。


string str = "abc/def/abc/def";

string str2 = str.Insert(2, "xyz");   // "abxyzc/def/abc/def"

string str3 = str.Insert(20, "xyz");   // 例外発生!!
       

MSDN

文字列の削除
現在の文字列から指定範囲内の文字列を削除した新しい文字列を返す。

なお引数値に文字列の範囲を超えるような値を指定した場合は例外が発生するので注意すること。


string str = "abc/def/abc/def";

// 現在の文字列の指定した位置( 0開始 )から指定した最後の位置までの全文字が削除された新しい文字列を返す
string str2 = str.Remove(2);   // "ab"

// 現在の文字列内の指定した位置( 0開始 )から指定した文字数が削除された新しい文字列を返す
string str3 = str.Remove(2, 6);   // "ababc/def"

string str4 = str.Remove(20);   // 例外発生!!

string str5 = str.Remove(0, 20);  // 例外発生!!
       

MSDN

部分文字列の取得
現在の文字列から部分文字列を取得

なお引数値に文字列の範囲を超えるような値を指定した場合は例外が発生するので注意すること。


string str = "abあ/def/abc/def";

// 文字列中の指定した文字の位置( 0開始 )で開始し、文字列の末尾まで続く。
string str1 = str.Substring(5);   // "ef/abc/def"

// 指定した文字位置( 0開始 )から開始し、指定した文字数の文字列を返す
string str2 = str.Substring(2, 3);   // "あ/d"

string str3 = str.Substring(20);   // 例外発生!!

string str4 = str.Substring(0, 20);   // 例外発生!!
       

MSDN

文字列比較
2 つの 文字列の値が同一かどうかを判断する。型でもクラスオブジェクトでもある文字列型は ==String.Equals のどちらでも使える。

大文字小文字区別して判定する。


string str = "abc def/ghe.";

if( "abc" == str )
{
    Debug.WriteLine("abc -> 一致");
}
else
{
    Debug.WriteLine("abc -> 不一致");
}

if ("abc def/ghe." == str)
{
    Debug.WriteLine("abc def/ghe. -> 一致");
}
else
{
    Debug.WriteLine("abc def/ghe. -> 不一致");
}

if( "Abc def/ghe." == str )
{
    Debug.WriteLine( "Abc def/ghe. -> 一致" );
}
else
{
    Debug.WriteLine( "Abc def/ghe. -> 不一致" );
}

// abc -> 不一致
// abc def/ghe. -> 一致
// Abc def/ghe. -> 不一致

       

文字列検索
現在の文字列に指定した文字または文字列が含まれているかの判定結果をbool型で返す。

大文字小文字区別して判定する


string str = "abc def/ghe.";

if (str.Contains( 'd'))
{
    Debug.WriteLine("d -> 見つかった");
}
else
{
    Debug.WriteLine("d -> 見つからない");
}

if (str.Contains('D'))
{
    Debug.WriteLine("D -> 見つかった");
}
else
{
    Debug.WriteLine("D -> 見つからない");
}

if (str.Contains("def"))
{
    Debug.WriteLine("def -> 見つかった");
}
else
{
    Debug.WriteLine("def -> 見つからない");
}

if (str.Contains("DEF"))
{
    Debug.WriteLine("DEF -> 見つかった");
}
else
{
    Debug.WriteLine("DEF -> 見つからない");
}

// d -> 見つかった
// D -> 見つからない
// def -> 見つかった
// DEF -> 見つからない
       

MSDN

現在の文字列の先頭が、指定した文字列と一致するかの判定結果をbool型で返す。

大文字小文字区別して判定する。


string str = "abc def/ghe.";

if( str.StartsWith( "abc" ) )
{
    Debug.WriteLine( "abc -> 見つかった" );
}
else
{
    Debug.WriteLine( "abc -> 見つからない" );
}

if( str.StartsWith( "ghe." ) )
{
    Debug.WriteLine( "ghe. -> 見つかった" );
}
else
{
    Debug.WriteLine( "ghe. -> 見つからない" );
}

if( str.StartsWith( "ABC" ) )
{
    Debug.WriteLine( "ABC -> 見つかった" );
}
else
{
    Debug.WriteLine( "ABC -> 見つからない" );
}

// abc -> 見つかった
// ghe. -> 見つからない
// ABC -> 見つからない

       

MSDN

現在の文字列の末尾が、指定した文字列と一致するかの判定結果をbool型で返す。

大文字小文字区別して判定する。


string str = "abc def/ghe";

if( str.EndsWith( "abc" ) )
{
    Debug.WriteLine( "abc -> 見つかった" );
}
else
{
    Debug.WriteLine( "abc -> 見つからない" );
}

if( str.EndsWith( "ghe" ) )
{
    Debug.WriteLine( "ghe -> 見つかった" );
}
else
{
    Debug.WriteLine( "ghe -> 見つからない" );
}

if( str.EndsWith( "GHE" ) )
{
    Debug.WriteLine( "GHE -> 見つかった" );
}
else
{
    Debug.WriteLine( "GHE -> 見つからない" );
}

// abc -> 見つからない
// ghe -> 見つかった
// GHE -> 見つからない

       

MSDN

現在の文字列内で最初に出現する指定 Unicode 文字または文字列の 0 から始まるインデックスを返す。 このインスタンス内で文字または文字列が見つからない場合、このメソッドは -1 を返す。

大文字小文字区別して判定する。

なお引数値に文字列の範囲を超えるような値を指定した場合は例外が発生するので注意すること。


string str = "abcd efgh/abcd:abcd";

int index1 = str.IndexOf( 'b' );   // 1

int index2 = str.IndexOf( 'B' );   // -1

int index3 = str.IndexOf( "cd" );   // 2

int index4 = str.IndexOf( "CD" );   // -1

// 指定したインデックス以降の文字列内で検索する
int index5 = str.IndexOf( 'b', 11 );   // 11

int index6 = str.IndexOf( "cd", 12 );   // 12

int index7 = str.IndexOf( "ab", 17 );   // -1

int index8 = str.IndexOf( "cd", 20 );   // "例外発生!!"

// 指定した範囲内で検索する
int index9 = str.IndexOf( 'b', 11, 1 );   // 11

int index10 = str.IndexOf( "cd", 12, 2 );   // 12

int index11 = str.IndexOf( "cd", 13, 5 );   // -1

int index12 = str.IndexOf( "ab", 17, 3 );   // "例外発生!!"
       

MSDN

この文字列内で最後に出現する指定 Unicode 文字または文字列の 0 から始まるインデックス位置を返す。このインスタンス内で文字または文字列が見つからない場合、このメソッドは -1 を返す。

大文字小文字区別して判定する。

なお引数値に文字列の範囲を超えるような値を指定した場合は例外が発生するので注意すること。


string str = "abcd efgh/abcd:abcd";

int index1 = str.LastIndexOf( 'b' );   // 16

int index2 = str.LastIndexOf( 'B' );   // -1

int index3 = str.LastIndexOf( "cd" );   // 17

int index4 = str.LastIndexOf( "CD" );   // -1

// 指定したインデックス以前の文字列内で検索する
int index5 = str.LastIndexOf( 'b', 11 );   // 11

int index6 = str.LastIndexOf( "cd", 13 );   // 12

int index7 = str.LastIndexOf( "cd", 2 );   // -1

int index8 = str.LastIndexOf( "cd", 20 );   // "例外発生!!"

// 指定したインデックス以前の文字列内で検索する。
// count は検索対象の文字数であり、文字列の開始方向の範囲内で検索する。
int index9 = str.LastIndexOf( 'b', 11, 11 );   // 11

int index10 = str.LastIndexOf( "cd", 12, 2 );   // -1

int index11 = str.LastIndexOf( "cd", 13, 2 );   // 12

int index12 = str.LastIndexOf( "cd", 12, 11 );   // 2

int index13 = str.LastIndexOf( "ab", 1, 3 );   // "例外発生!!"
       

MSDN

大文字変換
現在の文字列のコピーを大文字に変換して返す。
string str = "ABC def/あいう.";

string str1 = str.ToUpper();   // "ABC DEF/あいう."
       

MSDN

小文字変換
現在の文字列のコピーを小文字に変換して返す。
string str = "ABC def/あいう.";

string str1 = str.ToLower();   // "abc def/あいう."
       

MSDN

全角文字変換
半角文字を全角文字に変換した文字列を返す。

なおC#には半角全角文字を相互変換する機能はないため、VB.NETのStrConvメソッドを使う方法を示す。
参照設定で Microsoft.VisualBasic を追加することで使用できるようになる

StrConv以外のライブラリを使用する方法もあるらしいが詳細はDOBON.NET参照。


string str = "abc def/ghe.アイウエオ";

Debug.WriteLine(Microsoft.VisualBasic.Strings.StrConv( str, Microsoft.VisualBasic.VbStrConv.Wide ));

// "abc def/ghe.アイウエオ"
       

MSDN

半角文字変換
全角文字を半角文字に変換した文字列を返す。

なおC#には半角全角文字を相互変換する機能はないため、VB.NETのStrConvメソッドを使う方法を示す。
参照設定で Microsoft.VisualBasic を追加することで使用できるようになる

StrConv以外のライブラリを使用する方法もあるらしいが詳細はDOBON.NET参照。


string str = "abc def/ghe.アイウエオ";

Debug.WriteLine(Microsoft.VisualBasic.Strings.StrConv( str, Microsoft.VisualBasic.VbStrConv.Narrow ));

// "abc def/ghe.アイウエオ"
       

MSDN

先頭および末尾の文字の削除
現在の文字列の先頭および末尾から、指定した文字セットをすべて削除した新しい文字列を返す。
string str = "  abc def/ghe.アイウエオ  ";

string str1 = str.Trim();   // "abc def/ghe.アイウエオ"

// スペース以外の文字も除去できる
string str2 = str.Trim(new char[3] { 'a', 'b', ' ' });   // "c def/ghe.アイウエオ"

// 先頭の文字だけ除去する
string str3 = str.TrimStart();   // "abc def/ghe.アイウエオ  "

// 末尾の文字だけ除去する
string str4 = str.TrimEnd();   // "  abc def/ghe.アイウエオ"
       

MSDN

文字列の大小判定
現在の文字列と指定した文字列とを比較し、並べ替え順序において、現在の文字列の位置が指定した文字列の前、後ろ、または同じのいずれであるかを示す整数を返す。

比較オプションと、カルチャ固有の情報を指定できる。詳細はMSDN参照。


string str1 = "abc";
string str2 = "def";
string str3 = "ABC";

// 大文字小文字区別する
if( String.Compare(str1, str2) > 0 )
{
    Debug.WriteLine( "str1 > str2" );
}
else if( String.Compare(str1, str2) < 0 )
{
    Debug.WriteLine( "str1 < str2" );
}
else
{
    Debug.WriteLine( "str1 = str2" );
}

if( String.Compare(str1, str3) > 0 )
{
    Debug.WriteLine( "str1 > str3" );
}
else if( String.Compare(str1, str3) < 0 )
{
    Debug.WriteLine( "str1 < str3" );
}
else
{
    Debug.WriteLine( "str1 = str3" );
}

// ignoreCase = trueの場合大文字小文字区別せずに判定する
if( String.Compare( str1, str2, true ) > 0 )
{
    Debug.WriteLine( "str1 > str2" );
}
else if( String.Compare( str1, str2, true ) < 0 )
{
    Debug.WriteLine( "str1 < str2" );
}
else
{
    Debug.WriteLine( "str1 = str2" );
}

if( String.Compare( str1, str3, true ) > 0 )
{
    Debug.WriteLine( "str1 > str3" );
}
else if( String.Compare( str1, str3, true ) < 0 )
{
    Debug.WriteLine( "str1 < str3" );
}
else
{
    Debug.WriteLine( "str1 = str3" );
}

// str1 < str2
// str1 < str3
// str1 < str2
// str1 = str3

       

MSDN

パディング
現在の文字列の先頭に空白または指定された Unicode 文字が埋め込まれた指定された長さの新しい文字列を返す。
string str = "abc";

string str1 = str.PadLeft( 5 );   // "  abc"

string str2 = str.PadLeft( 1 );   // "abc"

string str3 = str.PadLeft( 5, '0' );   // "00abc"
       

MSDN

現在の文字列の末尾に空白または指定された Unicode 文字が埋め込まれた指定された長さの新しい文字列を返す。
string str = "abc";

string str1 = str.PadRight( 5 );   // "abc  "

string str2 = str.PadRight( 1 );   // "abc"

string str3 = str.PadRight( 5, '0' );   // "abc00"
       

MSDN

文字列型へ変換
数値型を表す文字列を返す。
int m1 = 123;
string str1 = m1.ToString();   // "123"

float m2 = 456.789f;
string str2 = m2.ToString();   // "456.789"
       

MSDN

書式変換
指定された形式に基づいてオブジェクトの値を文字列に変換し、別の文字列に挿入する。

第1引数は形式を指定し、第2引数以降に挿入するオブジェクトを指定する。
{}内の整数値は0開始となるオブジェクトのインデックスを示す。


Debug.WriteLine(String.Format("文字列1:{0}, 整数:{1}, 文字列2:{0}, 小数:{2}", "文字", 123, 456.15f));   // 文字列1:文字, 整数:123, 文字列2:文字, 小数:456.15

// C# 6 から以下のように直感的に記述できるようになった。以降のサンプルではこの書式を使用する。
Debug.WriteLine($"文字列1:{"文字"}, 整数:{123}, 文字列2:{"文字"}, 小数:{456.15f}");   // 文字列1:文字, 整数:123, 文字列2:文字, 小数:456.15

// 日付
Debug.WriteLine($"{DateTime.Now:yyyy/MM/dd HH:mm:ss}");   // 2019/09/04 20:55:13
Debug.WriteLine(DateTime.Now.ToString("yyyy/MM/dd HH:mm:ss"));   // 2019/09/04 20:55:13

// 右揃えで文字列の幅を定義する
Debug.WriteLine($"{"文字",10}");   //         文字

// 左揃えで文字列の幅を定義する
Debug.WriteLine($"{"文字",-10}");   // "文字        "

// 数値型を10進数の文字列に変換
Debug.WriteLine($"{0xA9:D}");   // 169

// 数値型を16進数の文字列に変換
Debug.WriteLine($"{123:X}");   // 7B

// 数値型のZeroパディング
Debug.WriteLine($"{123:D8}");   // 00000123

// 浮動小数の変換。小数点以下のZeroパディング
Debug.WriteLine($"{456.15f:F4}");   // 456.1500

// 浮動小数の変換。切り捨てた分は C# 標準の銀行丸めではなく四捨五入で丸め処理を行う。
Debug.WriteLine($"{456.15f:F1}");   // 456.2
Debug.WriteLine($"{456.25f:F1}");   // 456.3

// 整数に 3 桁ずつ , を挿入
Debug.WriteLine($"{123456789:#,0}");   // 123,456,789

// 整数に 3 桁ずつ , を挿入し \ マークを頭につける。
Debug.WriteLine($"{123456789:C}");   // \123,456,789

// 100倍した数値の最後に % をつける。
Debug.WriteLine($"{0.125:P}");   // 12.50%


// フォーマッタを自作する using System; using System.Diagnostics; namespace ConsoleApp1 { public class InsertHyphenFormatter : IFormatProvider, ICustomFormatter { public string Format(string format, object arg, IFormatProvider formatProvider) { if (!this.Equals(formatProvider)) { return null; } else { // フォーマットの種類 format = format.ToUpper(); // 変換前の値 string customerString = arg.ToString(); switch (format) { // 郵便番号 case "Z": if (customerString.Length != 7) { throw new ArgumentOutOfRangeException(); } customerString = customerString.Substring(0, 3) + "-" + customerString.Substring(3); break; // 携帯電話 case "P": if (customerString.Length != 11) { throw new ArgumentOutOfRangeException(); } customerString = customerString.Substring(0, 3) + "-" + customerString.Substring(3, 4) + "-" + customerString.Substring(7); break; default: throw new FormatException(String.Format("The '{0}' format specifier is not supported.", format)); } return customerString; } } public object GetFormat(Type formatType) { if (formatType == typeof(ICustomFormatter)) { return this; } else { return null; } } } class Program { static void Main(string[] args) { // 郵便番号 Debug.WriteLine(String.Format(new InsertHyphenFormatter(), "{0:Z}", "0001111")); // 000-1111 // 携帯電話 Debug.WriteLine(String.Format(new InsertHyphenFormatter(), "{0:P}", "09000001111")); // 090-0000-1111 } } }

MSDN

文字コード取得
文字コードを指定して、10進数での文字コードを取得する。
byte[] code = Encoding.GetEncoding( "Shift_JIS" ).GetBytes( "あ" );  // 130、160
       

MSDN


■null許容型

null許容型
C#にはnull許容型がある。int型のように非オブジェクト型の場合、値が格納されているか、されていないかの判定が必要となる場合、
null許容型にして判定できる。

なお文字列型である string はもともとオブジェクト型であるため?をつけなくてもnullを代入できる。


int? a = null;
Debug.WriteLine( a == null );   // True

a = 0;
Debug.WriteLine( a == null );   // False

MSDN

??演算子( Null 合体演算子 )
左側のオペランドが null 値でない場合には左側のオペランドを返し、null 値である場合には右側のオペランドを返す。

int? a = null;
int? b = 0;

Debug.WriteLine( a ?? b );   // 0
Debug.WriteLine( b ?? a );   // 0

int? c = null;
int? d = null;

Debug.WriteLine( c ?? d );   // null

int? e = 0;
int? f = 1;

Debug.WriteLine( e ?? f );   // 0
Debug.WriteLine( f ?? e );   // 1

int g = 0;
int? h = 1;

// 左オペランドがnull非許容型の場合コンパイルエラー
// Debug.WriteLine(g ?? h);

msdn

null 条件演算子
左辺のオブジェクトが null 以外の時は右辺のメンバの結果を返し、null のときは null を返す。
C# 6 以上で使用可能。
using System.Diagnostics;

namespace ConsoleApp1 {
    public class Foo {
        public int GetData()
        {
           return 4;
        }
    }
    class Program {
        static void Main(string[] args) {
            var foo = new Foo();

            int? data = foo?.GetData();
            Debug.WriteLine(data);   // 4

            foo = null;
            data = foo?.GetData();
            Debug.WriteLine(data);   // null

        }
    }
}


■型推論

型推論
右辺オペランドで初期値の代入が行われている場合、左辺オペランドによる型指定を var で省略できること。
長ったらしいコーディングする必要がなくなるので便利


var a = 0;       // a変数は数値型

var b = "abc";   // b変数は文字列型

var c;           // 初期値を代入していないのでコンパイルエラー

msdn


■匿名型

匿名型
クラスのインスタンス作成時に似たコードだが、匿名型の場合クラス名を指定しない。
読み取り専用となり、値の変更は不可。
また XML ドキュメント コメント も使えないので、使いどころなし。
var a = new { id = 0, name = "Java", comment = "使わない" };

Debug.WriteLine( a );      // { id = 0, name = Java, comment = 使わない }

Debug.WriteLine( a.id );      // 0

// コンパイルエラー
// a.id = 1;



■列挙型

列挙型
ここではビットフラグとしての列挙型について説明する。ビットフラグとは1ビットごとにオンオフを設定し、
複数のフラグを小さいメモリ上で表現できるようにしたもの。論理演算またはHasFlagメソッドを使用して各ビットにアクセスする。
using System;
using System.Diagnostics;

namespace ConsoleApplication17
{
   class Program
   {
      [Flags]
      public enum DataEnum
      {
         Data1 = 0x01,
         Data2 = 0x02,
         Data3 = 0x04,
      }
      static void Main(string[] args)
      {
         DataEnum data = 0;

         // フラグをオン
         data |= DataEnum.Data1;

         // フラグの値を取得
         Debug.WriteLine(data.HasFlag(DataEnum.Data1));   // True
         Debug.WriteLine(data.HasFlag(DataEnum.Data2));   // False
         Debug.WriteLine(data.HasFlag(DataEnum.Data3));   // False

         // フラグをオフ
         data = ( data | DataEnum.Data1 ) ^ DataEnum.Data1;

         // フラグの値を取得
         Debug.WriteLine(data.HasFlag(DataEnum.Data1));   // False
         Debug.WriteLine(data.HasFlag(DataEnum.Data2));   // False
         Debug.WriteLine(data.HasFlag(DataEnum.Data3));   // False
      }
   }
}

msdn


■共用体

共用体
C++ の union は存在しない。が以下のようにすれば同様の機能を実装できる。
しかし、プロパティの恩恵を受けることができなくなるため推奨しない。
using System;
using System.Diagnostics;
using System.Runtime.InteropServices;

namespace ConsoleApplication18
{
   public class Matrix
   {
      public float[] m = new float[9];

      // ただのプロパティ
      public float _m11 { set{ m[0] = value; } get{ return m[0]; } }
      public float _m12 { set{ m[1] = value; } get{ return m[1]; } }
      public float _m13 { set{ m[2] = value; } get{ return m[2]; } }
      public float _m21 { set{ m[3] = value; } get{ return m[3]; } }
      public float _m22 { set{ m[4] = value; } get{ return m[4]; } }
      public float _m23 { set{ m[5] = value; } get{ return m[5]; } }
      public float _m31 { set{ m[6] = value; } get{ return m[6]; } }
      public float _m32 { set{ m[7] = value; } get{ return m[7]; } }
      public float _m33 { set{ m[8] = value; } get{ return m[8]; } }
   }

   class Program
   {
      static void Main(string[] args)
      {
         var matrix = new Matrix();

         matrix.m[0] = 1;
         Debug.WriteLine(matrix._m11);

         matrix._m12 = 2;
         Debug.WriteLine(matrix.m[1]);
      }
   }
}

// 1
// 2


■operator

operator
operator キーワードを使用して、組み込みの演算子をオーバーロードする。
using System;

namespace ConsoleApplication16
{
   class Matrix
   {
      public float[] m = new float[9];

      public Matrix()
      {
         for (int i = 0; i < this.m.Length; i++)
         {
            this.m[i] = 0;
         }

         // 3 x 3 の単位行列
         this.m[0] = this.m[4] = this.m[8] = 1;
      }

      // 3 x 3 の行列の内積を計算する
      public static Matrix operator * (Matrix a, Matrix b)
      {
         Matrix _m = new Matrix();

         _m.m[0] = a.m[0] * b.m[0] + a.m[1] * b.m[3] + a.m[2] * b.m[6];
         _m.m[1] = a.m[0] * b.m[1] + a.m[1] * b.m[4] + a.m[2] * b.m[7];
         _m.m[2] = a.m[0] * b.m[2] + a.m[1] * b.m[5] + a.m[2] * b.m[8];
         _m.m[3] = a.m[3] * b.m[0] + a.m[4] * b.m[3] + a.m[5] * b.m[6];
         _m.m[4] = a.m[3] * b.m[1] + a.m[4] * b.m[4] + a.m[5] * b.m[7];
         _m.m[5] = a.m[3] * b.m[2] + a.m[4] * b.m[5] + a.m[5] * b.m[8];
         _m.m[6] = a.m[6] * b.m[0] + a.m[7] * b.m[3] + a.m[8] * b.m[6];
         _m.m[7] = a.m[6] * b.m[1] + a.m[7] * b.m[4] + a.m[8] * b.m[7];
         _m.m[8] = a.m[6] * b.m[2] + a.m[7] * b.m[5] + a.m[8] * b.m[8];

         return _m;
      }
   }

   class Program
   {
      static void Main(string[] args)
      {
         Matrix m1 = new Matrix(), m2 = new Matrix();

         m1.m[0] = 2;
         m1.m[1] = 3;
         m1.m[2] = 4;

         m2.m[0] = 1;
         m2.m[3] = 2;
         m2.m[6] = 3;

         Matrix m3 = m1 * m2;
         Debug.WriteLine(String.Join(" ", m3.m));   // 20 3 4 2 1 0 3 0 1
      }
   }
}

msdn


■型テストとキャスト演算子

is 演算子
オブジェクトと、指定した型との間に互換性があるかどうかをチェックし、キャストを行う。基底クラスへのキャストも可能。
キャスト演算子 ()と異なり、型テストに失敗した場合例外を発生させない。
また as 演算子と異なり、型テストのための null チェックが不要。
using System;
using System.Diagnostics;
using System.Windows.Forms;

namespace WindowsFormsApp1 {
   public partial class Form1 : Form {
      public Form1() {
         InitializeComponent();
      }

      private void Form1_Load(object sender, EventArgs e) {
         var foo1 = new Foo1();
         var foo2 = new Foo2();

         if (foo1 is Foo1 f1_1)
         {
            Debug.WriteLine("1:" + f1_1.name);   // 1:foo1
         }
         // Foo1 クラスの基底クラスである Foo2 クラスへキャストする
         if (foo1 is Foo2 f2_1)
         {
            Debug.WriteLine("2:" + f2_1.name);   // 2:foo2
         }

         if (foo2 is Foo1 f1_2)
         {
            // Foo2クラスはFoo1クラスと互換性がない
            Debug.WriteLine("3:" + f1_2.name);   // 出力されない
         }
         if (foo2 is Foo2 f2_2)
         {
            Debug.WriteLine("4:" + f2_2.name);   // 4:foo2
         }
         
         switch (foo2)
         {
         case Foo1 foo1_3:
            Debug.WriteLine("5:" + foo1_3.name);   // 出力されない
            break;

         case Foo2 foo2_3:
            Debug.WriteLine("5:" + foo2_3.name);   // 5:foo2
            break;
         }
      }
   }

   class Foo1 : Foo2 {
      public new string name = "foo1";
   }

   class Foo2 {
      public string name = "foo2";
   }
}


msdn

typeof 演算子
オブジェクトと、指定した型と完全一致するかどうかをチェックする。
is 演算子と異なり、基底クラスと比較判定した場合は、falseを返すことである。
using System;
using System.Diagnostics;
using System.Windows.Forms;

namespace WindowsFormsApp7 {
   public partial class Form1 : Form {
      public Form1() {
         InitializeComponent();
      }

      private void Form1_Load(object sender, EventArgs e) {
         var foo1 = new Foo1();

         if (foo1.GetType() == typeof(Foo1))
         {
            Debug.WriteLine("Foo1と完全一致");   // Foo1と完全一致
         }

         // 基底クラスと比較判定した場合はfalseを返す
         if (foo1.GetType() == typeof(Foo2))
         {
            Debug.WriteLine("Foo2と完全一致");   // 出力されない
         }
      }
   }

   class Foo1 : Foo2 {
      public new string name = "foo1";
   }

   class Foo2 {
      public string name = "foo2";
   }
}


msdn

as 演算子
オブジェクトと、指定した型との間に互換性があるかどうかをチェックし、キャストを行う。

using System;
using System.Diagnostics;
using System.Windows.Forms;

namespace WindowsFormsApp7 {
   public partial class Form1 : Form {
      public Form1() {
         InitializeComponent();
      }

      private void Form1_Load(object sender, EventArgs e) {
         var foo2 = new Foo2();

         var f1_1 = foo2 as Foo1;
         if (f1_1 != null)
         {
            Debug.WriteLine("1:" + f1_1.name);   // 出力されない
         }

         var f2_1 = foo2 as Foo2;
         if (f2_1 != null)
         {
            Debug.WriteLine("2:" + f2_1.name);   // 2:foo2
         }
      }
   }

   class Foo1 : Foo2 {
      public new string name = "foo1";
   }

   class Foo2 {
      public string name = "foo2";
   }
}


msdn


■ユーザー定義の変換演算子

ユーザー定義の変換演算子
implicit キーワードを使用して、暗黙のユーザー定義型変換演算子を宣言する。
変換を実行してもデータ損失が発生しないことが保証されている場合に使用できる。

explicit キーワードを使用すると、明示的にキャストを行う必要がある。



using System.Diagnostics;

namespace ConsoleApp1 {
   class Program {
      static void Main(string[] args) {
         var m = new Meter(100);

         // メートルクラスをキロメートルクラスに暗黙の型変換
         Kilometer k = m;

         Debug.WriteLine(k.Distance);

         // メートルクラスの値をint型にキャスト
         int m2 = (int)m;

         Debug.WriteLine(m2);
      }
   }

   class Meter {
      public double Distance {
         get;
      }

      public Meter(double meter) {
         this.Distance = meter;
      }

      // メートルクラスをキロメートルクラスに暗黙の型変換
      // どちらもdouble型のためデータの喪失は発生しない。そのため明示的なキャストは省略できる。
      public static implicit operator Kilometer(Meter t) {
         return new Kilometer(t.Distance * 0.001);
      }

      // メートルクラスの値をint型にキャスト
      // double型をint型にキャストするためデータの喪失が発生する可能性がある。したがって明示的にキャストを指定するようにする。
      public static explicit operator int(Meter t) {
         return (int)(t.Distance);
      }
   }

   class Kilometer {
      public double Distance {
         get;
      }

      public Kilometer(double kilometer) {
         this.Distance = kilometer;
      }
   }
}


■配列

配列
LINQ使えないので、Listコレクション推奨。
using System;
using System.Collections;
using System.Diagnostics;
using System.Windows.Forms;

namespace ConsoleApplication27
{
   class Program
   {
      static void Main(string[] args)
      {
         var array = new int[] { 1, 2, 3, 4, 5, 6, 7, 8, 9 };

         Debug.WriteLine(String.Join(" ", array));   // 1 2 3 4 5 6 7 8 9
         
         var arrList = new ArrayList() { 1, 2, 3, 4, 5, 6, 7, 8, 9 };
         foreach (var item in arrList)
         {
            Debug.Write(item + " ");   // 1 2 3 4 5 6 7 8 9
         }
         Debug.WriteLine("");
      }
   }
}

ジャグ配列
ジャグ配列とは配列の要素も配列になっているもの。
using System;
using System.Diagnostics;

namespace ConsoleApp1 {
   class Program {
      static void Main(string[] args) {
         var jaggedArray = new int[3][];
         
         // 各要素を配列で初期化する
         jaggedArray[0] = new int[] { 1, 3, 5, 7, 9 };   // 任意の初期値で初期化する
         jaggedArray[1] = new int[4];
         jaggedArray[2] = new int[2];

         Debug.WriteLine(jaggedArray[0][1]);   // 3

         int[][] jaggedArray3 = {
            new int[] {1,3,5,7,9},
            new int[] {0,2,4,6},
            new int[] {11,22}
         };

         Debug.WriteLine(jaggedArray3[0][1]);   // 3
      }
   }
}


■Collections

List
インデックスを使用してアクセスできる、厳密に型付けされたされたオブジェクトリスト。ジェネリックにより型指定できる。
foreachによる順番は保証される。
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;

namespace ConsoleApplication15
{
   class Program
   {
      static void Main(string[] args)
      {
         // インスタンス作成と同時に初期値代入
         var list = new List<string>() {
                                 { "abc" },
                                 { "def" },
                                 { "012" }
                              };

         // 要素の件数取得
         Debug.WriteLine("件数:" + list.Count);   // 件数:3

         // 列挙
         foreach(var item in list)
         {
            Debug.Write(item + " ");   // abc def 012 
         }
         Debug.WriteLine("");

         for(var i=0; i<list.Count; i++)
         {
            Debug.Write(list[i] + " ");   // abc def 012 
         }
         Debug.WriteLine("");

         // 値の検索
         Debug.WriteLine(list.IndexOf("def"));   // 1
         Debug.WriteLine(list.IndexOf("xyz"));   // -1
         Debug.WriteLine(list.FindIndex(a => a.Contains("e")));   // 1
         Debug.WriteLine(list.Any(a => a == "def"));   // True
         Debug.WriteLine(list.Any(a => a == "DEF"));   // False

         // 項目追加
         list.Add("hij");
         Debug.WriteLine(String.Join(" ", list));   // abc def 012 hij

         // 同じ値を追加
         list.Add("abc");
         Debug.WriteLine(String.Join(" ", list));   // abc def 012 hij abc

         // 値の修正
         list[2] = "xyz";
         Debug.WriteLine(String.Join(" ", list));   // abc def xyz hij abc

         // 項目の削除
         list.Remove("xyz");
         Debug.WriteLine(String.Join(" ", list));   // abc def hij abc
      }
   }
}

Dictionary
連想配列( ハッシュテーブル )のことで、キーと値のコレクション。順番は保証されない。
キーだけを変更することは通常できず、別にDictionaryインスタンスを作成する必要がある。
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;

namespace ConsoleApplication15
{
   class Program
   {
      static void Main(string[] args)
      {
         // インスタンス作成と同時に初期値代入
         var dic = new Dictionary<string, string>() {
                                    { "sato", "itiro" },
                                    { "watanabe", "jiro" },
                                    { "tanaka", "saburo" }
                                 };

         // 要素の件数取得
         Debug.WriteLine("件数:" + dic.Count);   // 件数:3

         // 列挙
         foreach (var key in dic.Keys)
         {
            Debug.Write(String.Format("[{0}, {1}] ", key, dic[key]));   // [sato, itiro] [watanabe, jiro] [tanaka, saburo] 
         }
         Debug.WriteLine("");

         foreach(KeyValuePair<string, string> kvp in dic )
         {
            Debug.Write(String.Format("[{0}, {1}] ", kvp.Key, kvp.Value));   // [sato, itiro] [watanabe, jiro] [tanaka, saburo] 
         }
         Debug.WriteLine("");
         
         // キーが存在する場合はtrueを返し、valueに値がセットされる。
         string value;
         if (dic.TryGetValue("tanaka", out value))
         {
            Debug.WriteLine(String.Format("[{0}, {1}]", "tanaka", value));   // [tanaka, saburo]
         }

         // キーが存在する場合はtrueを返し、valueに値がセットされる。
         if (dic.TryGetValue("suzuki", out value))
         {
            Debug.WriteLine(String.Format("[{0}, {1}]", "suzuki", value));   // 出力されない
         }

         // キーが存在しない場合はキー、値を追加
         if (!dic.ContainsKey("suzuki"))
         {
            dic.Add("suzuki", "siro");
            Debug.WriteLine(String.Join(" ", dic));   // [sato, itiro] [watanabe, jiro] [tanaka, saburo] [suzuki, siro]
         }

         // キーが存在しない場合はキー、値を追加
         // なお既に存在するキーを追加しようとすると例外発生
         if (!dic.ContainsKey("suzuki"))
         {
            dic.Add("suzuki", "siro");
            Debug.WriteLine(String.Join(" ", dic));   // 出力なし
         }

         // 値の修正
         dic["suzuki"] = "goro";
         Debug.WriteLine(String.Join(" ", dic));   // [sato, itiro] [watanabe, jiro] [tanaka, saburo] [suzuki, goro]

         // キー、値の削除
         dic.Remove("suzuki");
         Debug.WriteLine(String.Join(" ", dic));   // [sato, itiro] [watanabe, jiro] [tanaka, saburo]
      }
   }
}

SortedList
Dictionaryと同様にキー、値によるコレクションで、違いはキーによる順番が保証されていることである。

またSortedDictionaryと同じ機能を持ち、メモリ使用量やパフォーマンスによる違いがある。
1.SortedListはSortedDictionaryよりも少ないメモリを使用する。
2.SortedDictionaryでは並べ替えられていないデータへの挿入と削除が高速。
3.ソートされたデータからリストが一度にすべて作成される場合SortedListのほうが高速。

つまり、頻繁にデータの挿入と削除が行われる場合は、SortedDictionaryを使用すべき、そうでない場合はSortedListを使用すべきってなことでしょうかね。
もちろんキーによるソートが不要な場合は、Dictionaryを使用すべきでしょう。


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

namespace ConsoleApplication15
{
   class Program
   {
      static void Main(string[] args)
      {
         // インスタンス作成と同時に初期値代入
         var dic = new SortedList<string, string>() {
                                    { "sato", "itiro" },
                                    { "watanabe", "jiro" },
                                    { "tanaka", "saburo" }
                                 };

         // 要素の件数取得
         Debug.WriteLine("件数:" + dic.Count);   // 件数:3

         // 列挙
         foreach (var key in dic.Keys)
         {
            Debug.Write(String.Format("[{0}, {1}] ", key, dic[key]));   // [sato, itiro] [tanaka, saburo] [watanabe, jiro] 
         }
         Debug.WriteLine("");

         foreach(KeyValuePair<string, string> kvp in dic )
         {
            Debug.Write(String.Format("[{0}, {1}] ", kvp.Key, kvp.Value));   // [sato, itiro] [tanaka, saburo] [watanabe, jiro] 
         }
         Debug.WriteLine("");
         
         // キーが存在しない場合はキー、値を追加
         if (!dic.ContainsKey("suzuki"))
         {
            dic.Add("suzuki", "siro");
            Debug.WriteLine(String.Join(" ", dic));   // [sato, itiro] [suzuki, siro] [tanaka, saburo] [watanabe, jiro]
         }

         // キー、値の削除
         dic.Remove("sato");
         Debug.WriteLine(String.Join(" ", dic));   // [suzuki, siro] [tanaka, saburo] [watanabe, jiro]
      }
   }
}

msdn

SortedDictionary
Dictionaryと同様にキー、値によるコレクションで、違いはキーによる順番が保証されていることである。

またSortedListと同じ機能を持ち、メモリ使用量やパフォーマンスによる違いがある。
1.SortedListはSortedDictionaryよりも少ないメモリを使用する。
2.SortedDictionaryでは並べ替えられていないデータへの挿入と削除が高速。
3.ソートされたデータからリストが一度にすべて作成される場合SortedListのほうが高速。

つまり、頻繁にデータの挿入と削除が行われる場合は、SortedDictionaryを使用すべき、そうでない場合はSortedListを使用すべきってなことでしょうかね。
もちろんキーによるソートが不要な場合は、Dictionaryを使用すべきでしょう。


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

namespace ConsoleApplication15
{
   class Program
   {
      static void Main(string[] args)
      {
         // インスタンス作成と同時に初期値代入
         var dic = new SortedDictionary<string, string>() {
                                    { "sato", "itiro" },
                                    { "watanabe", "jiro" },
                                    { "tanaka", "saburo" }
                                 };

         // 要素の件数取得
         Debug.WriteLine("件数:" + dic.Count);   // 件数:3

         // 列挙
         foreach (var key in dic.Keys)
         {
            Debug.Write(String.Format("[{0}, {1}] ", key, dic[key]));   // [sato, itiro] [tanaka, saburo] [watanabe, jiro] 
         }
         Debug.WriteLine("");

         foreach(KeyValuePair<string, string> kvp in dic )
         {
            Debug.Write(String.Format("[{0}, {1}] ", kvp.Key, kvp.Value));   // [sato, itiro] [tanaka, saburo] [watanabe, jiro] 
         }
         Debug.WriteLine("");
         
         // キーが存在しない場合はキー、値を追加
         if (!dic.ContainsKey("suzuki"))
         {
            dic.Add("suzuki", "siro");
            Debug.WriteLine(String.Join(" ", dic));   // [sato, itiro] [suzuki, siro] [tanaka, saburo] [watanabe, jiro]
         }

         // キー、値の削除
         dic.Remove("sato");
         Debug.WriteLine(String.Join(" ", dic));   // [suzuki, siro] [tanaka, saburo] [watanabe, jiro]
      }
   }
}

msdn

HashSet
キーだけ保持するDictionaryのようなもの。いくつかの制限があるが、高パフォーマンスであるため可能であればListではなくHashSetを使うべき。
1.Listと異なり重複する値はセットされない。
2.高パフォーマンス
3.順番が保証されない。
4.値の変更ができない。
5.データアクセスにはインデックスを使用できないためforeachを使用する。
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;

namespace ConsoleApplication15
{
   class Program
   {
      static void Main(string[] args)
      {
         // インスタンス作成と同時に初期値代入
         var hash = new HashSet<string>() {
                                 { "abc" },
                                 { "def" },
                                 { "012" }
                              };

         // 要素の件数取得
         Debug.WriteLine("件数:" + hash.Count);   // 件数:3

         // 列挙
         foreach (var item in hash)
         {
            Debug.Write(item + " ");   // abc def 012 
         }
         Debug.WriteLine("");

         // 値の検索
         Debug.WriteLine(hash.Contains("def"));   // True
         Debug.WriteLine(hash.Contains("xyz"));   // False
         Debug.WriteLine(hash.Any(a => a.Contains("e")));   // True
         Debug.WriteLine(hash.Any(a => a == "def"));   // True
         Debug.WriteLine(hash.Any(a => a == "DEF"));   // False

         // 項目追加
         hash.Add("hij");
         Debug.WriteLine(String.Join(" ", hash));   // abc def 012 hij

         // 同じ値は追加されない
         hash.Add("abc");
         Debug.WriteLine(String.Join(" ", hash));   // abc def 012 hij

         // 項目の削除
         hash.Remove("abc");
         Debug.WriteLine(String.Join(" ", hash));   // def 012 hij

      }
   }
}

msdn

ReadOnlyCollection
読み取り専用のジェネリック コレクション
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Windows.Forms;

namespace ConsoleApplication15
{
   class Program
   {
      static void Main(string[] args)
      {
         // インスタンス作成と同時に初期値代入
         var list = new List<string>() {
                                 { "abc" },
                                 { "def" },
                                 { "012" }
                              };

         ReadOnlyCollection<string> readOnlyList = list.AsReadOnly();
         // 値を変更しようとするとコンパイルエラーになる
         // readOnlyList[0] = "xyz";
      }
   }
}


■クラス

カプセル化
オブジェクト指向の要の一つ。クラス内のフィールドを隠ぺいし、クラスの設計者が意図していないフィールドの値の変更を防ぐ。

// Shape.cs

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

public class Shape
{
    // このフィールドには直接アクセスできない
    private int _x;
    
    // get, set プロパティを使用してアクセサ( メソッド )にアクセスする
    public int x
    {
        get
        {
            return _x;
        }

        set
        {
            // 代入値が 0以上 の場合のみ格納する
            // value が代入値となる
            _x = value >= 0 ? value : 0;
        }
    }
}


// Form1.cs using System; using System.Collections.Generic; using System.ComponentModel; using System.Data; using System.Drawing; using System.Linq; using System.Text; using System.Threading.Tasks; using System.Windows.Forms; using System.Diagnostics; namespace WindowsFormsApplication4 { public partial class Form1 : Form { public Form1() { InitializeComponent(); } private void Form1_Load(object sender, EventArgs e) { Shape shape = new Shape(); shape.x = 1; Debug.WriteLine(shape.x); shape.x = -1; Debug.WriteLine(shape.x); } } } // 1 // 0

C# 6 からget、setアクセサのみで実装できるいわゆる自動プロパティに初期値を代入できるようになった。
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace ConsoleApp1 {
    class Foo {
        private int Data { get; set; } = 1;

        public int Add(int math) => Data + math;
    }

    class Program {
        static void Main(string[] args) {
            var foo = new Foo();

            int add = foo.Add(3);
            Debug.WriteLine(add);   // 4

        }
    }
}

ポリモーフィズム( 多態性 )
オブジェクト指向の要の一つ。基底クラスを継承する派生クラスを作成する場合、そのインスタンスは基底クラスと派生クラスの両方の振る舞いをすることを示す。
クラスを継承する場合基底クラスのメソッドを派生クラスのメソッドでオーバーライドつまり置き換えることができる。
オーバーライドを行えるのは、基底クラス側で virtual をつけたメソッドのみで( 例外はあるが )、派生クラス側で override をつけることで置き換えられる。
基本的には基底クラス側でデフォルト機能のメンバを持たせ、機能を変更する必要がある場合は派生クラス側で override するようにすると思う。

// Shape.cs

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Diagnostics;

public class Shape
{
    // 基底クラス内のDraw()メンバ
    // 必ず実装する必要がある
    public virtual void Draw()
    {
        Debug.WriteLine("Shape");
    }

    public void Move()
    {
        Debug.WriteLine("Shape Move");
    }
}


// Line.cs using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; using System.Diagnostics; // Shapeオブジェクトを継承 public class Line : Shape { // 派生クラス内のDraw()メンバ // 基底クラスのDraw()メンバは派生クラスのDraw()メンバで上書きされる。 // なお派生クラス側でoverrrideしない場合、基底クラスのメンバが使用される public override void Draw() { Debug.WriteLine("Line"); } // virtual でないメンバを override するとコンパイルエラーになる。 public override void Move() { Debug.WriteLine("LineMove"); } }
// Polygon.cs using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; using System.Diagnostics; // Shapeオブジェクトを継承 public class Polygon : Shape { public override void Draw() { Debug.WriteLine("Polygon"); } }
// Form1.cs using System; using System.Collections.Generic; using System.ComponentModel; using System.Data; using System.Drawing; using System.Linq; using System.Text; using System.Threading.Tasks; using System.Windows.Forms; namespace WindowsFormsApplication3 { public partial class Form1 : Form { public Form1() { InitializeComponent(); } private void Form1_Load(object sender, EventArgs e) { Line line = new Line(); line.Draw(); Polygon polygon = new Polygon(); polygon.Draw(); Shape shape = new Shape(); shape.Draw(); // 継承せずに virtual なメンバを呼び出してもOK } } } // Line // Polygon // Shape

継承
オブジェクト指向の要の一つ。派生クラス側で継承している基底クラスにメンバを追加することで機能拡張を行う。

// Shape.cs

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Diagnostics;

public class Shape
{
    protected int _x;
    protected int _y;

    public virtual void Draw()
    {
        Debug.WriteLine("Shape");
    }
}


// Line.cs using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; using System.Diagnostics; // Shapeオブジェクトを継承 public class Line : Shape { // 追加したメソッド public void SetPosition( int x, int y ) { _x = x; _y = y; } public override void Draw() { Debug.WriteLine(_x.ToString() + ":" + _y.ToString()); } }
// Form1.cs using System; using System.Collections.Generic; using System.ComponentModel; using System.Data; using System.Drawing; using System.Linq; using System.Text; using System.Threading.Tasks; using System.Windows.Forms; namespace WindowsFormsApplication5 { public partial class Form1 : Form { public Form1() { InitializeComponent(); } private void Form1_Load(object sender, EventArgs e) { Line line = new Line(); line.SetPosition(2, 3); line.Draw(); // ShapeオブジェクトにLineオブジェクトを格納することができる。 // この場合Lineオブジェクトで拡張した機能は使えないが、Lineオブジェクトで override したメンバは使用できる。 // Listクラスのような動的配列を使用することで、同一の基底クラスを持つ派生クラスを一括処理する際に使用できる。 Shape shape = line; shape.Draw(); line.SetPosition(3, 4); // shape変数の値も同様に変更される shape.Draw(); } } } // 2:3 // 2:3 // 3:4

interface
interfaceでは、クラスメンバの定義だけを記述し実装できない。interface を継承する派生クラスでは、interface で定義されたメンバを必ず実装する必要がある。
interfaceでは、フィールド、プロパティの記述もできない。
interfaceでは、インスタンスの作成はできない。
interfaceでは、多重継承できる。


// IShape.cs

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

// interfaceの命名規則は接頭語としてIをつけるのが一般的らしい
public interface IShape
{
    // メソッドの定義だけを行う。
    void Draw();
}


// IShape2.cs using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; public interface IShape2 { // メソッドの定義だけを行う。 void Draw2(); }
// Shape.cs using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; using System.Diagnostics; // interfaceを継承 public class Shape : IShape, IShape2 { protected int _x; protected int _y; // Draw()メンバを実装しない場合コンパイルエラーになる public virtual void Draw() { Debug.WriteLine("Shape"); } public virtual void Draw2() { Debug.WriteLine("Shape2"); } }
// Line.cs using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; using System.Diagnostics; public class Line : Shape { public void SetPosition( int x, int y ) { _x = x; _y = y; } public override void Draw() { Debug.WriteLine(_x.ToString() + ":" + _y.ToString()); } }
// Form1.cs using System; using System.Collections.Generic; using System.ComponentModel; using System.Data; using System.Drawing; using System.Linq; using System.Text; using System.Threading.Tasks; using System.Windows.Forms; namespace WindowsFormsApplication6 { public partial class Form1 : Form { public Form1() { InitializeComponent(); } private void Form1_Load(object sender, EventArgs e) { Line line = new Line(); line.SetPosition(2, 3); line.Draw(); line.Draw2(); } } } // 2:3 // Shape2

抽象クラス
抽象クラスでは、クラスメンバの実装ができる。
抽象クラスでは、フィールド、プロパティが記述できる。
抽象クラスでは、インスタンスの作成はできない。つまり、継承して使用することを強制する目的で使用する。
抽象クラスでは、多重継承できない。

// Shape.cs

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Diagnostics;

public abstract class Shape
{
    protected int _x;
    protected int _y;

    public virtual void Draw()
    {
        Debug.WriteLine("Shape");
    }
}


// Line.cs using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; using System.Diagnostics; // 抽象クラスを継承 // 多重継承不可 public class Line : Shape { public void SetPosition( int x, int y ) { _x = x; _y = y; } public override void Draw() { Debug.WriteLine(_x.ToString() + ":" + _y.ToString()); } }
// Form1.cs using System; using System.Collections.Generic; using System.ComponentModel; using System.Data; using System.Drawing; using System.Linq; using System.Text; using System.Threading.Tasks; using System.Windows.Forms; namespace WindowsFormsApplication7 { public partial class Form1 : Form { public Form1() { InitializeComponent(); } private void Form1_Load(object sender, EventArgs e) { Line line = new Line(); line.SetPosition(2, 3); line.Draw(); } } } // 2:3

sealed修飾子
sealed修飾子をクラスにつけると継承できなくなる。
// Shape.cs

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Diagnostics;

public sealed class Shape
{
    protected int _x;
    protected int _y;

    public virtual void Draw()
    {
        Debug.WriteLine("Shape");
    }
}


// Line.cs using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; using System.Diagnostics; // sealedクラスを継承するとコンパイルエラー public class Line : Shape { public void SetPosition( int x, int y ) { _x = x; _y = y; } public override void Draw() { Debug.WriteLine(_x.ToString() + ":" + _y.ToString()); } }

new
new を使用することで、virtual でないメンバを override できるようになる。
ただしクラス設計者は override してほしくないから virtual をつけていないはずなので、それを override するのは設計上どうかと思う。
ちなみに new をつけない場合、ワーニングエラーになる。

// Shape.cs

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Diagnostics;

public class Shape
{
    public void Draw()
    {
        Debug.WriteLine("Shape");
    }
}


// Line.cs using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; using System.Diagnostics; public class Line : Shape { public new void Draw() { Debug.WriteLine("Line"); } }
// Form1.cs using System; using System.Collections.Generic; using System.ComponentModel; using System.Data; using System.Drawing; using System.Linq; using System.Text; using System.Threading.Tasks; using System.Windows.Forms; namespace WindowsFormsApplication9 { public partial class Form1 : Form { public Form1() { InitializeComponent(); } private void Form1_Load(object sender, EventArgs e) { Line line = new Line(); line.Draw(); } } } // Line

コンストラクタ
コンストラクタを実装しなくてもインスタンス作成時にフィールドの初期化ができる。


// Foo.cs

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Diagnostics;

public class Foo
{
    public int a;
    public int b;
    
    public void Update()
    {
       Debug.WriteLine( a + ":" + b );
    }
}


// Form1.cs using System; using System.Collections.Generic; using System.ComponentModel; using System.Data; using System.Drawing; using System.Linq; using System.Text; using System.Threading.Tasks; using System.Windows.Forms; namespace WindowsFormsApplication16 { public partial class Form1 : Form { public Form1() { InitializeComponent(); } private void Form1_Load(object sender, EventArgs e) { Foo f = new Foo(){ a = 1, b = 2 }; f.Update(); } } } // 1:2

アクセス修飾子
public、private、protectedは省略。internalについて説明する。

internalの場合、アクセスは現在のアセンブリに制限される。


namespace ClassLibrary1
{
    // 同じアセンブリ内であればClass1のインスタンスを作成できるが、Class1のDLLを別のアセンブリから参照設定で追加した場合、アクセスできない。
    internal class Class1
    {
    }
}

msdn
部分型定義
クラス、構造体、またはインターフェイスの定義を複数のファイルに分割する。例えばフォームの実装でデザイナとロジックを分離するためなどに使用する。
namespace ConsoleApplication10
{
    public partial class A
    {
        public int num1 = 0;
        public void MethodA() { }
    }
}

namespace ConsoleApplication10
{
    public partial class A
    {
        public int num2 = 0;
        public void MethodB() { }
    }
}

// class A は num1、num2、MethodA、MethodBのすべてのフィールド、メソッドを持つ。

msdn
シリアライズ
クラスや、配列・コレクションオブジェクトをバイナリ、Soap、XML形式で出力することである。
なお基底クラスにSerializableAttributeを付与しても派生クラスでもSerializableAttributeを付与しないとシリアライズ可能にならない。
using System;
using System.Diagnostics;
using System.IO;
using System.Runtime.Serialization;
using System.Runtime.Serialization.Formatters.Binary;

namespace ConsoleApplication12
{
    // クラスをシリアライズ可能にする
    [Serializable]
    class Account
    {
        public int ID { get; set; }
        public string Name { get; set; }

        public override string ToString()
        {
            return string.Format("{0}:{1}", this.ID, this.Name);
        }
    }

    class Program
    {
        static void Main(string[] args)
        {
            var alice = new Account() { ID = 2, Name = "Alice " };

            Debug.WriteLine(alice);

            // 出力するファイルを開く
            using (Stream stream = File.OpenWrite("alice.bin"))
            {
                // バイナリ形式でシリアライズ
                var formatter = new BinaryFormatter();

                // オブジェクトaliceをバイナリ形式でシリアライズし、alice.binに出力
                formatter.Serialize(stream, alice);
            }

            using (Stream stream = File.OpenRead("alice.bin"))
            {
                var formatter = new BinaryFormatter();

                // alice.binを読み込んで、デシリアライズする
                var deserialized = (Account)formatter.Deserialize(stream);

                Debug.WriteLine(deserialized);
            }
        }
    }
}

// 2:Alice
// 2:Alice

smdn

■IComparable

IComparable
インスタンスの並べ替え機能を実装するためのインターフェース。
int CompareTo(T other) メソッドを実装しなければならない。
using System;
using System.Collections.Generic;
using System.Diagnostics;

namespace ConsoleApp1 {
   class Program {
      static void Main(string[] args) {
         var f = new Foo();

         // Fooクラス内のフィールド data とCompareToメソッドに渡す数値との大小比較
         Debug.WriteLine(f.CompareTo(40) > 0);    // true
         Debug.WriteLine(f.CompareTo(100) == 0);  // true
         Debug.WriteLine(f.CompareTo(120) < 0);   // true

         Random rnd = new Random();

         var f2 = new List<Foo>();
         for(var i=0; i<10; i++)
         {
            f2.Add(new Foo() { data = rnd.Next(100) });
         }

         // FooクラスのCompareToメソッドを呼び出して並べ替えを行う。
         f2.Sort();

         for (var i = 0; i < f2.Count; i++)
         {
            Debug.WriteLine(f2[i].data);
         }
      }
   }

   // Fooクラスは並べ替え機能を持つ IComparable インターフzェースを継承
   class Foo : IComparable {
      public int data { get; set; } = 100;

      // 昇順に並べ替えを行う
      public int CompareTo(object obj) {
         if (obj == null) { return 1; }

         int ret = 1;

         // Sortメソッドを呼び出した場合に処理される
         if (obj is Foo o)
         {
            ret = o.data;
         }
         // CompareToを呼び出した場合に処理される
         else
         {
            Int32.TryParse(obj.ToString(), out ret);
         }

         return data - ret;
      }
   }
}

msdn


■IEnumerableとIEnumerator

IEnumerable
IEnumerableは、foreachによる反復処理を実装するためのインターフェース。

IEnumeratorは、forやwhileによる反復処理を実装するためのインターフェース。


// IEnumerable

using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;

namespace ConsoleApp1 {
   class Program {
      static void Main(string[] args) {
         var items = new EnumerableThing();

         foreach(var item in items)
         {
            Debug.WriteLine(item);
         }
      }
   }

   // IEnumerable を継承しても動く。しかしforeachで型推論が使えなくなるので IEnumerable<out T> を継承したほうが良い。
   class EnumerableThing : IEnumerable<int> {
      // foreachで実際に呼び出されるメソッド。IEnumerable<out T>で定義されている。
      public IEnumerator<int> GetEnumerator() {
         for (int i = 1; i < 10; i++)
         {
            // yield を使用しループごとに結果を返す
            yield return i;
         }
      }

      // IEnumerable<out T>が継承しているIEnumerableで定義されている。使われることはまずない。
      IEnumerator IEnumerable.GetEnumerator() {
         return this.GetEnumerator();
      }
   }
}

       
// IEnumerator

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

namespace ConsoleApp1 {
   class Program {
      static void Main(string[] args) {

         using (var items = new EnumeratorThing())
         {
            while (items.MoveNext())
            {
               Debug.WriteLine(items.data[items.Current]);
            }
         }
         
         using (var items = new EnumeratorThing())
         {
            for (var i=0; i<items.data.Length; i++)
            {
               Debug.WriteLine(items.data[i]);
               if (i == 2) break;
            }
         }
      }
   }

   class EnumeratorThing : IEnumerator<int> {
      public int[] data { get; set; } = Enumerable.Range(0, 10).ToArray();

      private int index = -1;
      
      public int Current { get { return index; } }

      // IEnumeratorインターフェースにより定義されているメンバ。使われることはまずない。
      object IEnumerator.Current { get { return index; } }

      // ループの終了、break、例外発生時などでも確実にリソース開放できるようにDisposeを実装する
      public void Dispose() {
         Debug.WriteLine("Dispose!!");
      }

      // 次の要素があるかを判定し、ある場合は index をインクリメント
      public bool MoveNext() {
         if (this.index++ < data.Length - 1)
         {
            return true;
         }

         return false;
      }

      // index を初期値に戻す
      public void Reset() {
         this.index = -1;
      }
   }
}

Enumerable.Range
第1引数の数値から第2引数の数値までの数値の配列を作成する。第1引数 > 第2引数の場合は、第1引数の値のみ作成される。
var list = Enumerable.Range(1, 100).ToList();
Debug.WriteLine(String.Join(" ", list));   // 1 2 ...100

list = Enumerable.Range(100, 1).ToList();
Debug.WriteLine(String.Join(" ", list));   // 100

msdn

Enumerable.Repeat
第1引数の数値を第2引数の数値分の配列を作成する。
var list = Enumerable.Repeat(2, 3).ToList();
Debug.WriteLine(String.Join(" ", list));   // 2 2 2

msdn


■IDispose

IDispose
IDisposeは、リソースを解放する機能を提供する。

.NETフレームワークは、GCによってオブジェクトの作成と破棄を自動的に行うが、オブジェクトが使用するリソースは管理しない。
たとえば、アプリケーションがファイルハンドルを作成してそれをオブジェクトに格納した場合、そのオブジェクトが破棄されるとファイルハンドルは失われる。
オブジェクトが破棄される前にハンドルに接続されているファイルが正しく閉じられていないと、ハンドルが接続されているファイルは使用できなくなる。

マネージドリソース:.NET Framework で管理されるリソース。StreamReaderなど。
アンマネージドリソース:.NET Framework で管理されないリソース。Windows APIなど。



using System;
using System.Diagnostics;
using System.IO;
using System.Runtime.InteropServices;

namespace ConsoleApplication1 {
   class Program {
      static void Main(string[] args) {
         using (var r = new ResourceHolder())
         {
         }
         
         var r2 = new ResourceHolder();
         r2.Dispose();
      }
   }

   class ResourceHolder : IDisposable {

      // マネージドリソース
      private StreamReader sr;

      // アンマネージドリソース
      private IntPtr sptr;

      // Disposeを複数回呼び出さないようにするためのフラグ
      bool disposed = false;

      public ResourceHolder() {
         sr = new StreamReader(@"c:\Debug.txt");

         // マネージの文字列をアンマネージメモリにANSI形式でコピー
         sptr = Marshal.StringToHGlobalAnsi("I seem to be turned around!");
      }

      public void Dispose() {
         // マネージドリソースとアンマネージドリソースを解放する
         this.Dispose(true);

         // Disposeを呼び出したらファイナライザを呼び出さないようにする
         GC.SuppressFinalize(this);
      }

      protected virtual void Dispose(bool disposing) {
         // 既に解放済なので何もしない
         if (disposed) return;

         disposed = true;

         if (disposing)
         {
            Debug.WriteLine("マネージドリソース解放!!");
            sr.Close();
            sr = null;
         }

         Debug.WriteLine("アンマネージドリソース解放!!");
         Marshal.FreeHGlobal(sptr);
      }

      // ファイナライザ。GCによりメモリから破棄される時点で呼び出される。
      // Dispose内でGC.SuppressFinalizeを呼び出しているので、Dispose呼び出し忘れの場合のみ実行される。
      // ファイナライザはGC管理となるため、マネージドリソースの解放もGCに任せ、アンマネージドリソースのみ解放する。
      ~ResourceHolder() {
         Dispose(false);
      }
   }
}


■メソッド

引数
引数を渡すとき仮引数名を指定することができる。引数の数が多くなった場合、見やすくなる。

using System.Diagnostics;

namespace ConsoleApplication25
{
   class Program
   {
      static void foo(int a, int b)
      {
         Debug.WriteLine(a + ":" + b);   // 1:2
      }

      static void Main(string[] args)
      {
         foo(a:1, b:2);
      }
   }
}

可変長引数
可変長引数
using System.Diagnostics;
using System.Linq;

namespace ConsoleApplication28
{
   class Program
   {
      static void Main(string[] args)
      {
         Debug.WriteLine(add(1, 2, 3, 4, 5));   // 15
      }

      static int add( params int[] a )
      {
         int m = 0;

         a.ToList().ForEach(b => { m += b; });

         return m;
      }
   }
}

値型と参照型
値型:構造体、列挙体

   int型のようないわゆるプリミティブ型も、実は構造体である。
   intをクリックして「定義に移動」するとInt32構造体にジャンプする。
   これはint型はInt32という構造体のエイリアスであることを示している。

参照型:配列、class、interface、delegate、dynamic、object、string

   string型は参照型であるが、実際には値型のようにふるまう。
   string型の値を変更すると別のメモリ上に新しい値を設定し、参照先を変更する。
   このため参照型であっても値型としての性質を持つようになっている。


ref 、out ともにメソッドへ引数渡しをする際、参照型として扱うためのキーワードである。

ref:メソッドへ渡す引数の初期化が必須で、メソッド内での仮引数の変更が任意。
out:メソッドへ渡す引数の初期化が不要で、メソッド内での仮引数の変更が必須。


■ラムダ式

LINQで使用するラムダ式
LINQで使用するラムダ式の場合、以下のように記述する。

   var list = Enumerable.Range(1, 10).Where(a => a % 2 == 0).ToList();   // 2, 4, 6, 8, 10
       
Where メソッドが LINQ である。Where メソッドは与えられた条件が true となるレコードを抽出する機能を持つ。

Where メソッド内で呼び出しているラムダ式について説明する。
=> の左辺の a はコレクションの各レコードの値を受け取る変数。
=> の右辺が条件を記述する式となる。a がクラスや構造体の場合はドットをつけてクラスや構造体内のフィールドにアクセスする。
上記のサンプルの場合、各レコードの値を a という変数で受け取り、その値が偶数の時に true となる条件を与えているため、偶数レコードのみ抽出される。

LINQも併せて参照すること。

デリゲートで使用するラムダ式
ラムダ式は無名関数(C#ではデリゲートと呼ぶ)を実装するための記法の1つである。
デリゲートとは名前つけされていない関数のこと。
ラムダ式の記法は言語ごとに異なるが、C# では以下のように記述する。

   Action<string, string> func = (a, b) => Debug.WriteLine(a + b) ;
   
=> の左辺の (a, b) が関数の引数部分となる。
=> の右辺が関数の処理部分となる。
上記のサンプルの場合、引数 a と b を受け取り、それを文字列として連結した値をデバッガに出力している。
そしてこの一連の処理を func という変数に代入している。

関数の機能を実行するには以下のように func 変数に引数を与えて呼び出せばよい。

 func("1", "2");

デリゲートの使用目的は、C言語やC++の関数ポインタのように使用することで
メソッド内で実行させる機能を外部からカスタマイズできるようにすることにある。

デリゲートも併せて参照すること。


■デリゲート
Action<T> デリゲート
デリゲートとはメソッドを参照するための型のことである。C言語やC++では関数ポインタという。
あるメソッドにデリゲートを引数として渡して、メソッド内でデリゲートを実行させる場合に使用する。
Actionは戻り値を持たない場合に使用する。
using System;
using System.Diagnostics;

namespace ConsoleApplication2
{
    class Program
    {
        static void Main(string[] args)
        {
           // 引数なし
           // ここではデリゲートに代入した文が実行されない。
           Action act = () => { Debug.WriteLine(2 * 4); };

           Foo(() => act());
        }

        static void Foo(Action act) {
           // ここで実行される
           act();
        }
    }
}

using System;
using System.Collections.Generic;
using System.Diagnostics;

namespace ConsoleApplication2
{
    class Program
    {
        static void Main(string[] args)
        {
            // 引数あり
            // ここではデリゲートに代入した文が実行されない。
            Action<string, string> act = (a, b) => { Debug.WriteLine(a + b); };

            Foo(() => act("aaa", "bbb"));
        }
        
        static void Foo(Action act) {
           // ここで実行される
           act();
        }
    }
}

msdn

即時実行メソッド
using System;
using System.Collections.Generic;
using System.Diagnostics;

namespace ConsoleApplication2
{
    class Program
    {
        static void Main(string[] args)
        {
            // 再実行できない。
            new Action<string, string>((a, b) =>
            {
               Debug.WriteLine(a + b);
            })("aaa", "bbb");   // 最後に()をつけることでデリゲートを即時実行する
        }
    }
}

msdn

Func<T, TResult> デリゲート
デリゲートとはメソッドを参照するための型のことである。C言語やC++では関数ポインタという。
あるメソッドにデリゲートを引数として渡して、メソッド内でデリゲートを実行させる場合に使用する。
Funcは戻り値を持つ場合に使用する。
using System;
using System.Diagnostics;
using System.Linq;

namespace ConsoleApplication13
{
   class Program
   {
      static void Main(string[] args)
      {
         // Funcデリゲートの最後の引数が戻り値の型になる
         // 以下のサンプルの場合、int型の引数を2つ持ち、戻り値はstring型となる。
         // 引数なしの場合は1つになる。
         Func<int, int, string> func = (a, b) =>
         {
            return a.ToString() + ":" + b.ToString();
         };

         Foo(() => func(1, 2)); 
      }
      
      static void Foo(Func<string> func)
      {
         Debug.WriteLine(func());
      }
   }
}

msdn

即時実行関数。

using System;
using System.Diagnostics;

namespace ConsoleApplication13
{
   class Program
   {
      static void Main(string[] args)
      {
         string str = "";

         str = new Func<string>(() =>
         {
            return "abc";
         })();

         Debug.WriteLine(str);
      }
   }
}

// abc

msdn


■ジェネリック

ジェネリック
ジェネリックはメソッドの引数に任意の型を指定できるものである。

// デリゲートとジェネリックの混在
using System;
using System.Diagnostics;

namespace ConsoleApplication1 {
   class Program {
      static void Main(string[] args) {
         Foo<int>(5, 0, (a, b) =>   // 5
         {
            // data が負の数の場合は def で置き換える
            if (a < 0) return b;

            return a;
         });

         Foo<int>(-5, 0, (a, b) =>   // 0
         {
            // data が負の数の場合は def で置き換える
            if (a < 0) return b;

            return a;
         });
         
         Foo<float>(5.2f, 0, (a, b) =>   // 5.2
         {
            // data が負の数の場合は def で置き換える
            if (a < 0) return b;

            return a;
         });
      }

      static void Foo<T>(T data, T def, Func<T, T, T> func) {
         Debug.WriteLine(func(data, def));
      }
   }
}


■イベント

イベント
イベントの設定、実行、解除。
// イベント処理の基本

using System;
using System.Diagnostics;
using System.Windows.Forms;

namespace ConsoleApplication13
{
    public partial class Form1 : Form {

        private Alarm alarm = new Alarm();

        public Form1() {
            InitializeComponent();
        }

        // イベント呼び出しで実行される処理
        private void AlartLister1(object sender, EventArgs e) {
            Debug.WriteLine("Alarm listener 1 called");
        }

        // イベント呼び出しで実行される処理
        private void AlartLister2(object sender, EventArgs e) {
            Debug.WriteLine("Alarm listener 2 called");
        }

        private void Form1_Shown(object sender, EventArgs e) {
            // イベントを設定する。
            // 複数のイベントを設定するため += とする。
            // なお呼び出し順は保証されないことに注意が必要
            alarm.OnAlarmRaised += AlartLister1;
            alarm.OnAlarmRaised += AlartLister2;
        }

        private void button1_Click(object sender, EventArgs e) {
            // イベント実行
            // OnAlarmRaisedデリゲートを直接呼び出せない!!
            alarm.RaiseAlarm();
        }

        private void button2_Click(object sender, EventArgs e) {
            // イベント解除
            alarm.OnAlarmRaised -= AlartLister1;
            alarm.OnAlarmRaised -= AlartLister2;
        }
    }

    public class Alarm {
        // イベント呼び出しで実行される処理。呼び出し元で処理をオーバーライドする。
        // event キーワードをつけると、呼び出し元からOnAlarmRaisedデリゲートを直接呼び出せなくする。
        // カプセル化することでイベント実行に制限を与える。
        public event EventHandler OnAlarmRaised = delegate { };

        // イベント呼び出し。外部から呼び出し可能。
        public void RaiseAlarm() {
            OnAlarmRaised(this, EventArgs.Empty);
        }
    }
}

// 独自イベントの作成

using System;
using System.Diagnostics;
using System.Windows.Forms;

namespace ConsoleApplication13
{
   public partial class Form1 : Form {

      private Alarm alarm = new Alarm();

      public Form1() {
         InitializeComponent();
      }

      // イベント呼び出しで実行される処理
      private void AlartLister1(object sender, AlarmEventArgs args) {
         Debug.WriteLine("Alarm listener 1 called");
         Debug.WriteLine(args.Location);
      }

      private void Form1_Shown(object sender, EventArgs e) {
         // イベントを設定する。
         alarm.OnAlarmRaised += AlartLister1;
      }

      private void button1_Click(object sender, EventArgs e) {
         // イベント実行
         alarm.RaiseAlarm("button1_Click!!");
      }

      private void button2_Click(object sender, EventArgs e) {
         // イベント解除
         alarm.OnAlarmRaised -= AlartLister1;
      }
   }

   // 独自イベント
   public class AlarmEventArgs : EventArgs {
      public string Location {
         get; set;
      }

      public AlarmEventArgs(string location) {
         this.Location = location;
      }
   }

   public class Alarm {
      // イベント呼び出しで実行される処理。
      public event EventHandler<AlarmEventArgs> OnAlarmRaised = delegate { };
 
      // イベント呼び出し。外部から呼び出し可能。
      public void RaiseAlarm(string location) {
         OnAlarmRaised(this, new AlarmEventArgs(location));
      }
   }
}


■yield

yield
yield を使用することでコルーチンが実装できる。
yield でメソッドの処理を停止し結果を返す。次のメソッド呼び出しで、停止した次のステップから処理を再開する。これをメソッド終了まで繰り返す。
using System;
using System.Collections.Generic;
using System.Diagnostics;

namespace ConsoleApplication1
{
    class Program
    {
        static void Main(string[] args)
        {
            foreach (var item in Routine())
            {
                Debug.WriteLine(item);
            }
        }

        static IEnumerable<string> Routine()
        {
            Debug.WriteLine("Routine 1");
            yield return "Routine return 1";

            Debug.WriteLine("Routine 2");
            yield return "Routine return 2";

            Debug.WriteLine("Routine 3");
            yield return "Routine return 3";
        }
    }
}

// Routine 1
// Routine return 1
// Routine 2
// Routine return 2
// Routine 3
// Routine return 3

msdn


■LINQ

LINQ
統合言語クエリ( LINQ )。Listクラス、リレーショナルデータベース、XMLなどの異なるデータタイプに対する検索や更新を同一のクエリによって行うことができる。


LINQを使用する上での注意事項を述べる。

LINQは遅延評価、つまり値が必要となるまでクエリを実行しない。
そのため Where などのLINQ系メソッドを呼び出した段階ではクエリは実行されず、ToListメソッドなどで値が必要となる段階で初めてクエリが実行されることになる。
この仕様を理解しておかないと、原因不明のパフォーマンス低下を招く恐れがあるので注意が必要である。

例えば下記のようなプログラムを実行した場合、foreachによるループの範囲が絞り込み前の全レコードとなる。
そして各レコードごとにクエリを実行して、条件判定が true となるレコードのみ返す。
foreach の呼び出し場所が一か所だけなら問題ないが、複数個所で呼び出す場合は効率が悪い。

   var list = Enumerable.Range(1, 10).Where(a => a % 2 == 0);   // この段階ではWehre メソッドの条件判定は実行されない

   foreach (var item in list)   // 1 〜 10 の範囲内でループでまわし、Where メソッドの条件判定を行い、true となるレコードのみ返す
   {
      ;
   }

   foreach (var item in list)   // ここでも 1 〜 10 の範囲内でループでまわし、Where メソッドの条件判定を行い、true となるレコードのみ返す
   {
      ;
   }
   
効率よくループを回すには foreach の前に ToListメソッドを呼び出してあらかじめクエリを実行させておく。
   var list = Enumerable.Range(1, 10).Where(a => a % 2 == 0).ToList();   // Where メソッドの条件判定を実行し、絞り込み結果をlist変数にセットする

   foreach (var item in list)   // 絞り込み結果に対しループで回す
   {
      ;
   }

   foreach (var item in list)   // 絞り込み結果に対しループで回す
   {
      ;
   }
   
また ToList メソッドの呼び出し箇所についても注意が必要である。
ToList メソッドはメモリ上に新しい領域を確保して値をセットしていくため一般的に時間がかかる処理である。
したがってむやみに呼び出すとこれもまたパフォーマンス低下の原因となる。

通常はメソッドチェーンで連結した LINQ 系メソッドの最後に ToList メソッドを1回だけ呼び出すようにしておけばよい。


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

namespace ConsoleApp1
{
    public class ProductList
    {
        public string productName { get; set; }
        public int price { get; set; }
        public string makerName { get; set; }
    };

    public class ShopList
    {
        public string productName { get; set; }
        public string makerName { get; set; }
        public List<string> shopName { get; set; }
    };

    /// <summary>
    /// レコード同士の値比較処理を実装した、クラスオブジェクト同士を比較する際に必要となる。
    /// </summary>
    public class ProductListComparer : IEqualityComparer<ProductList> {
        public bool Equals(ProductList x, ProductList y) {
            if (x.productName == y.productName && x.price == y.price && x.makerName == y.makerName) {
                return true;
            }

            return false;
        }

        public int GetHashCode(ProductList obj) {
            if (obj is ProductList productList)
            {
                return productList.productName.GetHashCode() ^ productList.price ^ productList.makerName.GetHashCode();
            }

            return 0;
        }
    }


    class Program {
        static void Main(string[] args) {
            var productList = new List<ProductList>() {
                new ProductList() { productName = "テレビ",         price = 10000, makerName = "ソ〇ー" },
                new ProductList() { productName = "テレビ",         price = 12000, makerName = "パ〇ソニック" },
                new ProductList() { productName = "カメラ",         price = 15000, makerName = "オリ〇パス" },
                new ProductList() { productName = "スマートフォン", price = 55000, makerName = "〇コモ" },
                new ProductList() { productName = "冷蔵庫",         price = 80000, makerName = "シャー〇" },
                new ProductList() { productName = "エアコン",       price = 70000, makerName = "〇芝" },
            };

            var productList2 = new List<ProductList>() {
                new ProductList() { productName = "パソコン", price = 100000, makerName = "ドスパ〇" },
                new ProductList() { productName = "カメラ",   price = 15000,  makerName = "キヤノ〇" },
                new ProductList() { productName = "エアコン", price = 70000,  makerName = "〇芝" },
            };

            var shopList = new List<ShopList>() {
                new ShopList() { productName = "テレビ", makerName = "ソ〇ー",       shopName = new List<string>(){ "ヨドバ〇", "ビッ〇" }},
                new ShopList() { productName = "テレビ", makerName = "パ〇ソニック", shopName = new List<string>(){ "ヤ〇ダ" }},
                new ShopList() { productName = "テレビ", makerName = "シャー〇",     shopName = new List<string>(){ "〇ジマ" }},
                new ShopList() { productName = "カメラ", makerName = "オリ〇パス",   shopName = new List<string>(){ "ヨドバ〇", "ビッ〇", "ヤ〇ダ" }},
            };


            // ■データの存在チェック

            // すべてのレコードが条件を満たしているかをbool型で返す。
            Debug.WriteLine("All:" + productList.All(a => a.price >= 10000 ));   // True


            // 条件に一致するレコードが存在するかをbool型で返す。文字列は完全一致で比較している。
            Debug.WriteLine("Any:" + productList.Any(a => a.price >= 10000 ));   // True

            // ===============================================


            // ■射影

            // 指定した項目だけのリストを取得する
            var data0 = productList.Select(a => a.productName).ToList();
            Debug.WriteLine(String.Join(",", data0));   // テレビ,テレビ,カメラ,スマートフォン,冷蔵庫,エアコン


            // 指定したコレクション項目を平坦化する
            // Listコレクションである shopList 変数が持つ shopName フィールドもListコレクションである。この shopName フィールドを1次元に展開する。
            var data1 = shopList.SelectMany(a => a.shopName).ToList();
            Debug.WriteLine(String.Join(",", data1));   // ヨドバ〇,ビッ〇,ヤ〇ダ,〇ジマ,ヨドバ〇,ビッ〇,ヤ〇ダ

            // 複数の項目を指定する
            var data6 = productList.Select(a => new{
               productName = a.productName,
               price = a.price
            }).ToList();
            Debug.WriteLine(String.Join(",", data6));   // { productName = テレビ, price = 10000 },{ productName = テレビ, price = 12000 },{ productName = カメラ, price = 15000 },{ productName = スマートフォン, price = 55000 },{ productName = 冷蔵庫, price = 80000 },{ productName = エアコン, price = 70000 }
         
            // ===============================================


            // ■単一レコードの取得

            // 最初のレコードを返す。
            // レコード数が 0 の場合 null を返す。
            var data2 = productList.FirstOrDefault();
            Debug.WriteLine(data2.productName + "," + data2.price);   // テレビ,10000


            // 最後のレコードを返す。
            // レコード数が 0 の場合は nul lを返す
            data2 = productList.LastOrDefault();
            Debug.WriteLine(data2.productName + "," + data2.price);   // エアコン,70000


            // 条件に一致するレコードが1件のみの場合だけ使用できる。レコード数が 0 の場合は null、複数存在する場合は例外が発生するのでWhereを使用すること。
            data2 = productList.SingleOrDefault(a => a.price == 15000);
            Debug.WriteLine(data2.productName + "," + data2.price);   // カメラ,15000

            // ===============================================


            // ■複数レコードの取得

            // 条件に一致する複数のレコードを取得する。条件に一致するデータが見つからない場合はレコード数 0 を返す(foreachとの相性がよい)
            var data3 = productList.Where(a => a.productName == "テレビ").ToList();

            // { productName = テレビ, price = 10000, makerName = ソ〇ー },
            // { productName = テレビ, price = 12000, makerName = パ〇ソニック }
            Debug.WriteLine(String.Join(",", data3.Select(a => new { a.productName, a.price, a.makerName })));


            // 重複するレコードを1レコードにまとめる。なおサンプルでは productName 項目で重複するレコードをまとめている。
            var data4 = productList.Select(a => a.productName).Distinct().ToList();
            Debug.WriteLine(String.Join(",", data4));   // テレビ,カメラ,スマートフォン,冷蔵庫,エアコン


            // 先頭から指定されたレコード数分スキップして残りのレコードを取得する。残りのレコードが存在しない場合でも例外にはならない。
            data3 = productList.Skip(3).ToList();

            // { productName = スマートフォン, price = 55000, makerName = 〇コモ },
            // { productName = 冷蔵庫,         price = 80000, makerName = シャー〇 },
            // { productName = エアコン,       price = 70000, makerName = 〇芝 }
            Debug.WriteLine(String.Join(",", data3.Select(a => new { a.productName, a.price, a.makerName })));


            // 条件の結果が true の間スキップして残りのレコードを取得する。
            data3 = productList.SkipWhile(a => a.price <= 55000).ToList();

            // { productName = 冷蔵庫,   price = 80000, makerName = シャー〇 },
            // { productName = エアコン, price = 70000, makerName = 〇芝 }
            Debug.WriteLine(String.Join(",", data3.Select(a => new { a.productName, a.price, a.makerName })));


            // 先頭から指定されたレコード分を取得する。
            data3 = productList.Take(3).ToList();

            // { productName = テレビ, price = 10000, makerName = ソ〇ー },
            // { productName = テレビ, price = 12000, makerName = パ〇ソニック },
            // { productName = カメラ, price = 15000, makerName = オリ〇パス }
            Debug.WriteLine(String.Join(",", data3.Select(a => new { a.productName, a.price, a.makerName })));


            // 条件の結果が true の間レコードを取得する。
            data3 = productList.TakeWhile(a => a.price < 15000).ToList();

            // { productName = テレビ, price = 10000, makerName = ソ〇ー },
            // { productName = テレビ, price = 12000, makerName = パ〇ソニック }
            Debug.WriteLine(String.Join(",", data3.Select(a => new { a.productName, a.price, a.makerName })));

            // ===============================================


            // ■集計

            // 条件に一致するレコード件数を取得する
            // 条件に一致するレコードの存在チェックをする場合は、CountではなくAnyを使用すること。(Anyのほうがパフォーマンスがよいため)
            var num1 = productList.Count(a => a.productName == "テレビ");
            Debug.WriteLine(num1);   // 2


            // int型のすべてのレコードのうち最大値を取得する。レコード数が 0 の場合は例外を返す。
            num1 = productList.Select(a => a.price).Max();
            Debug.WriteLine(num1);   // 80000


            // int型のすべてのレコードのうち最小値を取得する。レコード数が 0 の場合は例外を返す。
            num1 = productList.Select(a => a.price).Min();
            Debug.WriteLine(num1);   // 10000


            // int型のすべてのレコードの合計値を取得する。レコード数が 0 の場合は0
            num1 = productList.Select(a => a.price).Sum();
            Debug.WriteLine(num1);   // 242000


            // int型のすべてのレコードの平均値を取得する。レコード数が 0 の場合は例外を返す。
            var num2 = productList.Select(a => a.price).Average();
            Debug.WriteLine(num2);   // 40333.3333333333


            // 指定した項目でグルーピングする。
            // サンプルでは price 項目が 55000 以下のレコードで絞り込んだうえで、name 項目でグルーピングし、グルーピングしたレコードごとにpriceの合計を計算している。
            var data5 = productList.Where(a => a.price <= 55000).GroupBy(a => a.productName).Select(a =>
                new { name = a.Key,
                      price = a.Sum(b => b.price) }).ToList();

            // { name = テレビ,         price = 22000 },
            // { name = カメラ,         price = 15000 },
            // { name = スマートフォン, price = 55000 }
            Debug.WriteLine(string.Join(",", data5));


            // 畳み込み処理
            // 以下のサンプルの場合、初期値 0 を 変数 n に代入して、各レコードの price の値 + 1 を 変数 n に加算していく。
            num1 = productList.Aggregate(0, (n, elm) => n + elm.price + 1);
            Debug.WriteLine(num1);

            // ===============================================


            // ■ソート

            data3 = productList.OrderBy(a => a.productName).ThenBy(a => a.price).ToList();

            // { productName = エアコン,       price = 70000 },
            // { productName = カメラ,         price = 15000 },
            // { productName = スマートフォン, price = 55000 },
            // { productName = テレビ,         price = 10000 },
            // { productName = テレビ,         price = 12000 },
            // { productName = 冷蔵庫,         price = 80000 }
            Debug.WriteLine(String.Join(",", data3.Select(a => new { a.productName, a.price })));


            data3 = productList.OrderByDescending(a => a.productName).ThenByDescending(a => a.price).ToList();

            // { productName = 冷蔵庫,         price = 80000 },
            // { productName = テレビ,         price = 12000 },
            // { productName = テレビ,         price = 10000 },
            // { productName = スマートフォン, price = 55000 },
            // { productName = カメラ,         price = 15000 },
            // { productName = エアコン,       price = 70000 }
            Debug.WriteLine(String.Join(",", data3.Select(a => new { a.productName, a.price })));

            // ===============================================


            // ■結合

            // 指定したレコードを追加する。同じデータがあってもまとめずそのまま追加する。
            var merge1 = productList.Select(a => a.productName).Concat(productList2.Select(a => a.productName)).ToList();
            merge1.ForEach(a => Debug.Write(a + ","));   // テレビ,テレビ,カメラ,スマートフォン,冷蔵庫,エアコン,パソコン,カメラ,エアコン,
            Debug.WriteLine("");


            // 指定したレコードを追加する。同じデータがあった場合1つにまとめる。
            merge1 = productList.Select(a => a.productName).Union(productList2.Select(a => a.productName)).ToList();
            merge1.ForEach(a => Debug.Write(a + ","));   // テレビ,カメラ,スマートフォン,冷蔵庫,エアコン,パソコン,
            Debug.WriteLine("");


            // INNER JOIN
            var merge2 = productList.Join(
                shopList,    // 結合するオブジェクト
                a => new { a.productName, a.makerName },  // 結合元となるオブジェクトの結合キー
                b => new { b.productName, b.makerName },  // 結合するオブジェクトの結合キー
                (a, b) => new {      // 生成するオブジェクトの項目を指定
                   a.productName,
                   a.price,
                   a.makerName,
                   shopName = String.Join(",", b.shopName)
                }).ToList();

            // { productName = テレビ, price = 10000, makerName = ソ〇ー,       shopName = ヨドバ〇,ビッ〇 },
            // { productName = テレビ, price = 12000, makerName = パ〇ソニック, shopName = ヤ〇ダ },
            // { productName = カメラ, price = 15000, makerName = オリ〇パス,   shopName = ヨドバ〇,ビッ〇,ヤ〇ダ }
            Debug.WriteLine(String.Join(",", merge2));   


            // OUTER JOIN
            var merge3 = productList.GroupJoin(
                shopList,    // 結合するオブジェクト
                a => new { a.productName, a.makerName },  // 結合元となるオブジェクトの結合キー
                b => new { b.productName, b.makerName },  // 結合するオブジェクトの結合キー
                (a, b) => new {      // 生成するオブジェクトの項目を指定
                   a.productName,
                   a.price,
                   a.makerName,
                   shopName = String.Join(",", b.SelectMany(c => c.shopName))
                }).ToList();

            // { productName = テレビ,         price = 10000, makerName = ソ〇ー,       shopName = ヨドバ〇,ビッ〇 },
            // { productName = テレビ,         price = 12000, makerName = パ〇ソニック, shopName = ヤ〇ダ },
            // { productName = カメラ,         price = 15000, makerName = オリ〇パス,   shopName = ヨドバ〇,ビッ〇,ヤ〇ダ },
            // { productName = スマートフォン, price = 55000, makerName = 〇コモ,       shopName =  },
            // { productName = 冷蔵庫,         price = 80000, makerName = シャー〇,     shopName =  },
            // { productName = エアコン,       price = 70000, makerName = 〇芝,         shopName =  }
            Debug.WriteLine(String.Join(",", merge3));


            // 1レコードずつ指定された条件を満たすレコードを返す。
            // レコード数が不一致の場合、あふれた分は結合されない。
            var merge5 = productList.Zip(productList2, (a, b) =>
            {
                // 2つのListコレクションのうち price の値が大きいほうを返す。
                if (a.price > b.price) return a.price;

                return b.price;
            }).ToList();
            Debug.WriteLine(String.Join(",", merge5));   // 100000,15000,70000

            // ===============================================


            // ■集合

            var comparer = new ProductListComparer();

            // 差集合

            // Listコレクションのジェネリックの型がプリミティブ型やstring型の場合、第2引数がなくても正しく動作する。

            var merge4 = productList.Except(productList2, comparer).ToList();

            // { productName = テレビ, price = 10000 },
            // { productName = テレビ, price = 12000 },
            // { productName = カメラ, price = 15000 },
            // { productName = スマートフォン, price = 55000 },
            // { productName = 冷蔵庫, price = 80000 }
            Debug.WriteLine(String.Join(",", merge4.Select(a => new { a.productName, a.price })));

            // 積集合
            merge4 = productList.Intersect(productList2, comparer).ToList();

            // { productName = エアコン, price = 70000 }
            Debug.WriteLine(String.Join(",", merge4.Select(a => new { a.productName, a.price })));

            // ===============================================


            // ■項目の比較

            // データコピー
            var copy = new List<ProductList>();
            foreach (var item in productList)
            {
                copy.Add(new ProductList()
                {
                    productName = item.productName,
                    price = item.price,
                    makerName = item.makerName
                });
            }

            // Listコレクションのジェネリックの型がプリミティブ型やstring型の場合、第2引数がなくても正しく動作する。

            var isMatch = productList.SequenceEqual(copy, comparer);
            Debug.WriteLine(isMatch);   // true


            copy[0].price = 0;
            isMatch = productList.SequenceEqual(copy, comparer);
            Debug.WriteLine(isMatch);   // false

            // ===============================================


            // ■パラメータの組み合わせパターンの作成

            // パラメータごとにデータ配列を作成し、パラメータの値の組み合わせパターンを作成するしくみ
            // 自動テストなどで使用できる。
            var test1 = from param1 in new[] { 0, 1 }          // 一つ目のパラメータ
                       from param2 in new[] { "abc", "xyz" }  // 二つ目のパラメータ
                       select new { Param1 = param1, Param2 = param2 };
            Debug.WriteLine(String.Join(",", test1.ToList()));   // { Param1 = 0, Param2 = abc },{ Param1 = 0, Param2 = xyz },{ Param1 = 1, Param2 = abc },{ Param1 = 1, Param2 = xyz }
        }
    }
}


■正規表現

正規表現
正規表現は、わかりにくいのでIndexOfメソッドやTryParseメソッドなどで対応できるならそのほうがよい。
しかし ASP.NET MVC のクライアント側のバリデーションチェックでどうしても必要となる場合があるので覚えておいて損はない。
using System.Diagnostics;
using System.Text.RegularExpressions;

namespace ConsoleApp1 {
   class Program {
      static void Main(string[] args) {

         // ■■■半角の正の整数であるかをチェックする
         // []内の-はパターンの範囲を示し、[0-9]の場合は 0 〜 9 のいずれかの文字であるかをチェックする。
         // + は + の直前のパターンと 1 回以上一致するかをチェックする
         // ^ と $ で囲むことで ^ と $ で囲まれたパターンにすべての文字が一致するかをチェックする。
         Debug.WriteLine( Regex.IsMatch("123", "^[0-9]+$") );  // true


         // ■■■半角の数値であるかをチェックする

         // ? は ? の直前のパターンと 0 または 1 回一致するかをチェックする
         // . は正規表現のメタ文字として予約されているので . を検索条件とする場合、\. とする
         // * は * の直前のパターンと 0 回以上一致するかをチェックする
         Debug.WriteLine( Regex.IsMatch("13.3", @"^-?[0-9]+(\.[0-9]*)?$"));  // true
         Debug.WriteLine( Regex.IsMatch("12", @"^-?[0-9]+(\.[0-9]*)?$"));    // true
         Debug.WriteLine( Regex.IsMatch("-12", @"^-?[0-9]+(\.[0-9]*)?$"));   // true
         Debug.WriteLine( Regex.IsMatch("-12.3", @"^-?[0-9]+(\.[0-9]*)?$")); // true
         Debug.WriteLine( Regex.IsMatch("-12.", @"^-?[0-9]+(\.[0-9]*)?$"));  // true


         // ■■■半角の大文字または小文字のアルファベットであるかをチェックする

         // RegexOptions.IgnoreCaseオプションを使用すると大文字小文字を区別しない
         Debug.WriteLine(Regex.IsMatch("abcABC", "^[a-z]+$", RegexOptions.IgnoreCase));  // true


         // ■■■半角の大文字または小文字のアルファベットまたは数字であるかをチェックする

         // [] 内に a-z と 0-9 といったように複数パターン指定した場合、そのいずれかのパターンに一致するかをチェックする
         Debug.WriteLine(Regex.IsMatch("abcABC123", "^[a-z0-9]+$", RegexOptions.IgnoreCase));  // true


         // ■■■有効なファイル名であるかをチェックする。

         // [^パターン] の場合、パターンに一致しないことをチェックする
         // \ は正規表現のメタ文字として予約されているので \ を検索条件とする場合、\\ とする
         Debug.WriteLine( Regex.IsMatch(@"test.txt", @"^[^\\/:*?""<>|]+$") );  // true
         Debug.WriteLine( Regex.IsMatch(@"/test.txt", @"^[^\\/:*?""<>|]+$") );  // false


         // ■■■有効な郵便番号であるかをチェックする

         // {X} は {X} の直前のパターンをX回繰り返す。
         Debug.WriteLine(Regex.IsMatch("111-0222", @"^[0-9]{3}-[0-9]{4}$"));  // true


         // ■■■有効な IPv4 であるかをチェックする

         // (パターン1|パターン2) はパターン1とパターン2のいずれかであることをチェックする。([0-9]|[1-9][0-9]) の場合、0 〜 9 の半角数字、または 10 〜 99 の半角数字と一致するかをチェックする。
         Debug.WriteLine(Regex.IsMatch("127.0.0.1", @"^(([0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5])\.){3}([0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5])$"));  // true


         // ■■■うるう年を考慮した有効な年月日であるかをチェックする

         //^
         // (?!([02468][1235679]|[13579][01345789])000229)               まず年を400で割り切れる場合はうるう年とするが、年を100で割り切れる場合はうるう年でないという判定を行う
         //                                                              そのため400の倍数を除いた100の倍数を?!(否定先読み)を使用して除外する判定を行っている
         //                                                              意味わからん場合は、400の倍数をずらっと並べていくとわかるかも
         //  (([0-9]{4}(01|03|05|07|08|10|12)(0[1-9]|[12][0-9]|3[01]))|  1,3,5,7,8,10,12月は31日までが有効
         //   ([0-9]{4}(04|06|09|11)(0[1-9]|[12][0-9]|30))|              4,6,9,11月は30日までが有効
         //   ([0-9]{4}02(0[1-9]|1[0-9]|2[0-8]))|                        2月は28日までが有効
         //   ([0-9]{2}([02468][048]|[13579][26])0229))                  4で割り切れる場合はうるう年
         // $
         // yyyymmdd形式日付文字列の妥当性をチェックする正規表現を読み解く
         Debug.WriteLine(Regex.IsMatch("20190131", "^(?!([02468][1235679]|[13579][01345789])000229)(([0-9]{4}(01|03|05|07|08|10|12)(0[1-9]|[12][0-9]|3[01]))|([0-9]{4}(04|06|09|11)(0[1-9]|[12][0-9]|30))|([0-9]{4}02(0[1-9]|1[0-9]|2[0-8]))|([0-9]{2}([02468][048]|[13579][26])0229))$"));  // true

         // ■■■Cookieから文字検索
         Debug.WriteLine(Regex.Match("name=太郎;age=30;sex=M;", "(age=.*?;)"));  // age=30;
      }
   }
}


■XML ドキュメント コメント

XML ドキュメント コメント
XML ドキュメント コメントを使用すると、Visual Studio 上でクラスやメンバなどをコーディングする際に説明分が表示されるため、開発効率が向上することが期待できる。
XML ドキュメント コメントを記述するには /// を使用する。
// MyMath.cs

using System;

/// <summary>
/// 算術クラス
/// </summary>
public class MyMath
{
    /// <summary>
    /// 加算結果
    /// </summary>
    private int add { get; set; } = 0;

    /// <summary>
    /// 加算処理を実行する
    /// </summary>
    /// <param name="a">加算する値</param>
    /// <returns>加算結果</returns>
    public int Add(int a)
    {
        this.add += a;
        return this.add;
    }

    /// <see cref="MyMath.Add(int)">のオーバーロードです
    // Ctrl押しながら上記のAddをクリックすると、参照先のメソッドへジャンプします
    public int Add()
    {
        this.add += 3;
        return this.add;
    }
}

ドキュメント コメント用の推奨タグ


■Trace

Trace
呼び出し元情報属性を使用すると、メソッドの呼び出し元に関する情報を取得できる。ソース コードのファイル パス、行番号、および呼び出し元のメンバー名を取得できる。
この情報はトレース、デバッグ、および診断ツールの作成を行う場合に使用できる。
using System;
using System.Diagnostics;
using System.Runtime.CompilerServices;

namespace ConsoleApplication3 {
   class Program {
      public static void WriteLine(string message,
          [CallerFilePath] string file = "",
          [CallerLineNumber] int line = 0,
          [CallerMemberName] string member = "") {

         Trace.WriteLine($"{DateTime.Now.ToString("yyyy/MM/dd HH:mm:ss")}: {file}: {line}: {member}: {message}");
      }

      static void Main(string[] args) {
         WriteLine("処理を実行します。");   // 2019/09/04 21:59:08: C:\Users\aaa\Documents\Visual Studio 2017\Projects\ConsoleApp3\Program.cs: 16: Main: 処理を実行します。
      }
   }
}


■デバッグ情報出力

デバッグ情報出力
デバッグ情報を出力するにはConsole.WriteLineメソッドではなくDebug.WriteLineメソッドを使用すること。
WindowsFormの場合、Console.WriteLineメソッドはプロジェクトのプロパティの設定にある「32 ビットを優先」をチェックしている時しか使えないためである。

Debug.WriteLine("Start");
Debug.Indent();
Debug.WriteLine("Indent");
Debug.Unindent();
Debug.WriteLine("End");
string str = "aaa";
Debug.WriteLineIf(String.IsNullOrEmpty(str), "Empty");

// Start
//   Indent
// End
// str変数が null または 空文 の場合のみEmptyと出力される。


■Obsolete

Obsolete
Obsolete属性をマークしたメソッドにアクセスすると、CS0612警告を発生させる。
将来のバージョンでサポートされなくなるメソッドに付与する。
using System;

class MyClass
{
   [Obsolete]
   public static void ObsoleteMethod()
   {
   }

   [Obsolete]
   public static int ObsoleteField;
}

class MainClass
{  
   static public void Main()
   {
      MyClass.ObsoleteMethod();    // CS0612警告
      MyClass.ObsoleteField = 0;   // CS0612警告
   }
}


■例外処理

例外処理
例外処理

// 例外を上の階層へ伝播させる

using System.Diagnostics;

namespace ConsoleApp1 {
   class Program {
      static void Main(string[] args) {
         try
         {
            //int a = 0, b = 0;
            //a = a / b;

            Foo();
         }
         catch (Exception ex)
         {
            // ex.InnerException が null 以外の場合は、ex.InnerException.StackTrace を返す。
            // ex.InnerException が null の場合は、ex.StackTrace を返す。
            // null 条件演算子null 合体演算子 の合わせ技
            Debug.WriteLine(ex.InnerException?.StackTrace ?? ex.StackTrace);
         }
      }

      static void Foo() {
         
         try
         {
            int a = 0, b = 0;
            a = a / b;
         }
         catch (Exception ex)
         {
            // 例外を上の階層へ伝播させる。
            // StackTraceには例外の発生個所が設定されるが、以下のように内部例外を使用しないと
            // 実際に例外が発生した箇所( a = a / b )の行番号ではなく、ここの行番号で上書きされてしまうので注意が必要。
            // なお throw; すると上書きされないという情報があるが、throw; を使用しても上書きされるので使わないように!!
            throw new Exception("例外が発生しました", ex);
         }
      }
   }
}

例外の種類


■キュー

キュー
オブジェクトの先入れ先出しコレクションを表す。
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Windows.Forms;

namespace WindowsFormsApplication9
{
   public partial class Form1 : Form
   {
      public Form1()
      {
         InitializeComponent();
      }

      private void Form1_Load(object sender, EventArgs e)
      {
         var demoQueue = new Queue<string>();
         
         demoQueue.Enqueue("Rob Miles");
         demoQueue.Enqueue("Immy Brown");
         
         if (demoQueue.Count > 0)
         {
            Debug.WriteLine(demoQueue.Dequeue());   // Rob Miles
         }
         
         if (demoQueue.Count > 0)
         {
            Debug.WriteLine(demoQueue.Dequeue());   // Immy Brown
         }
      }
   }
}

msdn


■スタック

スタック
オブジェクトの先入れ後出しコレクションを表す。
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Windows.Forms;

namespace WindowsFormsApplication9
{
   public partial class Form1 : Form
   {
      public Form1()
      {
         InitializeComponent();
      }

      private void Form1_Load(object sender, EventArgs e)
      {
         var demoStack = new Stack<string>();
         
         demoStack.Push("Rob Miles");
         demoStack.Push("Immy Brown");
         
         if (demoStack.Count > 0)
         {
            Debug.WriteLine(demoStack.Pop());   // Immy Brown
         }
         
         if (demoStack.Count > 0)
         {
            Debug.WriteLine(demoStack.Pop());   // Rob Miles
         }
      }
   }
}

msdn


■非同期処理

非同期処理
await は、同期メソッドのタスクに適用され、メソッドの実行を待機中のタスクが完了するまで中断する。
await 演算子を適用するタスクは、通常タスク ベースの非同期パターンを実装するメソッドに適用する。
例えば、Task、Task<TResult>、ValueTask、および ValueTask<TResult> オブジェクトを返すメソッドである。

async は、メソッド、ラムダ式、または匿名メソッドが非同期であることを指定する。awaitを1つ以上含むメソッドにつける必要がある。

await、asyncは C# 5.0 以上( Visual Studio 2012 以降 )で使用できる

Taskクラスは、スレッド間での値の受け渡しに使用する。.NetFramework 4.0 以上で使用できる。


// 同期処理

using System;
using System.Threading.Tasks;
using System.Windows.Forms;
using System.Net.Http;
using System.Diagnostics;

namespace Sample01
{
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
        }

        // 戻り値を指定することで呼び出し元で待機することが可能。待機するかどうかをメソッド利用者が選択できるようにするため戻り値を指定するのが望ましい。
        // 参考サイト:async、awaitそしてTaskについて(非同期とは何なのか)
        private async Task<string> DoWork()
        {
            using (var client = new HttpClient()) {
                Debug.WriteLine(DateTime.Now.ToString("yyyy/MM/dd HH:mm:ss") + ":DoWork Start");

                // コントロールを持つ親スレッド側で待機する場合、子スレッド内の待機可能メソッドにはConfigureAwaitメソッドをつける必要がある。そうしないとデットロックする。
                // 参考サイト:同期メソッドから非同期メソッドを呼び出すとアプリケーションがフリーズする - 非同期メソッド呼び出しによるデッドロック (C#プログラミング)
                var ret = await client.GetStringAsync("http://dev.windows.com/ja-jp").ConfigureAwait(continueOnCapturedContext: false);

                Debug.WriteLine(DateTime.Now.ToString("yyyy/MM/dd HH:mm:ss") + ":DoWork End");

                return ret;
            }
        }

        private void button1_Click(object sender, EventArgs e)
        {
            Debug.WriteLine(DateTime.Now.ToString("yyyy/MM/dd HH:mm:ss") + ":Main Start");

            // DoWorkメソッドがTaskを返しているのでWaitメソッドを呼び出して待機できる。
            // Waitメソッドを呼び出さない場合は非同期処理となる。
            // 待機しているので DoWork Start -> TaskStart -> TaskEnd -> DoWork End の順番で呼び出される  
            Task<string> ret = DoWork();
            
            ret.Wait();   // DoWorkが戻り値を返すまで待つ  

            Debug.WriteLine(DateTime.Now.ToString("yyyy/MM/dd HH:mm:ss") + ":Main End");
        }
    }
}

// 非同期処理

using System;
using System.Threading.Tasks;
using System.Windows.Forms;
using System.Net.Http;
using System.Diagnostics;

namespace Sample01
{
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
        }

        // このサンプルのように非同期の場合、戻り値なしにもできるが、待機するかどうかをメソッド利用者が選択できるようにするため戻り値を指定するのが望ましい。
        private async void DoWork()
        {
            using (var client = new HttpClient()) {
                Debug.WriteLine(DateTime.Now.ToString("yyyy/MM/dd HH:mm:ss") + ":DoWork Start");

                await client.GetStringAsync("http://dev.windows.com/ja-jp").ConfigureAwait(continueOnCapturedContext: false);

                Debug.WriteLine(DateTime.Now.ToString("yyyy/MM/dd HH:mm:ss") + ":DoWork End");
            }
        }

        private void button1_Click(object sender, EventArgs e)
        {
            Debug.WriteLine(DateTime.Now.ToString("yyyy/MM/dd HH:mm:ss") + ":Main Start");

            DoWork();

            // Waitメソッドなし

            Debug.WriteLine(DateTime.Now.ToString("yyyy/MM/dd HH:mm:ss") + ":Main End");
        }
    }
}

// 並列処理

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Net.Http;
using System.Threading.Tasks;
using System.Windows.Forms;

namespace Sample01
{
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
        }

        private async Task<string> DoWork(int i)
        {
            using (var client = new HttpClient()) {
                Debug.WriteLine(DateTime.Now.ToString("yyyy/MM/dd HH:mm:ss") + ":" + "DoWork" + i.ToString() + " Start");

                var ret = await client.GetStringAsync("http://dev.windows.com/ja-jp").ConfigureAwait(continueOnCapturedContext: false);

                Debug.WriteLine(DateTime.Now.ToString("yyyy/MM/dd HH:mm:ss") + ":" + "DoWork" + i.ToString() + " End");

                return ret;
            }
        }

        private void button1_Click(object sender, EventArgs e)
        {
            Debug.WriteLine(DateTime.Now.ToString("yyyy/MM/dd HH:mm:ss") + ":Main Start");

            var taskList = new List<Task>();

            for (int i = 0; i < 6; i++) 
            {
                // i をそのままDoWorkメソッドに代入してはいけない!!
                // 仮引数の値はメソッド実行時に決定する。そしてループごとにスレッドが実行されるとは限らない。
                // ループを抜けてからスレッドが実行されることもあり、その時の i の値は 6 であるため、DoWorkメソッドの仮引数の値も 6 になってしまう。
                // ループごとに毎回宣言している変数 n を DoWork に渡すことでこれを防ぐ。
                int n = i;
                taskList.Add(Task.Run(() => DoWork(n)));
            }

            // すべてのスレッドが終了するまで待機する。
            // コメントにすると非同期となり、Clickイベント終了時にスレッドが実行される。
            Task.WaitAll(taskList.ToArray());

            Debug.WriteLine(DateTime.Now.ToString("yyyy/MM/dd HH:mm:ss") + ":Main End");
        }
    }
}

// 待機可能メソッドがない場合

using System;
using System.Threading.Tasks;
using System.Windows.Forms;
using System.Diagnostics;
using System.Threading;
using System.Collections.Generic;

namespace Sample01
{
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
        }

        // 待機可能メソッドがない場合は戻り値も async も不要
        private void DoWork(int i)
        {
            Debug.WriteLine(DateTime.Now.ToString("yyyy/MM/dd HH:mm:ss") + ":" + "DoWork" + i.ToString() + " Start");

            Thread.Sleep(5000);
            
            Debug.WriteLine(DateTime.Now.ToString("yyyy/MM/dd HH:mm:ss") + ":" + "DoWork" + i.ToString() + " End");
        }

        private void button1_Click(object sender, EventArgs e)
        {
            Debug.WriteLine(DateTime.Now.ToString("yyyy/MM/dd HH:mm:ss") + ":Main Start");

            var taskList = new List<Task>();

            for (int i = 0; i < 6; i++) 
            {
                int n = i;
                taskList.Add(Task.Run(() => DoWork(n)));
            }

            Task.WaitAll(taskList.ToArray());

            Debug.WriteLine(DateTime.Now.ToString("yyyy/MM/dd HH:mm:ss") + ":Main End");
        }
    }
}

// 非同期スレッド終了後に別のスレッドを呼び出す

using System;
using System.Diagnostics;
using System.Net.Http;
using System.Threading.Tasks;
using System.Windows.Forms;

namespace Sample01
{
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
        }

        private async Task DoWork1(int i)
        {
            using (var client = new HttpClient()) {
                Debug.WriteLine(DateTime.Now.ToString("yyyy/MM/dd HH:mm:ss") + ":DoWork1 Start");

                var ret = await client.GetStringAsync("http://dev.windows.com/ja-jp").ConfigureAwait(continueOnCapturedContext: false);;

                Debug.WriteLine(DateTime.Now.ToString("yyyy/MM/dd HH:mm:ss") + ":DoWork1 End");
            }
        }

        private async Task DoWork2(int i)
        {
            using (var client = new HttpClient()) {
                Debug.WriteLine(DateTime.Now.ToString("yyyy/MM/dd HH:mm:ss") + ":DoWork2 Start");

                var ret = await client.GetStringAsync("http://dev.windows.com/ja-jp").ConfigureAwait(continueOnCapturedContext: false);;

                Debug.WriteLine(DateTime.Now.ToString("yyyy/MM/dd HH:mm:ss") + ":DoWork2 End");
            }
        }

        private void button1_Click(object sender, EventArgs e)
        {
            Debug.WriteLine(DateTime.Now.ToString("yyyy/MM/dd HH:mm:ss") + ":Main Start");

            Task task = Task.Run(() => DoWork1(1));

            // DoWork1完了後にDoWork2を呼び出す
            // WaitメソッドもWaitAllメソッドも呼び出していないため非同期となる
            task.ContinueWith(a => DoWork2(2));
            
            Debug.WriteLine(DateTime.Now.ToString("yyyy/MM/dd HH:mm:ss") + ":Main End");
        }
    }
}

// 子スレッド内でコントロールを更新( Windows Form版 )

using System;
using System.Threading.Tasks;
using System.Windows.Forms;
using System.Net.Http;
using System.Diagnostics;

namespace Sample01
{
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
        }

        private async Task<string> DoWork()
        {
            using (var client = new HttpClient()) {
                Debug.WriteLine(DateTime.Now.ToString("yyyy/MM/dd HH:mm:ss") + ":DoWork Start");

                var ret = await client.GetStringAsync("http://dev.windows.com/ja-jp").ConfigureAwait(continueOnCapturedContext: false);

                // 自身と異なる親スレッド上にあるコントロールにアクセスするためInvoke系メソッドを使用する。
                // BeginInvokeメソッドに代入したデリゲート親スレッドのイベント終了後に呼び出される。
                // EndInvokeメソッドを呼び出すとEndInvokeメソッドで待機するが、DoWorkメソッド呼び出し時点でデリゲートは呼び出されていないためデットロックする
                // よって子スレッド内ではEndInvokeメソッドを呼び出すべきではない。

                // Actionについてはこちらを参照
                var ret2 = label1.BeginInvoke(new Action<string>((b) => {
                    label1.Text = b.Substring(0, 100);
                }), ret);
                
                //label1.EndInvoke(ret2);
                
                Debug.WriteLine(DateTime.Now.ToString("yyyy/MM/dd HH:mm:ss") + ":DoWork End");

                return ret;
            }
        }

        private void button1_Click(object sender, EventArgs e)
        {
            Debug.WriteLine(DateTime.Now.ToString("yyyy/MM/dd HH:mm:ss") + ":Main Start");

            Task<string> ret = DoWork();
            
            ret.Wait();
            
            Debug.WriteLine(DateTime.Now.ToString("yyyy/MM/dd HH:mm:ss") + ":Main End");
        }
    }
}

// 子スレッド内でコントロールを更新( WPF版 )

using System;
using System.Diagnostics;
using System.Net.Http;
using System.Threading.Tasks;
using System.Windows;

namespace Sample01
{
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();
        }

        private async Task<string> DoWork()
        {
            using (var client = new HttpClient()) {
                Debug.WriteLine(DateTime.Now.ToString("yyyy/MM/dd HH:mm:ss") + ":DoWork Start");

                var ret = await client.GetStringAsync("http://dev.windows.com/ja-jp").ConfigureAwait(continueOnCapturedContext: false);

                // 自身と異なる親スレッド上にあるコントロールにアクセスするためInvoke系メソッドを使用する。
                // BeginInvokeメソッドに代入したデリゲートは親スレッドのイベント終了後に呼び出される。
                // Invokeメソッドを使用すると同期処理となるが、DoWorkメソッド呼び出し時点でデリゲートは呼び出されていないためデットロックする
                // よって子スレッド内ではInvokeメソッドを呼び出すべきではない。

                // Actionについてはこちらを参照
                var ret2 = label1.Dispatcher.BeginInvoke(new Action<string>((b) => {
                    label1.Content = b.Substring(0, 100);
                }), ret);

                Debug.WriteLine(DateTime.Now.ToString("yyyy/MM/dd HH:mm:ss") + ":DoWork End");

                return ret;
            }
        }

        private void button1_Click(object sender, RoutedEventArgs e)
        {
            Debug.WriteLine(DateTime.Now.ToString("yyyy/MM/dd HH:mm:ss") + ":Main Start");

            Task<string> ret = DoWork();

            ret.Wait();

            Debug.WriteLine(DateTime.Now.ToString("yyyy/MM/dd HH:mm:ss") + ":Main End");
        }
    }
}

// スレッドセーフなキュー
// ※キュー以外は以下を参照
// ConcurrentStack
// ConcurrentBag
// ConcurrentDictionary

using System;
using System.Collections.Concurrent;
using System.Diagnostics;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Forms;

namespace Sample01
{
    public partial class Form1 : Form
    {
        private BlockingCollection<int> data;

        public Form1()
        {
            InitializeComponent();
        }

        private void button1_Click(object sender, EventArgs e)
        {
            Debug.WriteLine(DateTime.Now.ToString("yyyy/MM/dd HH:mm:ss") + ":Main Start");

            // スレッドセーフな Queue オブジェクトのインスタンス作成
            data = new BlockingCollection<int>(new ConcurrentQueue<int>(), 5);
            
            Task.Run(() => {
                for (var i = 0; i < 5; i++) {
                    // キューにデータ追加
                    data.Add(i);
                    Debug.WriteLine("data{0} added successfully", i);
                }

                // これ以上追加できないようにする
                data.CompleteAdding();
            });

            Task.Run(() => {
                // dataにデータがある、または追加禁止でない場合回す。
                while (!data.IsCompleted || !data.IsAddingCompleted) {
                    var inv = -1;
                    // キューからデータを取り出す。
                    // データがあるとは限らないのでTry系メソッドを使用する
                    if (data.TryTake(out inv)) 
                    { 
                        Debug.WriteLine("data{0} taked successfully", inv);
                    }
                }
            });

            Debug.WriteLine(DateTime.Now.ToString("yyyy/MM/dd HH:mm:ss") + ":Main End");
        }
    }
}

// lock

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Threading.Tasks;
using System.Windows.Forms;

namespace Sample01
{
    public partial class Form1 : Form
    {
        private int[] items = Enumerable.Range(0, 100).ToArray();
        
        // itemsの数値の合計
        private long sharedTotal = 0;

        // ロック時に使用するオブジェクト
        private object lock1 = new object();

        public Form1() {
            InitializeComponent();
        }

        private void addRangeOfValues(int start, int end) 
        {
            while (start < end && start < items.Count()) 
            {
                // lockステートメントを外して実行するとsharedTotalの合計が4950未満となる場合がある。
                // これは複数のスレッドが同時にsharedTotalの値を参照し、後勝ちで更新するため始めの更新が無効となるからである。
                // なおデットロックするためlockステートメントを入れ子にしないように!!
                lock(lock1){
                    sharedTotal += items[start];
                }

                start++;
            }
        }


        private void button1_Click(object sender, EventArgs e) 
        {
            const int offset = 10;
            int start = 0;
            List<Task> taskList = new List<Task>();

            Debug.WriteLine(DateTime.Now.ToString("yyyy/MM/dd HH:mm:ss") + ":Main Start");

            sharedTotal = 0;

            foreach (var item in items)
            {
                int st = start;
                int ed = start + offset;
                taskList.Add(Task.Run(() => addRangeOfValues(st, ed)));

                start += offset;
            }

            Task.WaitAll(taskList.ToArray());

            Debug.WriteLine(sharedTotal);

            Debug.WriteLine(DateTime.Now.ToString("yyyy/MM/dd HH:mm:ss") + ":Main End");
        }
    }
}

// 途中で停止させる

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Threading.Tasks;
using System.Windows.Forms;

namespace Sample01
{
    public partial class Form1 : Form
    {
        // Taskへキャンセル要求を行う
        private CancellationTokenSource cancellationTokenSource;

        public Form1()
        {
            InitializeComponent();
        }

        private void DoWork(int i)
        {
            // キャンセル要求されたら何もしない
            if (cancellationTokenSource.IsCancellationRequested)
            {
                return;
            }

            Debug.WriteLine(DateTime.Now.ToString("yyyy/MM/dd HH:mm:ss") + ":" + "DoWork" + i.ToString() + " Start");

            Thread.Sleep(5000);
            
            Debug.WriteLine(DateTime.Now.ToString("yyyy/MM/dd HH:mm:ss") + ":" + "DoWork" + i.ToString() + " End");
        }

        // 処理開始ボタンクリックイベント
        private void button1_Click(object sender, EventArgs e)
        {
            Debug.WriteLine(DateTime.Now.ToString("yyyy/MM/dd HH:mm:ss") + ":Main Start");

            cancellationTokenSource = new CancellationTokenSource();

            var taskList = new List<Task>();

            for (int i = 0; i < 30; i++) 
            {
                int n = i;
                taskList.Add(Task.Run(() => DoWork(n)));
            }

            // WaitAllメソッドを使用しないように!!待機状態になると画面がロックされクリックイベントを受け付けなくなる。
            
            Debug.WriteLine(DateTime.Now.ToString("yyyy/MM/dd HH:mm:ss") + ":Main End");
        }

        // 停止ボタンクリックイベント
        private void button2_Click(object sender, EventArgs e) {
            Debug.WriteLine(DateTime.Now.ToString("yyyy/MM/dd HH:mm:ss") + ": Cancel Request");
            
            cancellationTokenSource.Cancel();
        }
    }
}

子スレッドからさらに孫スレッドを呼び出す場合は、Task.Factory.StartNew()メソッドを使用すること。
Microsoft Docs


■並列処理

並列処理
.NetFramework 4.0 以上で使用できる、並列処理を実装するための構文を示す。
Taskクラスと異なり、WaitAllメソッドの呼び出しが不要なためコード量が少なくなる利点がある。
その反面、Parallelは同期専用であることに注意が必要である。
// スレッドを個別指定する場合

using System;
using System.Threading.Tasks;
using System.Windows.Forms;
using System.Threading;
using System.Diagnostics;

namespace Sample01
{
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
        }

        private void Task1()
        {
           Debug.WriteLine(DateTime.Now.ToString("yyyy/MM/dd HH:mm:ss") + ":Task1 Start");
           Thread.Sleep(2000);
           Debug.WriteLine(DateTime.Now.ToString("yyyy/MM/dd HH:mm:ss") + ":Task1 End");
        }

        private void Task2()
        {
           Debug.WriteLine(DateTime.Now.ToString("yyyy/MM/dd HH:mm:ss") + ":Task2 Start");
           Thread.Sleep(1000);
           Debug.WriteLine(DateTime.Now.ToString("yyyy/MM/dd HH:mm:ss") + ":Task2 End");
        }

        private void button1_Click(object sender, EventArgs e)
        {
            Debug.WriteLine(DateTime.Now.ToString("yyyy/MM/dd HH:mm:ss") + ":Main Start");

            Parallel.Invoke(() => Task1(), () => Task2());

            Debug.WriteLine(DateTime.Now.ToString("yyyy/MM/dd HH:mm:ss") + ":Main End");
        }
    }
}

// ForEachによるイテレーション

using System;
using System.Threading.Tasks;
using System.Windows.Forms;
using System.Threading;
using System.Diagnostics;

namespace Sample01
{
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
        }

        private void Task(int i)
        {
           Debug.WriteLine(DateTime.Now.ToString("yyyy/MM/dd HH:mm:ss") + ":Task" + i.ToString() + " Start");
           Thread.Sleep(2000);
           Debug.WriteLine(DateTime.Now.ToString("yyyy/MM/dd HH:mm:ss") + ":Task" + i.ToString() + " End");
        }

        private void button1_Click(object sender, EventArgs e)
        {
            Debug.WriteLine(DateTime.Now.ToString("yyyy/MM/dd HH:mm:ss") + ":Main Start");

            // 値 0 から始まる要素数 10 のイテレータ
            var items = Enumerable.Range(0, 10);

            // 第1引数:イテレータ
            // 第2引数:デリゲート
            Parallel.ForEach( items, item =>{
               Task(item);
            });

            Debug.WriteLine(DateTime.Now.ToString("yyyy/MM/dd HH:mm:ss") + ":Main End");
        }
    }
}

// 途中で停止させる

using System;
using System.Threading.Tasks;
using System.Windows.Forms;
using System.Threading;
using System.Diagnostics;

namespace Sample01
{
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
        }

        private void Task(long i)
        {
           Debug.WriteLine(DateTime.Now.ToString("yyyy/MM/dd HH:mm:ss") + ":Task" + i.ToString() + " Start");
           Thread.Sleep(2000);
           Debug.WriteLine(DateTime.Now.ToString("yyyy/MM/dd HH:mm:ss") + ":Task" + i.ToString() + " End");
        }

        private void button1_Click(object sender, EventArgs e)
        {
            Debug.WriteLine(DateTime.Now.ToString("yyyy/MM/dd HH:mm:ss") + ":Main Start");
            
            var items = Enumerable.Range(1, 1000).ToArray();
            
            // 第1引数:イテレータの開始インデックス
            // 第2引数:イテレータの終了インデックス。指定したインデックス - 1 までをループで繰り返す。
            // 第3引数:デリゲート
            Parallel.For( 0, items.Count(), (int i, ParallelLoopState loopState) =>{
               if (items[i] % 10 == 0)
               {
                  Debug.WriteLine(DateTime.Now.ToString("yyyy/MM/dd HH:mm:ss") + ":Stop");
                  
                  // スレッドを新規実行させずに、実行中のすべてのスレッドが終了したら停止する。
                  
                  loopState.Stop();
               }
               
               Task(items[i]);
            });

            Debug.WriteLine(DateTime.Now.ToString("yyyy/MM/dd HH:mm:ss") + ":Main End");
        }
    }
}

// PLINK

using System;
using System.Threading.Tasks;
using System.Windows.Forms;
using System.Threading;
using System.Diagnostics;

namespace Sample01
{
    public class Person
    {
       public string Name { get; set; }
       public string City { get; set; }
    }
    
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
        }

        private void Task(long i)
        {
           Debug.WriteLine(DateTime.Now.ToString("yyyy/MM/dd HH:mm:ss") + ":Task" + i.ToString() + " Start");
           Thread.Sleep(2000);
           Debug.WriteLine(DateTime.Now.ToString("yyyy/MM/dd HH:mm:ss") + ":Task" + i.ToString() + " End");
        }

        private void button1_Click(object sender, EventArgs e)
        {
            Person [] people = new Person [] {
               new Person { Name = "Henry", City = "Seattle" },
               new Person { Name = "Isaac", City = "Seattle" },
               new Person { Name = "Alan", City = "Hull" },
               new Person { Name = "Beryl", City = "Seattle" },
               new Person { Name = "Charles", City = "London" },
               new Person { Name = "David", City = "Seattle" },
               new Person { Name = "Eddy", City = "Paris" },
               new Person { Name = "Fred", City = "Berlin" },
               new Person { Name = "Gordon", City = "Hull" },
               new Person { Name = "James", City = "London" }
            };

            Debug.WriteLine(DateTime.Now.ToString("yyyy/MM/dd HH:mm:ss") + ":Main Start");
            
            // AsParallelメソッドを呼び出すことでクエリを並列処理する。
            // AsSequentialメソッドを呼び出すことでソート順が保持される。
            // 保持されたソート順に対してTakeメソッドでデータの絞り込みを行う。
            
            var result = ( from person in people.AsParallel()
                                where person.City == "Seattle"
                                orderby (person.Name)
                                select new
                                {
                                   Name = person.Name
                                }).AsSequential().Take(1).ToList();
            

            result.ForEach( a => {
               Debug.WriteLine(a.Name);
            });

            Debug.WriteLine(DateTime.Now.ToString("yyyy/MM/dd HH:mm:ss") + ":Main End");
        }
    }
}

// PLINK

using System;
using System.Threading.Tasks;
using System.Windows.Forms;
using System.Threading;
using System.Diagnostics;

namespace Sample01
{
    public class Person
    {
       public string Name { get; set; }
       public string City { get; set; }
    }
    
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
        }

        private void Task(long i)
        {
           Debug.WriteLine(DateTime.Now.ToString("yyyy/MM/dd HH:mm:ss") + ":Task" + i.ToString() + " Start");
           Thread.Sleep(2000);
           Debug.WriteLine(DateTime.Now.ToString("yyyy/MM/dd HH:mm:ss") + ":Task" + i.ToString() + " End");
        }

        private void button1_Click(object sender, EventArgs e)
        {
            Person [] people = new Person [] {
               new Person { Name = "Henry", City = "Seattle" },
               new Person { Name = "Isaac", City = "Seattle" },
               new Person { Name = "Alan", City = "Hull" },
               new Person { Name = "Beryl", City = "Seattle" },
               new Person { Name = "Charles", City = "London" },
               new Person { Name = "David", City = "Seattle" },
               new Person { Name = "Eddy", City = "Paris" },
               new Person { Name = "Fred", City = "Berlin" },
               new Person { Name = "Gordon", City = "Hull" },
               new Person { Name = "James", City = "London" }
            };

            Debug.WriteLine(DateTime.Now.ToString("yyyy/MM/dd HH:mm:ss") + ":Main Start");
            
            var result = ( from person in people.AsParallel()
                                where person.City == "Seattle"
                                orderby (person.Name)
                                select new
                                {
                                   Name = person.Name
                                });

            // ForAllメソッドを使用することですべてのレコードにアクセスできる。ForEachメソッドではないことに注意が必要。
            result.ForAll(a => {
               Debug.WriteLine(a.Name);
            });

            Debug.WriteLine(DateTime.Now.ToString("yyyy/MM/dd HH:mm:ss") + ":Main End");
        }
    }
}


■Thread

Thread
非同期処理の実装は Task を使用すること。
Threadクラスの場合、並列処理を行う際すべてのスレッドが終了するまで待機することができないため。(たぶん)

スレッドプールは、再利用可能なスレッドオブジェクトのコレクションのこと。
なおスレッドプールはいくつかの注意事項がある。
 1)長時間アイドル状態になるスレッドを多数作成すると、ThreadPoolは有限のため、ThreadPoolがブロックされることがある。
 2)スレッドの優先順位を決められない。
 3)ThreadPoolスレッドが再利用された場合、ThreadLocalは初期化されない。
以上からスレッドプールは、メモリ使用量を抑えたいという明確な理由がない限り使うべきではないと思った。


// Threadクラス

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Threading;
using System.Windows.Forms;

namespace Sample01
{
    public partial class Form1 : Form
    {
        // ThreadLocalは共有変数だが、スレッドごとに値を保持できる
        private static ThreadLocal<bool> flag = new ThreadLocal<bool>(() => {
            return false;   // falseで初期化
        });
        
        List<Thread> threadList = new List<Thread>();
        
        public Form1()
        {
            InitializeComponent();
        }

        // 仮引数 i は Threadのインスタンス作成時に設定した引数 data に対応する
        private void DoWork(object i)
        {
           // flagの値はスレッドごとに保持されるため常にfalseとなる
           Debug.WriteLine(DateTime.Now.ToString("yyyy/MM/dd HH:mm:ss") + ":DoWork" + i.ToString() + " Start:" + flag.ToString());

           // ThreadLocalを変更
           flag.Value = true;

           Thread.Sleep(2000);

           Debug.WriteLine(DateTime.Now.ToString("yyyy/MM/dd HH:mm:ss") + ":DoWork" + i.ToString() + " End:" + flag.ToString());
        }
        
        private void button1_Click(object sender, EventArgs e)
        {
            Debug.WriteLine(DateTime.Now.ToString("yyyy/MM/dd HH:mm:ss") + ":Main Start");

            for(int i=0; i < 50; i++) {
                // dataはスレッドに渡す引数
                var thread = new Thread((data) => {
                    DoWork(data);
                });
                
                // i が偶数のスレッドを優先的に実行するための処理
                // 終了ログの後半に割と奇数スレッドが集まっていることが確認できるはず
                if (i % 2 == 0) {
                    thread.Priority = ThreadPriority.Highest;
                }
                else {
                    thread.Priority = ThreadPriority.Lowest;
                }

                thread.Start(i);

                // コメント外すとスレッドが終了するまで待機する。
                // ただしこのサンプルではボタンクリックでスレッドを停止させることを想定しており、
                // 待機させるとクリックイベントを受け付けなくなるので待機させてはいけない。
                //thread.Join();
                
                threadList.Add(thread);
            }
            
            Debug.WriteLine(DateTime.Now.ToString("yyyy/MM/dd HH:mm:ss") + ":Main End");
        }
        
        private void button2_Click(object sender, EventArgs e) {

            Debug.WriteLine(DateTime.Now.ToString("yyyy/MM/dd HH:mm:ss") + ":Abort!!");

            foreach (var thread in threadList)
            {
                // スレッドが起動中の場合 true
                if (thread.IsAlive)
                {
                    // スレッドを停止させる。
                    thread.Abort();
                }
            }
        }
    }
}

// スレッドプール

using System;
using System.Threading.Tasks;
using System.Windows.Forms;
using System.Threading;
using System.Diagnostics;

namespace Sample01
{
    public partial class Form1 : Form
    {
        // ThreadPoolでThreadLocalを使用すると初期化されずtrueになる場合がある。スレッドプールでThreadLocalは使用しないように!!

        public Form1()
        {
            InitializeComponent();
        }

        // 仮引数 i は Threadのインスタンス作成時に設定した引数 data に対応する
        private void DoWork(object i)
        {
           Debug.WriteLine(DateTime.Now.ToString("yyyy/MM/dd HH:mm:ss") + ":DoWork" + i.ToString() + " Start");

           Thread.Sleep(2000);

           Debug.WriteLine(DateTime.Now.ToString("yyyy/MM/dd HH:mm:ss") + ":DoWork" + i.ToString() + " End");
        }
        
        private void button1_Click(object sender, EventArgs e)
        {
            Debug.WriteLine(DateTime.Now.ToString("yyyy/MM/dd HH:mm:ss") + ":Main Start");

            for (int i = 0; i < 50; i++) {
                int data = i;

                ThreadPool.QueueUserWorkItem(a => 
                {
                    DoWork(data);
                });
            }
            
            Debug.WriteLine(DateTime.Now.ToString("yyyy/MM/dd HH:mm:ss") + ":Main End");
        }
    }
}


■#pragma

#pragma
警告の抑制。警告番号を確認するには、ビルドを行うことで出力ウィンドウに表示される。
using System;

// すべての警告を無効にする
#pragma warning disable

// すべての警告を有効にする。
#pragma warning restore

// 指定した警告番号のみ無効にする。
#pragma warning disable 168, 612

namespace ConsoleApplication23
{
   [ObsoleteAttribute]
   class foo
   {

   }

   class Program
   {
      static void Main(string[] args)
      {
         int a;

         return;
         
         var f = new foo();  // 警告	CS0162	到達できないコードが検出されました。
      }
   }
}

msdn


■ログ出力

テキストログ出力
// テキストファイルに出力

TextWriterTraceListener textListener = new TextWriterTraceListener(@"D:\log.log");
Trace.Listeners.Add(textListener);
Trace.WriteLine("aaa");
Trace.Flush();


// 重要度( Level )に応じて出力するログの種類を制御する // 第1引数は通常アプリケーション名を設定する TraceSource trace = new TraceSource("WindowsFormApp1"); // ここではテキストファイルに出力する TextWriterTraceListener textListener2 = new TextWriterTraceListener(@"D:\log2.log"); trace.Listeners.Add(textListener2); // 第1引数はSourceSwitchの名称を設定する SourceSwitch control = new SourceSwitch("SourceSwitch"); control.Level = SourceLevels.Error; // Errorの場合、Critical および Error を出力する trace.Switch = control; trace.TraceEvent(TraceEventType.Start, 10000); trace.TraceEvent(TraceEventType.Warning, 10001, "{0:yyyy/MM/dd HH:mm:ss}|{1}", DateTime.Now, "Warning Log"); trace.TraceEvent(TraceEventType.Error, 10002, "{0:yyyy/MM/dd HH:mm:ss}|{1}", DateTime.Now, "Error Log"); trace.TraceEvent(TraceEventType.Critical, 10003, "{0:yyyy/MM/dd HH:mm:ss}|{1}", DateTime.Now, "Critical Log"); trace.TraceEvent(TraceEventType.Information, 10004, "{0:yyyy/MM/dd HH:mm:ss}|{1}", DateTime.Now, "Information Log"); trace.Flush(); trace.Close(); // WindowsFormApp1 Error: 10002 : Error!! // WindowsFormApp1 Critical: 10003 : Critical!!
// @ configで設定する // configファイルに以下を追加する <system.diagnostics> <sources> <source name="TraceSourceApp" switchName="sourceSwitch" switchType="System.Diagnostics.SourceSwitch"> <listeners> <add name="myListener" type="System.Diagnostics.TextWriterTraceListener" initializeData="d:\log3.log" /> <remove name="Default"/> </listeners> </source> </sources> <switches> <add name="sourceSwitch" value="Error" /> </switches> </system.diagnostics> // A C# ソースの実装 // ソース名はconfigファイルのsource要素のname属性と同じにすること TraceSource trace = new TraceSource("TraceSourceApp"); trace.TraceEvent(TraceEventType.Start, 10000); trace.TraceEvent(TraceEventType.Warning, 10001, "{0:yyyy/MM/dd HH:mm:ss}|{1}", DateTime.Now, "Warning Log"); trace.TraceEvent(TraceEventType.Error, 10002, "{0:yyyy/MM/dd HH:mm:ss}|{1}", DateTime.Now, "Error Log"); trace.TraceEvent(TraceEventType.Critical, 10003, "{0:yyyy/MM/dd HH:mm:ss}|{1}", DateTime.Now, "Critical Log"); trace.TraceEvent(TraceEventType.Information, 10004, "{0:yyyy/MM/dd HH:mm:ss}|{1}", DateTime.Now, "Information Log"); trace.Flush(); trace.Close(); // WindowsFormApp1 Error: 10002 : Error!! // WindowsFormApp1 Critical: 10003 : Critical!!

イベントログ出力
// @ イベントログへ書き込むにはソース名で一意となるイベントログの事前登録が必要。
//   下記の例を参考にし、管理者権限で起動したコマンドプロンプト上で実行すること。
eventcreate /ID 1 /L Application /SO "my source3" /T Information /D "テスト"

// A configファイルに以下を追加する

  <system.diagnostics>
    <sources>
      <source name="TraceSourceApp" switchName="sourceSwitch" switchType="System.Diagnostics.SourceSwitch">
        <listeners>
          <!-- initializeDataがソース名 -->
          <add name="myListener" type="System.Diagnostics.EventLogTraceListener" initializeData="my source3" />
          <remove name="Default"/>
        </listeners>
      </source>
    </sources>
    <switches>
      <add name="sourceSwitch" value="Error" />
    </switches>
  </system.diagnostics>

// B C# ソースの実装

// イベントログに出力

// ソース名はconfigファイルのsource要素のname属性と同じにすること
TraceSource trace = new TraceSource("TraceSourceApp");
trace.TraceEvent(TraceEventType.Error, 1000, "エラーメッセージ");
trace.Flush();
trace.Close();


// よくあるサンプルではSourceExistsを使用してソース名が登録済であるかをチェックし、未登録の場合はEventLog.CreateEventSourceで登録するようにしている。 // しかしイベントログの書き込み以外の理由で管理者権限で実行する必要がない場合は、eventcreate コマンドを実行し、事前登録する方法をお勧めする。 EventLog eventLog = new EventLog(); eventLog.Source = "my source3"; eventLog.WriteEntry("エラーメッセージ4", EventLogEntryType.Error, 1001);


■動的型付け

動的型付け
実行時にメンバーを動的に追加および削除できるオブジェクト。

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Dynamic;

namespace ConsoleApp1 {
   class Program {
      static void Main(string[] args) {
         dynamic exp = new ExpandoObject();

         // メンバーを追加
         exp.Name = "a";
         Debug.WriteLine((string)exp.Name);

         // メンバーの存在をチェック
         Debug.WriteLine(((IDictionary<string, object>)exp).ContainsKey("Name"));

         // メンバーを削除
         ((IDictionary<string, object>)exp).Remove("Name");

         Debug.WriteLine(((IDictionary<string, object>)exp).ContainsKey("Name"));
         
         // メソッドを追加
         exp.Foo = (Action<string, string>)((a, b) => {
            Debug.Write(a + b + " called");
         });

         exp.Foo("aa", "bb");
      }
   }
}


■プロセス管理

プロセス実行
外部プロセスを実行する。
using System;
using System.Windows.Forms;
using System.Diagnostics;

namespace Sample01
{
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
        }

        private void button1_Click(object sender, EventArgs e)
        {
            Process.Start("C:/Program Files/Internet Explorer/iexplore.exe", "http://maverickproj.web.fc2.com/index.html");
        }
    }
}

プロセス終了
外部プロセスを終了する。
using System;
using System.Windows.Forms;
using System.Diagnostics;

namespace Sample01
{
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
        }

        private void button1_Click(object sender, EventArgs e)
        {
            Process[] ps = Process.GetProcessesByName("iexplore");

            foreach (System.Diagnostics.Process p in ps)
            {
                //プロセスを強制的に終了させる
                p.Kill();

                // 1秒間ブロックし、終了するまで待つ。ブロックするのでフリーズする
                if( p.WaitForExit(1000) )
                {
                    Console.Out.WriteLine("終了した");
                }
                else
                {
                    Console.Out.WriteLine("終了しなかった");
                }
            }
        }
    }
}

■拡張機能

VSCommands
Visual Studio の拡張機能の一つ。VSCommands自体にいろいろと機能はあるようだが、一つだけ紹介する。

Gitブランチの名称が Visual Studio に表示される。
Visual Studio のバージョンによって表示される場所が違うようだが、 Visual Studio 2015 Community の場合、右下の行数とかが表示されるところに表示される。
またブランチの切り替えもできるようである。
セットアップ手順

「ツール」メニューの「拡張機能と更新プログラム」からVSCommandsを選択してインストールする。
なおExpressは拡張機能は使えないので、Communityを使用すること。

インストール後Visual Studioの再起動要求が発生するので再起動する。


■NuGet

Moq
Visual Studio上で自動テストする際に使用する、ダミーオブジェクト。

例えばシステム日付を取得して何らかの処理を行うメソッドの自動テストを行う場合を考える。
この場合システム日付をテストパターンとしてコーディングする必要があるが、システム日付を取得する箇所を
ダミーオブジェクトに置き換えることでテストできるようにする。


1.「コンソール アプリケーション」としてプロジェクトを作成し以下のようにコーディング。なおMoqに置き換えるオブジェクトはポリモーフィズムにより置き換え可能なオブジェクトとする。

// foo.cs

using System;

public class foo
{
    public virtual DateTime GetNow()
    {
        return DateTime.Now;
    }

    public string GetDateDiff()
    {
        DateTime now = this.GetNow();

        return (now - DateTime.Parse("2016/06/15 12:00:00")).ToString();
    }
}

2.「単体テスト プロジェクト」としてプロジェクトを作成し、ライブラリのインストール

NUGetで「Moq: an enjoyable mocking library」をインストールする。


3.単体テストのコーディング
// UnitTest1.cs

using System;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Moq;

[TestClass]
public class UnitTest1
{
   [TestMethod]
   public void GetDateDiff()
   {
      var mock = new Mock();
      mock.Setup(c => c.GetNow()).                                // GetNow()メソッドを対象とし、
              Returns(DateTime.Parse("2016/06/15 12:00:00"));     // 引数の値を常に返すようにする

      var dateDiff = mock.Object.GetDateDiff();

      // Setup()で指定したメソッドが実行されているかをチェックする。実行されていない場合例外が発生する。
      mock.VerifyAll();

      mock.Verify(c => c.GetNow(), Times.Once);

      // テスト結果が期待値と一致しているか。一致していない場合は例外が発生する。
      // なおコンソールに出力したテスト結果は「テストエクスプローラー」の下側に「出力」という項目があるので、それをクリックすると表示される。
      Assert.AreEqual(dateDiff, "00:00:00");

   }
}


プログラムの個人的なメモ

Visual Studio上で自動テストする際に使用する、ダミーオブジェクト。

例えばシステム日付を取得して何らかの処理を行うメソッドの自動テストを行う場合を考える。
この場合システム日付をテストパターンとしてコーディングする必要があるが、システム日付を取得する箇所を
ダミーオブジェクトに置き換えることでテストできるようにする。


1.「コンソール アプリケーション」としてプロジェクトを作成し以下のようにコーディング。なおMoqに置き換えるオブジェクトはポリモーフィズムにより置き換え可能なオブジェクトとする。

public class foo
{
    public virtual DateTime GetNow()
    {
        return DateTime.Now;
    }

    public void GetDateDiff()
    {
        DateTime now = this.GetNow();

        string dateDiff = (now - DateTime.Parse("2016/06/15 12:00:00")).ToString();

        Debug.WriteLine(dateDiff);
    }
}

class Program
{
    static void Main(string[] args)
    {
        var f = new foo();
        f.GetDateDiff();
    }
}

2.「単体テスト プロジェクト」としてプロジェクトを作成し、ライブラリのインストール

NUGetで「Moq: an enjoyable mocking library」をインストールする。


3.単体テストのコーディング
public class foo
{
    // これはそのまま
    public virtual DateTime GetNow()
    {
        return DateTime.Now;
    }

    public void GetDateDiff()
    {
        // this.GetNow()をモックに置き換える
        // DateTime now = this.GetNow();

        var mock = new Mock<foo>();
        mock.Setup(c => c.GetNow()).                                // GetNow()メソッドを対象とし、
                Returns(DateTime.Parse("2016/06/15 12:00:00"));     // 引数の値を常に返すようにする

        var f = mock.Object;

        // GetNow()実行
        DateTime now = f.GetNow();

        string dateDiff = (now - DateTime.Parse("2016/06/15 12:00:00")).ToString();

        Debug.WriteLine(dateDiff);

        // Setup()で指定したメソッドが実行されているかをチェックする。実行されていない場合例外が発生する。
        mock.VerifyAll();
        
        // テスト結果が期待値と一致しているか。一致していない場合は例外が発生する。
        // なおコンソールに出力したテスト結果は「テストエクスプローラー」の下側に「出力」という項目があるので、それをクリックすると表示される。
        Assert.AreEqual( dateDiff, "00:00:00" );
    }
}

[TestClass]
public class UnitTest1
{
    [TestMethod]
    public void Main()
    {
        var f = new foo();
        f.GetDateDiff();
    }
}

プログラムの個人的なメモ

Chaining Assertion
NuGetでインストールできるライブラリの一つ。自動テストで使用する。ここではVisual Studioに標準で使用できる「単体 テストプロジェクト」を使用した場合の手順を紹介する。

Chaining Assertion を使用すると、テスト内容に対する結果を左から右へとメソッドチェーンで連結するようになり、わかりやすくなる。


1.「単体テスト プロジェクト」を使用してプロジェクトを作成する。


2.ライブラリのインストール

NUGetで「Chaining Assertion for MSTest」をインストールする。


3.単体テスト用にコーディング
public class Tuple
{

}

class MyClass
{
    public int IntProp { get; set; }
    public string StrField;
}

public class PrivateMock
{
    private string privateField = "homu";

    private string PrivateField
    {
        get { return this.privateField + this.privateField; }
        set { this.privateField = value; }
    }

    private string PrivateMethod(int count)
    {
        return string.Join("", Enumerable.Repeat(this.privateField, count));
    }
}

public class Person
{
    public int Age { get; set; }
    public string FamilyName { get; set; }
    public string GivenName { get; set; }
}

[TestClass]
public class UnitTest1
{
    [TestMethod]
    public void Main()
    {
       // ■ 比較
       
       // Assertを使用したコーディング
       Assert.AreEqual(Math.Pow(5, 2), 25);

       // Chaining Assertion を使用した場合のコーディング
       Math.Pow(5, 2).Is(25);
   
       // =======================================================================================
   
       // Assertを使用したコーディング
       Assert.IsTrue("foobar".StartsWith("foo") && "foobar".EndsWith("bar"));
       
       // Chaining Assertion を使用した場合のコーディング
       "foobar".Is(s => s.StartsWith("foo") && s.EndsWith("bar"));

       // =======================================================================================

       // Assertを使用したコーディング
       CollectionAssert.AreEqual(new[]{1, 2, 3, 4, 5}, Enumerable.Range(1,5).ToList());
       
       // Chaining Assertion を使用した場合のコーディング
       Enumerable.Range(1, 5).Is(1, 2, 3, 4, 5);
       
       // #######################################################################################

       // ■ Collection の値の比較
              
       var array = new[] { 1, 3, 7, 8 };

       // Assertを使用したコーディング
       Assert.AreEqual(array.Count(), 4);
       
       // Chaining Assertion を使用した場合のコーディング
       array.Count().Is(4);

       // =======================================================================================

       // Assertを使用したコーディング
       Assert.IsTrue(array.Contains(8));

       // Chaining Assertion を使用した場合のコーディング
       array.Contains(8).IsTrue();

       // =======================================================================================

       // Assertを使用したコーディング
       Assert.IsFalse(array.All( i => i < 5 ));

       // Chaining Assertion を使用した場合のコーディング
       array.All(i => i < 5).IsFalse();

       // =======================================================================================

       // Assertを使用したコーディング
       Assert.IsTrue(array.Any());

       // Chaining Assertion を使用した場合のコーディング
       array.Any().Is(true);

       // =======================================================================================

       // Assertを使用したコーディング
       Assert.IsFalse(new int[] { }.Any());

       // Chaining Assertion を使用した場合のコーディング
       new int[] { }.Any().Is(false);

       // =======================================================================================

       // Assertを使用したコーディング
       CollectionAssert.AreEqual(array.OrderBy(x => x).ToList(), array);

       // Chaining Assertion を使用した場合のコーディング
       array.OrderBy(x => x).Is(array);

       // #######################################################################################

       // ■ Null判定、オブジェクト判定、型判定
       
       Object obj = null;
       
       // Assertを使用したコーディング
       Assert.IsNull( obj );

       // Chaining Assertion を使用した場合のコーディング
       obj.IsNull();

       // =======================================================================================

       // Assertを使用したコーディング
       Assert.IsNotNull( new Object() );

       // Chaining Assertion を使用した場合のコーディング
       new Object().IsNotNull();

       // =======================================================================================

       // Assertを使用したコーディング
       Assert.AreNotEqual( "foobar", "fooooooo" );

       // Chaining Assertion を使用した場合のコーディング
       "foobar".IsNot("fooooooo");

       // =======================================================================================

       // Assertを使用したコーディング
       CollectionAssert.AreNotEqual( new[] { "a", "z", "x" }, new[] { "a", "x", "z" } );

       // Chaining Assertion を使用した場合のコーディング
       new[] { "a", "z", "x" }.IsNot("a", "x", "z");
       new[] { "a", "z", "x" }.IsNot( new[] { "a", "x", "z" });

       // =======================================================================================

       var tuple = new Tuple();

       // Assertを使用したコーディング
       Assert.AreSame( tuple, tuple );

       // Chaining Assertion を使用した場合のコーディング
       tuple.IsSameReferenceAs( tuple );

       // =======================================================================================

       // Assertを使用したコーディング
       Assert.AreNotSame( tuple, new Tuple() );

       // Chaining Assertion を使用した場合のコーディング
       tuple.IsNotSameReferenceAs( new Tuple() );

       // =======================================================================================

       // Assertを使用したコーディング
       Assert.IsInstanceOfType( "foobar", Type.GetType("System.String") );

       // Chaining Assertion を使用した場合のコーディング
       "foobar".IsInstanceOf<string>();
       
       // =======================================================================================

       // Assertを使用したコーディング
       Assert.IsInstanceOfType( 999.0, Type.GetType("System.Double") );

       // Chaining Assertion を使用した場合のコーディング
       (999.0).IsInstanceOf<double>();

       // #######################################################################################

       // ■ Collection で大文字小文字区別なしで比較

       var lower = new[] { "a", "b", "c" };
       var upper = new[] { "A", "B", "C" };
       
       // =======================================================================================

       // Assertを使用したコーディング
       CollectionAssert.AreEqual(lower, upper, StringComparer.OrdinalIgnoreCase);   // 大文字小文字区別なしで比較

       // Chaining Assertion を使用した場合のコーディング
       lower.Is(upper, StringComparer.OrdinalIgnoreCase);   // 大文字小文字区別なしで比較

       // =======================================================================================

       // Assertを使用したコーディング
       CollectionAssert.AreEqual(lower.Select(a => a.ToUpper()).ToList(), upper.Select(b => b.ToUpper()).ToList());

       // Chaining Assertion を使用した場合のコーディング
       lower.Is( upper, (x, y) => x.ToUpper() == y.ToUpper() );

       // =======================================================================================

       // Assertを使用したコーディング
       CollectionAssert.AreEqual(lower, upper, StringComparer.OrdinalIgnoreCase);   // 大文字小文字区別なしで比較

       // Chaining Assertion を使用した場合のコーディング
       lower.SequenceEqual(upper, StringComparer.OrdinalIgnoreCase).Is(true);   // 大文字小文字区別なしで比較

       // #######################################################################################

       // ■ クラスに内の全フィールド、プロパティの比較判定

       var mc1 = new MyClass() { IntProp = 10, StrField = "foo" };
       var mc2 = new MyClass() { IntProp = 10, StrField = "foo" };

       // =======================================================================================

       // Assertを使用したコーディング
       AssertEx.IsStructuralEqual(mc1, mc2);

       // Chaining Assertion を使用した場合のコーディング
       mc1.IsStructuralEqual(mc2);

       // =======================================================================================

       mc1.IntProp = 20;

       // Assertを使用したコーディング
       AssertEx.IsNotStructuralEqual(mc1, mc2);

       // Chaining Assertion を使用した場合のコーディング
       mc1.IsNotStructuralEqual(mc2);

       // #######################################################################################

       // ■ クラス内のprivateスコープのフィールド、プロパティの比較判定

       var actual = new PrivateMock().AsDynamic().PrivateField as string;   // PrivateFieldは実行時に解決されるため明示的に型指定しておく必要がある。

       // Assertを使用したコーディング
       Assert.AreEqual("homuhomu", actual);
       
       // Chaining Assertion を使用した場合のコーディング
       actual.Is( "homuhomu" );

       // =======================================================================================

       // Assertを使用したコーディング
       Assert.AreEqual("homuhomuhomu", new PrivateMock().AsDynamic().PrivateMethod(3) as string);
       
       // Chaining Assertion を使用した場合のコーディング
       (new PrivateMock().AsDynamic().PrivateMethod(3) as string).Is("homuhomuhomu");

       // =======================================================================================

       var mock = new PrivateMock().AsDynamic();
       mock.PrivateField = "mogumogu";
       
       // Assertを使用したコーディング
       Assert.AreEqual("mogumogu", mock.privateField as string);
       
       // Chaining Assertion を使用した場合のコーディング
       (mock.privateField as string).Is("mogumogu");

       // #######################################################################################

       // ■ ラムダ式のエラーメッセージ

       var person = new Person { Age = 50, FamilyName = "Yamamoto", GivenName = "Tasuke" };
       
       person.Is(p => p.Age <= 10 && p.FamilyName == "Yamada" && p.GivenName == "Tarou");
       
       追加情報:Assert.IsTrue に失敗しました。
       p = UnitTestProject2.Person
       Age = 50, FamilyName = Yamamoto, GivenName = Tasuke
       p => (((p.Age <= 10) AndAlso (p.FamilyName == "Yamada")) AndAlso (p.GivenName == "Tarou"))

       // #######################################################################################

       // ■ 例外が発生するかの判定。
       
       // ジェネリックに指定した型に対応する例外が発生した場合成功する。
       AssertEx.Throws<ArgumentNullException>( () => "foo".StartsWith( null ) );
       
       // 派生元の例外( 例えば Exception )も判定の対象となる。
       AssertEx.Catch<Exception>( () => "foo".StartsWith( null ) );
       
       // AssertEx.ThrowsContractException( () => /* contract method よくわからん */ );

       var ex = AssertEx.Throws<InvalidOperationException>(() =>
       {
          // 例外を意図的に発生させる
          throw new InvalidOperationException("foobar operation");
       });
       
       // 例外のメッセージの内容を Is() メソッドを使用してチェックしている
       ex.Message.Is(s => s.Contains("foobar"));

       // 例外が発生しない場合成功する
       AssertEx.DoesNotThrow(() =>
       {
          int a = 0, b = 1;
          a = a / b;
       });

    }
}

// ■TestCase属性によりテストデータを指定する

[TestClass]
public class UnitTest1
{
    // よくわからんけどこう書かなければならないってことで
    public TestContext TestContext { get; set; }

    [TestMethod]
    // テストデータを指定
    [TestCase(1, 2, 3 )]
    [TestCase(10, 20, 30)]
    [TestCase(100, 200, 300)]
    public void TestMethod1()
    {
        TestContext.Run((int x, int y, int z) =>
        {
            (x + y).Is(z);
            (x + y + z).Is(i => i < 1000);
        });
    }
}

// ■TestCaseSource属性によりテストデータが格納されているオブジェクト名を指定する

[TestClass]
public class UnitTest1
{
    // よくわからんけどこう書かなければならないってことで
    public TestContext TestContext { get; set; }
    
    public static object[] toaruSource = new[]
    {
        new object[] {1, 1, "11"},
        new object[] {5, 3, "53"},
        new object[] {9, 4, "94"}
    };

    [TestMethod]
    // テストデータが格納されているオブジェクト名を指定
    [TestCaseSource("toaruSource")]
    public void TestMethod1()
    {
        TestContext.Run((int x, int y, string z) =>
        {
            // 2つの文字列を連結して比較する
            string.Concat(x, y).Is(z);
        });
    }
}

Chaining Assertion

StyleCop
NuGetでインストールできるライブラリの一つ。デバック時に使用する。規約にのっとってコーディングされているかをデバッグ時にチェックでき、
違反している場合はワーニングエラーが表示される。
1.NuGetから「StyleCop.MSBuild」をインストールする。
2.デバックする。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace ConsoleApplication11
{
    class Program
    {
        static void Main( string[] args)
        {
        }
    }
}

// 上記のソースをデバックすると大量にワーニングエラーが出力されるので修正する

チェック項目はツールを使用してオンオフすることができる。

まず下記のフォルダを開く。
  プロジェクトを作成したフォルダ\packages\StyleCop.MSBuild.4.7.49.0\tools

StyleCopの設定情報が記載されている「Settings.StyleCop」ファイルを同フォルダ内にある「StyleCopSettingsEditor.exe」にドラックする。


StyleCopの導入と使用

AutoMapper
O/Rマッパー。コピー元とコピー先のモデルでフィールド、プロパティ名が同じものについてコピーを行うもの。
インストールはNuGetから「AutoMapper」をインストールするだけである。
using AutoMapper;
using System;

namespace ConsoleApplication12
{
   class Model1
   {
      public int? a { get; set; }
      public string b { get; set; }
      public float c { get; set; }
      public string d { get; set; }
      public int e { get; set; }
   }

   class Model2
   {
      public int a { get; set; }
      public string b { get; set; }
      public string c { get; set; }
      public double d { get; set; }
      public int e2 { get; set; }
   }
   
   class Program
   {
      static void Main(string[] args)
      {
         var m1 = new Model1() { a = null, b = "aaa", c = 1.1f, d = "a", e = 5 };
         var m2 = new Model2();

         var conf = new MapperConfiguration( cfg => {
             cfg.CreateMap<Model1, Model2>();   // 第1引数はコピー元、第2引数はコピー先を指定する
         });

         var map = conf.CreateMapper();

         // マッピング
         map.Map(m1, m2);
      }
   }
}

// a = 0      コピー元の変数 が null で、コピー先の変数が null 許容してない場合でもエラーにならない。
// b = "aaa"  単純コピー。
// c = "1.1"  コピー先が文字列の場合、文字列に変換してコピーされる。
// d          型変換できない場合は実行時エラーになる。
// e2 = 0     コピー元に変数 e2 が存在しないためコピーされない。

using AutoMapper;
using System;

namespace ConsoleApplication12
{
   class Model1
   {
      public int? a { get; set; }
      public string b { get; set; }
      public float c { get; set; }
      public int e { get; set; }
   }

   class Model2
   {
      public int a { get; set; }
      public string b { get; set; }
      public string c { get; set; }
      public int e2 { get; set; }
   }

   class Program
   {
      static void Main(string[] args)
      {
         var m1 = new Model1() { a = null, b = "aaa", c = 1.1f, e = 5 };
         var m2 = new Model2();

         var conf = new MapperConfiguration(cfg =>
         {
            cfg.CreateMap<Model1, Model2>()
               .BeforeMap((src, dst) => src.e += 100)          // コピー元マップのマッピング方式のカスタマイズ
               .AfterMap((src, dst) => dst.e2 = src.e + 10);   // コピー先マップのマッピング方式のカスタマイズ
         });

         var map = conf.CreateMapper();

         // マッピング
         map.Map(m1, m2);
      }
   }
}
// コピー元マップの変数 e は 105
// コピー先マップの変数 e2 は 115

AutoMapper


■暗号化と復号化

暗号化と復号化
AESと呼ばれる、共通鍵暗号アルゴリズムによる暗号化と復号化。

using System.Diagnostics;
using System.Security.Cryptography;
using System.Text;

namespace ConsoleApplication1 {
   class Program {
      static void Main(string[] args) {
         // 暗号化するメッセージ
         string plainText = "これは私の超秘密データです。";

         // 暗号化されたメッセージ
         byte[] cipherText;

         // 暗号化キー
         byte[] key;

         // 初期化ベクトル
         byte[] initializationVector;

         // Aes暗号化
         using (Aes aes = Aes.Create())
         {
            // 暗号化キーを取得。復号時に使用する。
            // 暗号化キーを使いまわさないようにすることが大事。
            key = aes.Key;

            // 初期化ベクトルを取得。復号時に使用する。
            initializationVector = aes.IV;

            // Aes暗号化オブジェクトを作成
            ICryptoTransform encryptor = aes.CreateEncryptor();

            byte[] plainBytes = Encoding.UTF8.GetBytes(plainText);

            // Aes暗号化
            cipherText = encryptor.TransformFinalBlock(plainBytes, 0, plainBytes.Length);
         }

         Debug.WriteLine("暗号化するメッセージ: " + plainText);
         DumpBytes("Key: ", key);
         DumpBytes("Initialization Vector: ", initializationVector);
         DumpBytes("Encrypted: ", cipherText);

         // Aes復号化
         using (Aes aes = Aes.Create())
         {
            // Aes復号化オブジェクトを作成
            ICryptoTransform decryptor = aes.CreateDecryptor(key, initializationVector);

            // Aes復号化
            byte[] decBytes = decryptor.TransformFinalBlock(cipherText, 0, cipherText.Length);

            plainText = Encoding.UTF8.GetString(decBytes);
         }

         Debug.WriteLine("復号化したメッセージ: " + plainText);

      }

      static void DumpBytes(string title, byte[] bytes) {
         Debug.Write(title);
         foreach (byte b in bytes)
         {
            Debug.Write(string.Format("[{0:X}]", b));
         }
         Debug.WriteLine("");
      }
   }
}

RSA アルゴリズムを使用して非対称暗号化および復号化を行う。
公開鍵による暗号化、および秘密鍵による復号化を行う。

using System.Diagnostics;
using System.Security.Cryptography;
using System.Text;

namespace ConsoleApplication1 {
   class Program {
      static void Main(string[] args) {
         // 暗号化するメッセージ
         string plainText = "これは私の超秘密データです。";

         UTF8Encoding converter = new UTF8Encoding();
         
         byte[] plainBytes = converter.GetBytes(plainText);

         DumpBytes("Plain bytes: ", plainBytes);

         // 暗号化したデータ
         byte[] encryptedBytes;

         // 復号化したデータ
         byte[] decryptedBytes;

         // 公開キーのみ
         string publicKey;

         // 公開キーと秘密キー
         string privateKey;

         // RSAアルゴリズムで暗号化
         using (RSACryptoServiceProvider rsaEncrypt = new RSACryptoServiceProvider())
         {
            // 公開キーのみ取得
            publicKey = rsaEncrypt.ToXmlString(includePrivateParameters: false);

            // 公開キーと秘密キーを取得
            privateKey = rsaEncrypt.ToXmlString(includePrivateParameters: true);

            // RSAアルゴリズムで暗号化
            encryptedBytes = rsaEncrypt.Encrypt(plainBytes, fOAEP: false);
         }

         Debug.WriteLine(string.Format("Public key: {0}", publicKey));
         Debug.WriteLine(string.Format("Private key: {0}", privateKey));
         DumpBytes("Encrypted bytes: ", encryptedBytes);

         // RSAアルゴリズムで復号化
         using (RSACryptoServiceProvider rsaDecrypt = new RSACryptoServiceProvider())
         {
            // 復号化には秘密鍵が必要
            rsaDecrypt.FromXmlString(privateKey);

            decryptedBytes = rsaDecrypt.Decrypt(encryptedBytes, fOAEP: false);
         }

         DumpBytes("Decrypted bytes: ", decryptedBytes);
         Debug.WriteLine(string.Format("Decrypted string: {0}", converter.GetString(decryptedBytes)));

         // 公開キーを使用して暗号化
         using (RSACryptoServiceProvider rsaEncrypt = new RSACryptoServiceProvider())
         {
            // 公開キーを設定
            rsaEncrypt.FromXmlString(publicKey);

            // RSAアルゴリズムで暗号化
            encryptedBytes = rsaEncrypt.Encrypt(plainBytes, fOAEP: false);
         }

         DumpBytes("Encrypted bytes: ", encryptedBytes);
      }

      static void DumpBytes(string title, byte[] bytes) {
         Debug.Write(title);
         foreach (byte b in bytes)
         {
            Debug.Write(string.Format("[{0:X}]", b));
         }
         Debug.WriteLine("");
      }
   }
}


// RSA キーを管理する using System.Diagnostics; using System.Security.Cryptography; using System.Text; namespace ConsoleApplication1 { class Program { static void Main(string[] args) { // 暗号化するメッセージ string plainText = "これは私の超秘密データです。"; UTF8Encoding converter = new UTF8Encoding(); byte[] plainBytes = converter.GetBytes(plainText); DumpBytes("Plain bytes: ", plainBytes); // 暗号化したデータ byte[] encryptedBytes; // 復号化したデータ byte[] decryptedBytes; // キー名 const string containerName = "MyKeyStore"; // キー情報は %AppData%\Roaming\Microsoft\Crypto\RSAフォルダ内に保存されるらしい。 CspParameters csp = new CspParameters(); csp.KeyContainerName = containerName; // CspParametersを使用してRSAアルゴリズムで暗号化 using (RSACryptoServiceProvider rsaEncrypt = new RSACryptoServiceProvider(csp)) { // RSAアルゴリズムで暗号化 encryptedBytes = rsaEncrypt.Encrypt(plainBytes, fOAEP: false); } DumpBytes("Encrypted bytes: ", encryptedBytes); // CspParametersを使用してRSAアルゴリズムで復号化 using (RSACryptoServiceProvider rsaDecrypt = new RSACryptoServiceProvider(csp)) { decryptedBytes = rsaDecrypt.Decrypt(encryptedBytes, fOAEP: false); } DumpBytes("Decrypted bytes: ", decryptedBytes); Debug.WriteLine(string.Format("Decrypted string: {0}", converter.GetString(decryptedBytes))); } static void DumpBytes(string title, byte[] bytes) { Debug.Write(title); foreach (byte b in bytes) { Debug.Write(string.Format("[{0:X}]", b)); } Debug.WriteLine(""); } } }
// デジタル署名 using System.Diagnostics; using System.Security.Cryptography; using System.Text; namespace ConsoleApplication1 { class Program { static void Main(string[] args) { // 暗号化するメッセージ string plainText = "これは私の超秘密データです。"; UTF8Encoding converter = new UTF8Encoding(); byte[] plainBytes = converter.GetBytes(plainText); DumpBytes("Plain bytes: ", plainBytes); byte[] signBytes; HashAlgorithm sha = SHA256.Create(); // SHA256ハッシュ値を計算 byte[] hash = sha.ComputeHash(plainBytes); DumpBytes("hash bytes: ", hash); const string containerName = "MyKeyStore"; CspParameters csp = new CspParameters(); csp.KeyContainerName = containerName; // 秘密キーで暗号化して署名を計算する using (RSACryptoServiceProvider rsaEncrypt = new RSACryptoServiceProvider(csp)) { // 秘密キーで暗号化して署名を計算する signBytes = rsaEncrypt.SignHash(hash, CryptoConfig.MapNameToOID("SHA256")); } DumpBytes("Signed bytes: ", signBytes); // デジタル署名が正しいかをチェックする using (RSACryptoServiceProvider rsaDecrypt = new RSACryptoServiceProvider(csp)) { // デジタル署名が正しいかをチェックする bool validSignature = rsaDecrypt.VerifyHash(hash, CryptoConfig.MapNameToOID("SHA256"), signBytes); if (!validSignature) { Debug.WriteLine("デジタル署名が不正です!!"); return; } else { Debug.WriteLine("デジタル署名が正しいです"); } } } static void DumpBytes(string title, byte[] bytes) { Debug.Write(title); foreach (byte b in bytes) { Debug.Write(string.Format("[{0:X}]", b)); } Debug.WriteLine(""); } } }


■処理時間を計測する

処理時間を計測する
処理時間を計測する。

using System;
using System.Diagnostics;
using System.Threading;

namespace ConsoleApplication1 {
   class Program {
      static void Main(string[] args) {
         var sw = new Stopwatch();

         sw.Start();

         Thread.Sleep(5005);

         sw.Stop();

         Debug.WriteLine(new TimeSpan(0, 0, 0, 0, (int)sw.ElapsedMilliseconds));
         
         // 00:00:05.0060000
      }
   }
}


■パフォーマンスカウンターを使用する

パフォーマンスカウンターを使用する
パフォーマンスカウンターを使用する。
既定のCategory名およびCounter名はパフォーマンスモニターで確認できる

using System.Diagnostics;
using System.Threading;

namespace ConsoleApp1 {
   class Program {
      static void Main(string[] args) {
         var categoryName = "Processor Information";
         var counterName = "% Processor Time";   // CPUの使用率

         //カテゴリが存在するか確かめる
         if (!System.Diagnostics.PerformanceCounterCategory.Exists(categoryName))
         {
            Debug.WriteLine("登録されていないカテゴリです。");
            return;
         }

         //カウンタが存在するか確かめる
         if (!System.Diagnostics.PerformanceCounterCategory.CounterExists(counterName, categoryName))
         {
            Debug.WriteLine("登録されていないカウンタです。");
            return;
         }

         PerformanceCounter processor = new PerformanceCounter(
                        categoryName: categoryName,
                        counterName: counterName,
                        instanceName: "_Total");

         var count = 10;
         while (count >= 0)
         {
            Debug.WriteLine("{0} : Processor time {1}", count, processor.NextValue());
            Thread.Sleep(500);
            count--;
         }

      }

   }
}


■ファイルアップロード

HTTPプロトコルを使用してファイルアップロード
HTTPプロトコルを使用してファイルをWebAPIにアップロードする。
WebClient.UploadFileメソッドを使用すると、手軽に実装できるがタイムアウト時間を指定できない。



// クライアント側

using System;
using System.Collections.Generic;
using System.IO;
using System.Net;
using System.Text;
using System.Web.Script.Serialization;
using System.Windows.Forms;

namespace WindowsFormsApp1 {
   public partial class Form1 : Form {
      public Form1() {
         InitializeComponent();
      }

      private void Form1_Load(object sender, EventArgs e) {
         UploadFile("http://localhost:52967/Home/FileUpload?token=abc", 5000, @"D:\aaa.csv");
      }
      
      private void UploadFile(string url, int timeOut, string filePath)
      {
         // 送信するファイル名を取得
         string fileName = Path.GetFileName(filePath);
         
         // 文字コード
         Encoding enc = Encoding.UTF8;
         
         // 区切り文字列
         // 詳しくはMDN参照
         string boundary = "aBoundaryString";

         HttpWebRequest req = (HttpWebRequest)WebRequest.Create(url);
         req.Method = "POST";
         
         // ファイルアップロード( GetResponseメソッド呼び出し )時のタイムアウト時間を設定
         req.Timeout = timeOut;
         
         // メディアタイプと区切り文字を設定。multipart/form-dataはHTML フォームの内容をサーバーに送信するときに使用する。
         // 詳しくはMDB参照
         req.ContentType = "multipart/form-data; boundary=" + boundary;

         var ContentType = "";
         var extension = Path.GetExtension(filePath).ToLower();
         // 送信するファイルのメディアタイプを取得。
         switch (extension)
         {
         case ".csv":
            ContentType = "text/csv";
            break;
            
         default:
            throw new Exception("ファイルアップロードできません。未対応な拡張子のファイルです。filePath:" + filePath);
         }
         
         string postData = "";
         
         // レスポンスヘッダーを作成
         postData = "--" + boundary + "\r\n" +   // 区切り文字
             "Content-Disposition: form-data;" +   // レスポンスヘッダー。詳しくはMDB参照
             "name=\"uploadFile\";" +     // htmlフィールドの名称。つまりWebAPI側でアップロードされたファイルを受け取る引数名となる。
             "filename=\"" + fileName + "\"" + ";"+
             "Content-Type=" + ContentType + ";" +
             "charset=utf-8;" +
             "\r\n\r\n";

         // レスポンスヘッダーをバイト型配列に変換
         byte[] startData = enc.GetBytes(postData);

         // 区切り文字
         postData = "\r\n--" + boundary + "--\r\n";
         
         // 区切り文字をバイト型配列に変換
         byte[] endData = enc.GetBytes(postData);

         // 送信するファイルを開く
         using (FileStream fs = new FileStream(filePath, System.IO.FileMode.Open, FileAccess.Read))
         {
            // POST送信するデータの長さを設定
            req.ContentLength = startData.Length + endData.Length + fs.Length;

            using (Stream reqStream = req.GetRequestStream())
            {
               // レスポンスヘッダーを送信するデータに書き込む
               reqStream.Write(startData, 0, startData.Length);
               
               //ファイルの内容を送信
               byte[] readData = new byte[0x1000];
               int readSize = 0;
               while (true)
               {
                  // 送信するファイルから0x1000サイズ分だけ読み込み。
                  readSize = fs.Read(readData, 0, readData.Length);
                  if (readSize == 0)
                  {
                     // 読み込むデータがないので終了
                     break;
                  }
                  
                  // 読み込んだデータ分だけ送信するデータに書き込む
                  reqStream.Write(readData, 0, readSize);
               }
               
               // 区切り文字を送信するデータに書き込む
               reqStream.Write(endData, 0, endData.Length);
            }
         }

         // サーバーからの応答を受信するためのWebResponseを取得
         HttpWebResponse res = (HttpWebResponse)req.GetResponse();
         
         // 応答データを受信するためのStreamを取得
         using(Stream resStream = res.GetResponseStream())
         {
            var jsonResult = new Dictionary<string, string>();

            using (StreamReader sr = new StreamReader(resStream, enc))
            {
               // 受信データを読み込む
               var result = sr.ReadToEnd();

               // System.Web.Extensionsの参照の追加が必要
               var java = new JavaScriptSerializer();

               // jsonデータをDictionaryにデシリアライズ
               jsonResult = java.Deserialize<Dictionary<string, string>>(result);

               if (jsonResult["status"] != "OK")
               {
                  throw new Exception("ファイルアップロードできません。サーバーエラーです。status:" + jsonResult["status"]);
               }
            }
         }
      }
   }
}


// WebAPI側 using System.IO; using System.Web; using System.Web.Mvc; namespace WebApplication6.Controllers { public class HomeController : Controller { public ActionResult Index() { return View(); } [HttpPost] public ActionResult FileUpload(string token, HttpPostedFileWrapper uploadFile) { if (uploadFile != null) { uploadFile.SaveAs(@"d:\" + Path.GetFileName(uploadFile.FileName)); } return Json(new { status = "OK" }, JsonRequestBehavior.DenyGet); } } }

FTPを使用してファイルアップロード
FTPを使用してファイルアップロードする。


// クライアント側

using System;
using System.Diagnostics;
using System.IO;
using System.Net;
using System.Windows.Forms;

namespace WindowsFormsApp1 {
   public partial class Form1 : Form {
      public Form1() {
         InitializeComponent();
      }

      private void Form1_Load(object sender, EventArgs e) {
         UploadFile("ftp://localhost/test.txt", "userName", "password", 1000, @"c:\test.txt");
      }
      
      private void UploadFile(string url, string userName, string password, int timeOut, string filePath) {
         // アップロード先のURI
         Uri u = new Uri(url);

         FtpWebRequest ftpReq = (FtpWebRequest)WebRequest.Create(u);
 
         // ログインユーザー名とパスワードを設定
         ftpReq.Credentials = new NetworkCredential(userName, password);

         // MethodにWebRequestMethods.Ftp.UploadFile("STOR")を設定
         ftpReq.Method = WebRequestMethods.Ftp.UploadFile;

         // 要求の完了後に接続を閉じる
         ftpReq.KeepAlive = false;
         
         // バイナリモードで転送する
         ftpReq.UseBinary = true;
         
         // PASVモードを有効にする
         ftpReq.UsePassive = true;

         ftpReq.Timeout = timeOut;

         using (Stream reqStrm = ftpReq.GetRequestStream())
         {
            using (FileStream fs = new FileStream(filePath, FileMode.Open, FileAccess.Read))
            {
               byte[] buffer = new byte[1024];
               while (true)
               {
                  int readSize = fs.Read(buffer, 0, buffer.Length);
                  if (readSize == 0)
                  {
                     break;
                  }
                  reqStrm.Write(buffer, 0, readSize);
               }
            }
         }

         using (FtpWebResponse ftpRes = (FtpWebResponse)ftpReq.GetResponse())
         {
            Debug.WriteLine(ftpRes.StatusCode.ToString() + ":" + ftpRes.StatusDescription);
         }
      }
   }
}


■ZIP圧縮

ZIP圧縮
ZIP圧縮
using System;
using System.IO;
using System.IO.Compression;
using System.Windows.Forms;

namespace WindowsFormsApp7 {
   public partial class Form1 : Form {
      public Form1() {
         InitializeComponent();
      }

      private void Form1_Load(object sender, EventArgs e) {
         ZipCompress(@"d:\target", @"d:\result.zip");
      }

      /// <summary>
      /// ZIP圧縮する
      /// System.IO.Compression.FileSystem の参照追加が必要
      /// </summary>
      /// <param name="targetPath">圧縮対象のディレクトリ</param>
      /// <param name="zipPath">圧縮先のディレクトリ</param>
      private void ZipCompress(string targetPath, string zipPath) {
         if (Directory.Exists(targetPath))
         {
            if (File.Exists(zipPath))
            {
               File.Delete(zipPath);
            }

            ZipFile.CreateFromDirectory(targetPath, zipPath, CompressionLevel.Optimal, false);
         }
         else
         {
            throw new Exception("圧縮対象のディレクトリが存在しません。");
         }
      }
   }
}


■アセンブリ

GAC
同一端末上で実行される複数のアプリケーションが、同一のアセンブリを使用する場合、GAC( グローバル アセンブリ キャッシュ )を使用することで
使用するアセンブリを共有することができる。
特定のアプリケーションでのみ使用する場合などは GAC に登録するのではなく、アプリケーション ディレクトリへの配置を検討すること。
GAC とアプリケーション ディレクトリとの優先順位は GAC のほうが高い。

.NET Framework 4 以降では、GAC の既定の場所は %windir%\Microsoft.NET\assembly となる。


●アセンブリを GAC へ登録し、登録したアセンブリをアプリケーションで使用するまでの手順を示す。

1.Visual Studio 上で新規プロジェクトのクラスライブラリを選択し、dll ( ここではClassLibrary.dll )を作成する。
2.Visual Studio 上でアセンブリに署名する。(GACに登録するにはアセンブリの署名が必要)
   
3.スタートメニューを表示し、 Developer Command Prompt VS 2017 を管理者権限で実行する。( Visual Studio 2017 の場合 )
4.下記のコマンドを実行して GAC にインストールする。パスおよびdll名は適宜読み替えること。
   gacutil /i "C:\ClassLibrary.dll" 

  下記のフォルダにClassLibrary.dllがコピーされる。
   C:\Windows\Microsoft.NET\assembly\GAC_MSIL\ClassLibrary
5.Visual Studio 上で新規プロジェクトを作成する。
6.GAC_MSILフォルダ内にコピーされたClassLibrary.dllをパス指定で参照追加する。リストに表示されるようにするにはレジストリ登録が必要らしい。


●GACからアンインストールするには下記のコマンドを実行する。
   gacutil /u "ClassLibrary"
MSDN

アセンブリのバージョン管理
GAC にアセンブリを登録する場合、アセンブリのバージョン管理が重要となる。
同じアセンブリのさまざまなバージョンに複数のアプリがアクセスする場合、アプリごとにどのバージョンのアセンブリを使用するかを明確にする必要がある。
これを行うためにアセンブリのバージョン管理を行う。
バージョン設定は Visual Studio の下記の画面で行う。

   

バージョンが異なるアセンブリを GAC に登録すると、バージョンごとにフォルダが作成され、区別されるようになる。

   

あとは参照の追加で必要なバージョンのアセンブリを追加すればよい。

アセンブリ署名
アセンブリ署名は、アセンブリに署名するで設定する。
   

アセンブリ署名によるメリットは下記のとおりとなる。

1.逆アセンブルにより不正改造されたアセンブリを使用できなくさせることができる。
  不正改造したアセンブリを使用すると下記のような例外が発生する。
   

2.GACに登録することができるようになる

3.アセンブリとそれに付属する証明書ファイルを使用してアセンブリの作成者が本人であることの確認ができる。
   パスワード付きでアセンブリに署名すると、Visual Studio のプロジェクト上にpfxという拡張子のファイルが作成される。
  このファイルは、秘密鍵をパスワードで保護しつつ証明書と一緒に格納できる証明書ファイルの一種である。
  つまりアセンブリと証明書ファイルをセットで配布することで、アセンブリの使用者がそのアセンブリが
  間違いなく本人によって作成されたものであることを確認できるようになる。
  (ということだと思う。よくわかってないが。。。)


MSDN

アプリケーション ディレクトリ内のアセンブリをフォルダ別に管理する
アプリケーション ディレクトリ内でアセンブリを参照できるパスはルートフォルダのみとなる。
しかしアセンブリの管理上、サブフォルダに置きたい場合もあると思う。
その場合は、configファイルのcodeBase要素でパスを指定することで対応できる。

以下にサンプルを示す。これをconfigファイルに追加して適宜修正すること。

   <runtime>
      <assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
         <dependentAssembly>
            <assemblyIdentity name="ClassLibrary"
                              <!-- publicKeyToken は Developer Command Prompt VS 2017 で「sn -T "D:\ClassLibrary.dll"」 コマンドを実行して調べることができる -->
                              publicKeyToken="c0604684ddd4225f"
                              culture="neutral" />
            <codeBase version="2.0.0.0" href="D:\dll\ClassLibrary.dll"/>
         </dependentAssembly>
      </assemblyBinding>
   </runtime>
アセンブリに署名していない場合は設定を省略できるらしいが、
事故防止の意味でもアセンブリに署名したうえで正しくconfigファイルに設定するのが望ましいと思う。

また GAC に登録している場合は、GAC の設定が優先される。

逆アセンブル
逆アセンブルし、コードを書き換える方法を示す。

1.スタートメニューを表示し、 Developer Command Prompt VS 2017 を実行する。( Visual Studio 2017 の場合 )
2.下記のコマンドを実行する。
   ildasm
3.起動した IL 逆アセンブラー ツール上で逆アセンブルするアセンブリを選択する。
   
4.ファイルメニューのダンプをクリックして、ilファイルを出力する。
5.4で出力したファイルをテキストエディタで開いて編集して保存する。
6.Developer Command Prompt VS 2017 上で下記のコマンドを実行する。
   ilasm "D:\ClassLibrary.il" /dll
7.6で作成したアセンブリをIL 逆アセンブラー ツールで読み込むと、編集されていることが確認できるはず。


Top
inserted by FC2 system