C#のテンプレート

良く使うコード、たまにしか使わないので忘れてしまうコード等を書き留めます。
### １．構造体をListにする (配列[]は使えません)
構造体を配列にするには、Listを使うしかないです。
```C#:C#
        private struct structAAA 
        {
            public int x1;      //publicにしないと同じクラスからアクセスできないみたい。
            public int x2;
            public int x3;
            public int x4;
            public string text;  //文字列も含めちゃいましょ。

            public structAAA(int c1, int c2, int c3, int c4, string str1)   //コンストラクタ
            {
                this.x1 = c1;
                this.x2 = c2;
                this.x3 = c3;
                this.x4 = c4;
                this.text = str1;
            }
        }

        //ここでList宣言！
        private List<structAAA> sA = new List<structAAA>();

        //要素追加
       sA.Add(new structAAA(10, 20, 30, 40 , "こんにちは"));   //次々にAddしてください…

        //取り出し
        for (int i =  0; i < sA.Count; i++)
        {
            int tmpInt = structAAA[i].x1
            //こんな感じで…
        }

```
### ２．配列を動的に増やす
但し、Array.Resizeできるのは1次元配列のみ。
```C#:C#
    string[] 顧客 = new string[0];   //空の配列を作る。

    for(int i= 0; i < 9; i++)
    {
        Array.Resize(ref 顧客, i + 1);  //配列のリサイズ。既存値はそのまま。リサイズ直後の値はNull(のようだ)。
    	顧客[i] = i;
    	Console.WriteLine(顧客[i]);
    }

    Console.WriteLine("要素数: "+ 顧客.length);
```
### ３．コントロールを静的配列にする
静的なら、これが一番簡単かな？
```C#:C#
       //テキストボックスの配列を準備する。
        Control[] textBox配列 = new Control[10];

        private void Form_Load(object sender, EventArgs e)
        {
            //テキストボックスを配列にセットする。
            textBox配列[0] = this.textBox1;
            textBox配列[1] = this.textBox2;
            textBox配列[2] = this.textBox3;
            textBox配列[3] = this.textBox4;
            textBox配列[4] = this.textBox5;
            textBox配列[5] = this.textBox6;
            textBox配列[6] = this.textBox7;
            textBox配列[7] = this.textBox8;
            textBox配列[8] = this.textBox9;
            textBox配列[9] = this.textBox10;
        }

        private void XXX()
        {
            for (int i = 0; i < 9; i++)
            {
                textBox配列[i].Text = i.ToString();

            }
        }
```




### ４．DataTableの扱い方
```c#:C#
        //DataTableを空にするためのDataTable (これ便利アル)
        DataTable dt空 = new DataTable();

        //結果をセットする為のDataTable 
        DataTable dt = new DataTable();

		//カラム定義
		dt.Columns.Add("社員番号", typeof(String));
		dt.Columns.Add("氏名", typeof(String));
		dt.Columns.Add("入社日", typeof(DateTime));
		dt.Columns.Add("提案回数", typeof(int)).DefaultValue = 0;   //[重要]初期値を0にする場合
		
		DataRow dr;

		//データセット
		dr = dt.NewRow();

		dr["社員番号"] = "A1234"
		dr["氏名"] = "山田　一郎"
		dr["入社日"] = "2020/01/01"
		dr["提案回数"] = 3

		dt.Rows.Add(dr);  // テーブルに追加

		//読出し
		Console.WriteLine(dt.Rows[0]["社員番号"]);  //A1234
    
        //Select文
        //  引数(where句, order by句)
        //  注意：選択される行が無ければエラーだよ！
        DataTable dt2 = dt.Select(" [氏名] LIKE '%山田%'" , "[入社日] asc").CopyToDataTable();

        //何行Selectされるか知りたい場合はこれを前段に↓
        int rowsCount = dt.Select("・・・", "・・・").Length;
        if (rowsCount <= 0)
        {
              //０行なら処理中断!
              MessageBox.Show("メッセージ");
              return;
        }

        //ループ2種
        foreach (DataRow dr in dt.Rows)
        {
             Console.WriteLine(dr["氏名"]);
        }

        for (int i = 0; i < dt2.Rows.Count; i++)
        {
            Console.WriteLine(dt2.Rows[i]["氏名"]);
        }
        
        //構造とデータをまるっとコピー
        DataTable dt3 = dt.Copy();

        //構造のみコピー
        DataTable dt4 = dt.Clone(); 

        //値のみクリア
        dt.Clear();   //カラム定義は残るよ

        //値もカラム定義も削除。
        dt = dt空.Copy();
```
### ５．Dictionary / SortedDictionary
```c#:C#
            //何処でも書ける定義方法↓
            Dictionary<string, string> dicA = new Dictionary<string, string>();
            
            //↓メソッド内ではこの書き方でも可
            var dicA = new Dictionary<string, string>();

            dicA["0001"] = "リンゴ";
            dicA["0002"] = "みかん";
            
            //Keyを指定し要素を削除する
            bool result = dicA.Remove("0002");

            //全要素削除
            dicA.Clear();
            
            // ループ変数にKeyValuePairを使う
            foreach (KeyValuePair<string, string> kvp in dicA)
            {
                string コード= kvp.Key;
                string 内容  = kvp.Value;
                Console.WriteLine("表示: {0} / {1}", コード, 内容);
            }


            //keyの昇順でソートしてくれる！
            var dicB = new SortedDictionary<int, string>
                {
                    { 3, "ブドウ" },
                    { 2, "イチゴ" },
                    { 1, "かぼちゃ" },
                };

            foreach (var r in dicB)
            {
                Console.WriteLine(r.Key + ": " + r.Value);
            }
```




### ５０．Yes/No確認
```c#:C#
            //Yse/No確認
            DialogResult result = MessageBox.Show("登録しますか？",
                "登録確認",
                MessageBoxButtons.YesNo,
                MessageBoxIcon.Question,
                MessageBoxDefaultButton.Button2);     //Button2-> デフォルト選択ボタン

            //結果確認
            if (result == DialogResult.Yes)
            {
                //処理無し
            }
            else if (result == DialogResult.No)
            {
                return;
            }
```

### ５１．タイマーの１回目を即起動させる

タイマーでIntervalを60000と設定した場合、初回の起動が1分後になる。
初回だけを即実行する方法はこれ。
先に実行して、後はタイマーのInterval毎にまかせる。


```c#:C#
        private void button1_Click(object sender, EventArgs e)
        {
            //初回を実行
            timer1_Tick(sender,e);
            
            //後は設定時間毎に実行
            timer1.Enabled = true;
        }
        
        private void timer1_Tick(object sender, EventArgs e)
        {
                //処理いろいろ
        }
```


### ５２．textBoxに行数制限

Multiline有効にしたtextBoxにログを残す様な場合、新しい行から(下から)指定行残します。
指定行に満たない場合はそのままにします。

```c#:C#　呼出側
        private void button1_Click(object sender, EventArgs e)
        {
            textBox行数制限(textBox1, 100);   //100行残したい
        }
```
```c#:C#　メソッド
        private void textBox行数制限(System.Windows.Forms.TextBox t, int maxRows )
        {
            /**************************************************************************************
             *  MultilineプロパティをtrueにしたtextBoxについて、
             *  下から指定行のみを残す。(下の方が新しい前提)
             *  【注意】改行記号のみの行も1行としてます。
             *   
             *      textBox: 制限対象とするtextBox
             *      maxRows: 下から数えて残す行数
             *************************************************************************************/

            Console.WriteLine("t      : " + t.ToString());
            Console.WriteLine("maxRows: " + maxRows.ToString());

            int rowCount = t.Lines.Length;
            Console.WriteLine("現在の行数: " + rowCount.ToString());

            int remainRow = rowCount - maxRows;     // ex. 4 = 7 - 3

            List<string> oldLines = new List<string>(t.Lines);
            List<string> newLines = new List<string>();

            int i = 0;
            foreach (var line in oldLines)
            {
                Console.WriteLine("　要素:" + i.ToString() + " / " + line.ToString());
                if (i >= remainRow)
                {
                    newLines.Add(line.ToString());
                }
                i = i + 1;
            }

            t.Text = String.Join("\r\n", newLines);       //新List代入
            
            //最下端を表示する。
            t.SelectionStart = t.Text.Length;   //カレット位置を末尾に移動
            t.Focus();                          //テキストボックスにフォーカスを移動
            t.ScrollToCaret();                  //カレット位置までスクロール
        }
```



### ５３．mp3を鳴らすぞ

[設定1]　プロジェクトの参照に、C:\Windows\System32\wmp.dllを追加する
[設定2]　必要に応じ、using WMPLib;　を追記する。


```c#:C# 宣言
        //メディアプレーヤ関連
        WMPLib.WindowsMediaPlayer mediaPlayer = new WMPLib.WindowsMediaPlayer();
```

```c#:C# メソッド
        private void Sound(string 音)
        {
            string url = "";

            switch (音)
            {
                case "ピンポン":
                    url = @".\Sound\効果音_ピンポン.mp3";
                    break;
                    
                case "不正解":
                    url = @".\Sound\効果音_不正解.mp3";
                    break;
                    
                default:
                    return;
            }

            //オーディオファイルを指定する
            mediaPlayer.URL = url;
            //効果音を再生する
            mediaPlayer.controls.play();
            
            //止める場合は、別ボタンに
            //mediaPlayer.controls.stop();

        }
```







### ５４．TabControlでタブ名指定しタブ移動
```c#:C# メソッド
        private void タブ移動(string 移動先タブ名)
        {
            //移動先ページの設定
            string targetPage = 移動先タブ名;
            int targetIndex = -1;
            //Console.WriteLine("tp.TabCount.ToString(): " + tabControl1.TabCount);

            for (int i = 0; i < tabControl1.TabCount; i++)
            {
                if (tabControl1.TabPages[i].Text == targetPage)
                {
                    targetIndex = i;
                }
                //Console.WriteLine("i: " + i.ToString());
                //Console.WriteLine("tp.TabCount.ToString(): " + tabControl1.TabPages[i].Text);
                //Console.WriteLine("");
            }

            //タブ移動
            tabControl1.SelectedIndex = targetIndex;
        }
```
### ５５．プロセス間通信
```c#:C#
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

using System.Runtime.InteropServices;  //プロセス間通信用
using System.Diagnostics;

namespace XXX
{
    class Classプロセス間通信
    {
        /*---------------------------------------------------------------------------------------------------
         *  【機能】
         *  指定したアプリケーションに対し、指定した文字列をプロセス間通信で送信する。
         * 
         * 
         *      【参考】ht tps://note.com/marupeke296/n/n5e126185f628
         * 
         * ------------------------------------------------------------------------------------------------*/

        [DllImport("User32.dll", EntryPoint = "SendMessage")]
        static extern Int32 sendMessage(Int32 hWnd, Int32 Msg, Int32 wParam, ref COPYDATASTRUCT32 lParam);


        //プロセス間通信用構造体
        [StructLayout(LayoutKind.Explicit)]
        struct COPYDATASTRUCT32
        {
            [FieldOffset(0)]
            public UInt32 dwData;
            [FieldOffset(4)]
            public UInt32 cbData;
            [FieldOffset(8)]
            public IntPtr lpData;
        }

        // WM_COPYDATAのメッセージID
        const int WM_COPYDATA = 0x004A;


        public void _Send(string appName, string msg)
        {
            //appName:  指定したアプリケーション
            //msg:      送信すべき文字列

            // 指定のウィンドウを検索
            var windowHandle = new IntPtr(0);
            foreach (var process in Process.GetProcesses())
            {
                //Console.WriteLine(process.MainWindowTitle);
                if (process.MainWindowTitle == appName)
                {
                    windowHandle = process.MainWindowHandle;
                    break;
                }
            }
            if (((int)windowHandle) != 0)
            {
                // 送信データを作成
                string message = msg;

                var cds = new COPYDATASTRUCT32();
                cds.dwData = 0;     // 任意の数値
                cds.lpData = Marshal.StringToHGlobalAnsi(message);  // 文字列をキャスト
                cds.cbData = (uint)message.Length + 1;

                sendMessage((int)windowHandle, WM_COPYDATA, 0, ref cds);

                Marshal.FreeHGlobal(cds.lpData);  // メモリを解放

                Console.WriteLine("送信しました。");
            }
        }
    }
}
```
### ５６．キーボード操作
```c#:C#
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

using System.ComponentModel;

using System.Runtime.InteropServices;

using System.Windows.Forms;

using System.Drawing.Imaging;
using System.Drawing;

namespace XXX
{


    //-----------------------------------------------------------------------------------------------------------------
    class Classスリープ
    {
        //メソッド
        public string _Sleep(int MSec)
        {
            //指定ミリ秒停止する
            System.Threading.Thread.Sleep(MSec);

            Console.WriteLine("スリープ：" + MSec.ToString() + "ミリ秒間停止しました。");

            //エラーは無いと思うので、OKを返す
            return "OK";
        }
    }


    //-----------------------------------------------------------------------------------------------------------------
    class Classマウスポインタ移動
    {
        [DllImport("USER32.dll", CallingConvention = CallingConvention.StdCall)]
        private static extern void SetCursorPos(int X, int Y);

        public string _MovePointerTo(int X, int Y)
        {
            //マウス移動
            SetCursorPos(X, Y);

            //1秒強制停止する
            System.Threading.Thread.Sleep(1000);

            Console.WriteLine("マウスポインタ移動：X⇒{0}, Y⇒{1}へ移動しました。", X.ToString(), Y.ToString());

            //エラーは無いと思うので、OKを返す
            return "OK";
        }
    }


    //-----------------------------------------------------------------------------------------------------------------
    class Classシングルクリック
    {
        [DllImport("USER32.dll", CallingConvention = CallingConvention.StdCall)]
        private static extern void mouse_event(int dwFlags, int dx, int dy, int cButtons, int dwExtraInfo);

        private const int MOUSEEVENTF_LEFTDOWN = 0x2;
        private const int MOUSEEVENTF_LEFTUP = 0x4;

        //メソッド
        public string _SingleClick()
        {
            mouse_event(MOUSEEVENTF_LEFTDOWN, 0, 0, 0, 0);
            mouse_event(MOUSEEVENTF_LEFTUP, 0, 0, 0, 0);

            //1秒強制停止する
            System.Threading.Thread.Sleep(1000);


            Console.WriteLine("シングルクリック: 完了");

            //エラーは無いと思うので、OKを返す
            return "OK";
        }
    }


    //-----------------------------------------------------------------------------------------------------------------
    class Classダブルクリック
    {
        [DllImport("USER32.dll", CallingConvention = CallingConvention.StdCall)]
        static extern void mouse_event(int dwFlags, int dx, int dy, int cButtons, int dwExtraInfo);

        private const int MOUSEEVENTF_LEFTDOWN = 0x2;
        private const int MOUSEEVENTF_LEFTUP = 0x4;

        //メソッド
        public string _DoubleClick()
        {
            mouse_event(MOUSEEVENTF_LEFTDOWN, 0, 0, 0, 0);
            mouse_event(MOUSEEVENTF_LEFTUP, 0, 0, 0, 0);

            mouse_event(MOUSEEVENTF_LEFTDOWN, 0, 0, 0, 0);
            mouse_event(MOUSEEVENTF_LEFTUP, 0, 0, 0, 0);

            //1秒強制停止する
            System.Threading.Thread.Sleep(1000);

            Console.WriteLine("ダブルクリック: 完了");

            //エラーは無いと思うので、OKを返す
            return "OK";
        }
    }


    //-----------------------------------------------------------------------------------------------------------------
    class ClassドラッグToモニタTop
    {
        [DllImport("USER32.dll", CallingConvention = CallingConvention.StdCall)]
        static extern void mouse_event(int dwFlags, int dx, int dy, int cButtons, int dwExtraInfo);

        private const int MOUSEEVENTF_MOVE = 0x1;           // マウスを移動する
        private const int MOUSEEVENTF_ABSOLUTE = 0x8000;    // 絶対座標指定
        private const int MOUSEEVENTF_LEFTDOWN = 0x2;
        private const int MOUSEEVENTF_LEFTUP = 0x4;

        //メソッド
        public string _DrugToMonitorTop()
        {
            //マウスダウン
            mouse_event(MOUSEEVENTF_LEFTDOWN, 0, 0, 0, 0);

            System.Threading.Thread.Sleep(300); //要否不明だが…

            //マウスムーブ
            //int x = 400 * (65535 / Screen.PrimaryScreen.Bounds.Width);   //
            int x =Cursor.Position.X * (65535 / Screen.PrimaryScreen.Bounds.Width); 
            int y = 0;
            mouse_event(MOUSEEVENTF_MOVE| MOUSEEVENTF_ABSOLUTE, x, y, 0, 0) ; 

            System.Threading.Thread.Sleep(300); //要否不明だが…

            //マウスアップ
            mouse_event(MOUSEEVENTF_LEFTUP, 0, 0, 0, 0);

            //1秒強制停止する
            System.Threading.Thread.Sleep(1000);

            Console.WriteLine("ドラッグToモニタTop: 完了");

            //エラーは無いと思うので、OKを返す
            return "OK";
        }
    }

    //-----------------------------------------------------------------------------------------------------------------
    class ClassドラッグDoun20
    {   
        //ドラッグして20ピクセル下方に下げる。

        [DllImport("USER32.dll", CallingConvention = CallingConvention.StdCall)]
        static extern void mouse_event(int dwFlags, int dx, int dy, int cButtons, int dwExtraInfo);

        private const int MOUSEEVENTF_MOVE = 0x1;           // マウスを移動する
        private const int MOUSEEVENTF_ABSOLUTE = 0x8000;    // 絶対座標指定
        private const int MOUSEEVENTF_LEFTDOWN = 0x2;
        private const int MOUSEEVENTF_LEFTUP = 0x4;

        //メソッド
        public string _DrugDown20()
        {
            Console.WriteLine("------BEFORE---------");
            Console.WriteLine("Cursor.Position.X:" + Cursor.Position.X.ToString());
            Console.WriteLine("Cursor.Position.Y:" + Cursor.Position.Y.ToString());

            int initX = Cursor.Position.X;

            //マウスダウン
            mouse_event(MOUSEEVENTF_LEFTDOWN, 0, 0, 0, 0);

            System.Threading.Thread.Sleep(300); //要否不明だが…

            //マウスムーブ
            //int x = 400 * (65535 / Screen.PrimaryScreen.Bounds.Width);   //
            int x = (initX+4) * (65535 / Screen.PrimaryScreen.Bounds.Width);    //+4は誤差修正斜め矢印の場合だけ？
            int y = 40 * (65535 / Screen.PrimaryScreen.Bounds.Width);
            mouse_event(MOUSEEVENTF_MOVE | MOUSEEVENTF_ABSOLUTE, x, y, 0, 0);

            System.Threading.Thread.Sleep(300); //要否不明だが…

            //マウスアップ
            mouse_event(MOUSEEVENTF_LEFTUP, 0, 0, 0, 0);

            //1秒強制停止する
            System.Threading.Thread.Sleep(1000);

            Console.WriteLine("------AFTER---------");
            Console.WriteLine("Cursor.Position.X:" + Cursor.Position.X.ToString());
            Console.WriteLine("Cursor.Position.Y:" + Cursor.Position.Y.ToString());

            Console.WriteLine("ドラッグDoun20: 完了");

            //エラーは無いと思うので、OKを返す
            return "OK";
        }
    }





    //-----------------------------------------------------------------------------------------------------------------
    class ClassTabキーを押す
    {
        //メソッド
        public string _HitTab(int Count)
        {
            for (int i = 0; i < Count; i++)
            {
                SendKeys.Send("{TAB}");
                //指定ミリ秒停止する
                System.Threading.Thread.Sleep(300);
            }

            //1秒強制停止する
            System.Threading.Thread.Sleep(500);

            Console.WriteLine("Tabキーを押す: {0} 完了", Count);

            //エラーは無いと思うので、OKを返す
            return "OK";
        }
    }

    //-----------------------------------------------------------------------------------------------------------------
    class Class矢印キーを押す
    {
        //メソッド
        public string _HitArrow(string param)
        {
            var splitted = param.Split('_');

            string direction = splitted[0];
            int count = int.Parse(splitted[1]);

            for (int i= 0; i < count; i++)
            {
                switch (direction)
                {
                    case "左":
                        SendKeys.Send("{LEFT}");
                        break;
                    case "右":
                        SendKeys.Send("{RIGHT}");
                        break;
                    case "上":
                        SendKeys.Send("{UP}");
                        break;
                    case "下":
                        SendKeys.Send("{DOWN}");
                        break;
                    default:
                        break;
                }
                System.Threading.Thread.Sleep(300);
            }

            //1秒強制停止する
            System.Threading.Thread.Sleep(500);

            Console.WriteLine("矢印キーを押す: {0} 完了", param);

            //エラーは無いと思うので、OKを返す
            return "OK";
        }
    }


    //-----------------------------------------------------------------------------------------------------------------
    class ClassDeleteキーを押す
    {
        //メソッド
        public string _HitDelete()
        {
            SendKeys.Send("{DELETE}");

            //1秒強制停止する
            System.Threading.Thread.Sleep(500);

            Console.WriteLine("Deleteキーを押す: {0}回完了");

            //エラーは無いと思うので、OKを返す
            return "OK";
        }
    }


    //-----------------------------------------------------------------------------------------------------------------
    class ClassEnterキーを押す
    {
        //メソッド
        public string _HitEnter()
        {
            SendKeys.Send("{ENTER}");

            //1秒強制停止する
            System.Threading.Thread.Sleep(1000);

            Console.WriteLine("Tabキーを押す: 完了");

            //エラーは無いと思うので、OKを返す
            return "OK";
        }
    }


    //-----------------------------------------------------------------------------------------------------------------
    class Class文字入力
    {
        //メソッド
        public string _HitCharacter(string Character)
        {
            SendKeys.Send(Character);

            //1秒強制停止する
            System.Threading.Thread.Sleep(500);

            Console.WriteLine("文字入力: [{0}]完了", Character);

            //エラーは無いと思うので、OKを返す
            return "OK";
        }
    }

    //-----------------------------------------------------------------------------------------------------------------
    class Class日付入力_Day
    {
        //メソッド
        public string _InputDateDay(string Count)
        {
            int count = int.Parse(Count);
            string inputDate = DateTime.Now.AddDays(count).ToString("yyyy/MM/dd"); 
            SendKeys.Send(inputDate);

            //1秒強制停止する
            System.Threading.Thread.Sleep(1000);

            Console.WriteLine("日付入力_Day: [{0}]完了", inputDate);

            //エラーは無いと思うので、OKを返す
            return "OK";
        }
    }


    //-----------------------------------------------------------------------------------------------------------------
    class Class日付入力_1日
    {
        //メソッド
        public string _InputDateDay(string Count)
        {
            int count = int.Parse(Count);
            string inputDate = DateTime.Now.AddMonths(count).ToString("yyyy/MM/01");
            SendKeys.Send(inputDate);

            //1秒強制停止する
            System.Threading.Thread.Sleep(1000);

            Console.WriteLine("日付入力_Day: [{0}]完了", inputDate);

            //エラーは無いと思うので、OKを返す
            return "OK";
        }
    }


    //-----------------------------------------------------------------------------------------------------------------
    class Class日付入力_末日
    {
        //メソッド
        public string _InputDateDay(string Count)
        {
            int count = int.Parse(Count);
            count += 1;　　//プラス1月しておいて…
            string inputDate = DateTime.Now.AddMonths(count).ToString("yyyy/MM/01");　//１日を指定
            inputDate = DateTime.Parse(inputDate).AddDays(-1).ToString("yyyy/MM/dd"); //1日マイナスで、月末日を計算する。

            SendKeys.Send(inputDate);

            //1秒強制停止する
            System.Threading.Thread.Sleep(1000);

            Console.WriteLine("日付入力_Day: [{0}]完了", inputDate);

            //エラーは無いと思うので、OKを返す
            return "OK";
        }
    }

    //-----------------------------------------------------------------------------------------------------------------
    class Classフルパス入力
    {
        //メソッド
        public string _InputFullPath(string Path)
        {
            Path = Path.Replace("年月日時間", DateTime.Now.ToString("yyyyMMdd_HHmmss"));
            SendKeys.Send(Path);

            //1秒強制停止する
            System.Threading.Thread.Sleep(1000);

            Console.WriteLine("フルパス入力: [{0}]完了", Path);

            //エラーは無いと思うので、OKを返す
            return "OK";
        }
    }



    //-----------------------------------------------------------------------------------------------------------------
    class Class画像マッチング
    {
        [DllImport("USER32.dll", CallingConvention = CallingConvention.StdCall)]　　//マウスポインタ移動用
        static extern void SetCursorPos(int X, int Y);


        //プロパティ
        private string  templateImagePath;  //テンプレート画像のフルパス
        public string _TemplateImagePath
        {
            get { return templateImagePath; }
        }

        private string searchImagePath;  //サーチ画像のフルパス
        public string _SearchImagePath
        {
            get { return searchImagePath; }
        }


        //メソッド
        public string _ImageMatching(IProgress<int> Progress, string Param)
        {
            return ImageMatching(Progress, Param);
        }

        private string ImageMatching(IProgress<int> progress, string param)
        {
            /*引数の文字を分解して、各変数に格納する。-----------------------------------
             * 
             * [引数の例]
             *  「0010SAPユーザ初期画面.bmp_60,207」 の場合↓
             * 
             *  テンプレートとして利用する画像: 
             *          0010SAPユーザ初期画面.bmp
             *          
             *  マウスポインタを移動させる座標(上記画像の左上原点からの相対座標)：
             *          x座標：60
             *          y座標：207 
             * 
             * [注意] 
             *  画像を保存するフォルダは、下記に固定されている。
             * 
             * 　　   　┣実行ファイル.exe
             * 　　   　┗画像
             * 　　   　　　┗0010SAPユーザ初期画面.bmp
             * 　　   　　
             *-------------------------------------------------------------------------*/
            
            string テンプレート画像 = "";
            int xLocMouse = 0;     //マウスポインタを移動させるLocal座標(テンプレート画像の左肩を原点とした際の座標)
            int yLocMouse = 0;     //マウスポインタを移動させるLocal座標(テンプレート画像の左肩を原点とした際の座標)

            string エラーMSG = "";

            try
            {
                var splitted1 = param.Split('_');
                テンプレート画像 = splitted1[0];

                var splitted2 = splitted1[1].Split(',');
                xLocMouse = int.Parse(splitted2[0]);
                yLocMouse = int.Parse(splitted2[1]);

                if (テンプレート画像.Contains(".bmp") == false)
                {
                    エラーMSG = " 画像は.bmp形式のみです。";
                    throw new Exception();
                }
            }
            catch (FormatException)
            {
                エラーMSG = " 座標データが不正かもしれません。";
                return "NG: \r\n パラメータが不正です。確認ください。\r\n" + エラーMSG;
            }
            catch (Exception)
            {
                return "NG: \r\n パラメータが不正です。確認ください。\r\n" + エラーMSG;
            }

            //初期値設定
            Formメイン._FormMainInstance._StatusAll = 0;  //検索する総画素数
            Formメイン._FormMainInstance._StatusDone = 0;  //検索完了した画素数


            //画像の保存先の指定-------------------------------------------------------------------
            //テンプレート画像用フルパス作成
            string テンプレート画像フルパス = System.Environment.CurrentDirectory + "\\画像\\" + テンプレート画像;
            //サーチ画像用フルパス作成
            string サーチ画像フルパス = System.Environment.CurrentDirectory + "\\SearchImage\\SearchImage.bmp";

            //外部出力用
            templateImagePath = テンプレート画像フルパス;
            searchImagePath = サーチ画像フルパス;



            //テンプレート画像関連処理-------------------------------------------------------------
            //テンプレート画像を分析し、画像一致判断のデータを取得する。
            //【参考】ht　tps://araramistudio.jimdo.com/2021/03/10/c-%E3%81%A7%E7%94%BB%E5%83%8F%E3%81%AE%E3%83%94%E3%82%AF%E3%82%BB%E3%83%AB%E3%83%87%E3%83%BC%E3%82%BF%E3%81%B8%E3%82%A2%E3%82%AF%E3%82%BB%E3%82%B9%E3%81%99%E3%82%8B/
            
            //テンプレート画像bpm作成
            var テンプレート画像bpm = new Bitmap(テンプレート画像フルパス);

            //寸法関連の値確保
            int wTmpl = テンプレート画像bpm.Width;      //全幅
            int hTmpl = テンプレート画像bpm.Height;     //全高

            int wCenter = wTmpl / 2;                    //幅の中央X座標
            int hCenter = hTmpl / 2;                    //高さの中央Y座標

            //【利便性向上】パラメータのXとYが供に0の場合は、テンプレート画像の中央に変更しておく。
            if (xLocMouse == 0 && yLocMouse == 0)
            {
                xLocMouse = wCenter;
                yLocMouse = hCenter;
            }

                Console.WriteLine("  -------------------" );
                Console.WriteLine("   wTmpl  : " + wTmpl.ToString());
                Console.WriteLine("   hTmpl  : " + hTmpl.ToString());
                Console.WriteLine("   wCenter: " + wCenter.ToString());
                Console.WriteLine("   hCenter: " + hCenter.ToString());
                Console.WriteLine("  -------------------");

            //テンプレート画像のデータを確保する為のListを定義する。
            //縦赤線のピクセル別RGB情報を取得する
            //【訂正】値は、Rのみを繰り返す
            //【廃止】値は、R,G,B,R,G,B,R,G,B,…を繰り返す
            var list縦赤線RGB = new List<int>();   //縦赤線(縦中央線のデータのRGB羅列)のデータ用変数
            var list横赤線RGB = new List<int>();   //横赤線(横中央線のデータのRGB羅列)のデータ用変数

            var bitmapTmpl = new Bitmap(テンプレート画像bpm);       //ビットマップをコピー
            var rectTmpl = new Rectangle(0, 0, wTmpl, hTmpl);       //Rectangle 構造体を「テンプレート画像bpm」と同サイズで作成
            //LockBits メソッドを使い画像データをbyte配列に展開する
            var bmpdataTmpl = bitmapTmpl.LockBits(rectTmpl, ImageLockMode.ReadWrite, PixelFormat.Format32bppArgb);　
            //バイト型を定義する。
            var imgdataTmpl = new byte[bmpdataTmpl.Stride * bitmapTmpl.Height];
            System.Runtime.InteropServices.Marshal.Copy(bmpdataTmpl.Scan0, imgdataTmpl, 0, imgdataTmpl.Length);

            //縦赤線のテンプレートデータを確保する。
            int x = 0;
            int y = 0;

            x = wCenter; //固定
            for (y = 0; y < hTmpl; ++y)
            {
                int pos = y * bmpdataTmpl.Stride + x * 4;  //Format32bppArgbは1ピクセル4バイト
                //var b = imgdataTmpl[pos];
                //var g = imgdataTmpl[pos + 1];
                var r = imgdataTmpl[pos + 2];
                //var a = imgdataTmpl[pos + 3];

                list縦赤線RGB.Add(r);
                //list縦赤線RGB.Add(g);
                //list縦赤線RGB.Add(b);
            }

            //横赤線のテンプレートデータを確保する。
            y = hCenter; //固定
            for (x = 0; x < wTmpl; ++x)
            {
                int pos = y * bmpdataTmpl.Stride + x * 4;  //Format32bppArgbは1ピクセル4バイト
                //var b = imgdataTmpl[pos];
                //var g = imgdataTmpl[pos + 1];
                var r = imgdataTmpl[pos + 2];
                //var a = imgdataTmpl[pos + 3];

                list横赤線RGB.Add(r);
                //list横赤線RGB.Add(g);
                //list横赤線RGB.Add(b);
            }


            /*テンプレートマッチング処理-----------------------------------------------------------
             * 
             * 1)マウスポインタが邪魔しない様、(0,0)に移動させる。
             * 
             * 2)プリントスクリーン機能でモニタ全体の画面をビットマップ画像ファイル(以下サーチ画像)にして保存する。
             *   保存する理由は、後で検証し易くする為。
             *   　 
             * 　[保存先] 
             *　　　画像を保存するフォルダとファイル名は、下記に固定する。
             * 
             * 　　   　┣実行ファイル.exe
             * 　　   　┗SearchImage
             * 　　   　　　┗SearchImage.bmp
             * 
             * 
             * 3)サーチ画像の中に、テンプレート画像と一致する座標を見つける。
             * 
             *   NGの場合
             *      ・[中止/初回一致でOKとした] 座標が2カ所以上存在する。
             *      ・一致座標が見つからなかった。
             *   NGの場合の戻り値例
             *   　　"NG: エラー内容"
             *   NGの場合の後処理
             *   　　即return　　
             *      
             *   OKの場合、マウスポインタを指定位置に移動させ、 戻り値"OK"を返す。 
             *   
             * 4)【追加】初回マッチング、2回目マッチンでNGでもリトライさせる。
             * 　        さすがに、3回目でもNGなら、NGを返す。
             * 　        マッチング処理のトライ回数は変数[cntTry]に書込む。       
             *       
            -------------------------------------------------------------------------------------*/

            int cntTry = 0;  //マッチング処理のトライ回数　

        ReTryPoint:
            cntTry += 1;


            //1)マウスポインタが邪魔しない様、(0,0)に移動させる。
            SetCursorPos(0, 0);    //マウスカーソル移動

            //1-1) マウス移動後、ツールチップが残ることがあるので1秒待機。(2→1秒に変更/20231225)
            System.Threading.Thread.Sleep(1000);

            //2)プリントスクリーン機能でモニタ全体の画面をビットマップ画像ファイルにして保存する。
            //空のBitmapの作成
            Bitmap サーチ画像bmp = new Bitmap(Screen.PrimaryScreen.Bounds.Width, Screen.PrimaryScreen.Bounds.Height);

            //Graphicsの作成
            Graphics gr = Graphics.FromImage(サーチ画像bmp);
            //画面全体をコピーする
            gr.CopyFromScreen(new Point(0, 0), new Point(0, 0), サーチ画像bmp.Size);
            //解放
            gr.Dispose();
            //ビットマップ画像を保存する
            //　[処理]ファイルがぞんざいしていれば、既存ファイル削除して保存する。
            if (System.IO.File.Exists(サーチ画像フルパス))
            {
                //存在する
                System.IO.File.Delete(サーチ画像フルパス);    //削除
                サーチ画像bmp.Save(サーチ画像フルパス);       //保存
            }
            else
            {
                //存在しない
                サーチ画像bmp.Save(サーチ画像フルパス);       //保存
            }



            //3)サーチ画像の中に、テンプレート画像と一致する座標を見つける。
            int 画像一致回数 = 0;
            string 処理結果 = "";

            Bitmap bmpSrch = (Bitmap)サーチ画像bmp.Clone(); 　//念のため、クローンを作ったが…処理時間が掛かるようだと廃止。

            //進捗連絡用
            int 総画素数 = (bmpSrch.Height - hTmpl - 1) * (bmpSrch.Width - wTmpl - 1);   //検索する総画素数
            int 検索画素数 = 0;                                                          //検索完了した画素数

            //Formメイン._FormMainInstance._StatusAll = (bitmapSrch.Height - hTmpl - 1) * (bitmapSrch.Width - wCenter - 1);  //検索する総画素数
            //Formメイン._FormMainInstance._StatusDone = 0;  //検索完了した画素数

            var rect = new Rectangle(0, 0, bmpSrch.Width, bmpSrch.Height);
            var bmpdata = bmpSrch.LockBits(rect, ImageLockMode.ReadWrite, PixelFormat.Format32bppArgb);
            try
            {
                Console.WriteLine("---これからimgdata---");
                var imgdata = new byte[bmpdata.Stride * bmpSrch.Height];
                System.Runtime.InteropServices.Marshal.Copy(bmpdata.Scan0, imgdata, 0, imgdata.Length);

                Console.WriteLine("---forループ---");
                Console.WriteLine("bitmapSrch.Width       : " + bmpSrch.Width.ToString());
                Console.WriteLine("bitmapSrch.Height      : " + bmpSrch.Height.ToString());
                Console.WriteLine("list縦赤線RGB.Count: " + list縦赤線RGB.Count.ToString());

                //サーチ画像のデータ抽出(縦赤線のピクセル別RGB情報を取得すし比較する)

                //【訂正】値は、Rのみを繰り返す
                //【廃止】値は、R,G,B,R,G,B,R,G,B,…を繰り返す
                var listサーチ縦線RGB = new List<int>();
                var listサーチ横線RGB = new List<int>();

                //サーチ対象画面のサーチ中の座標を一時的に保持する。
                int x1 = 0;
                int y1 = 0;

                //下記ループのxyは、テンプレート画像の原点(左上)とする。
                for (y = 0; y < bmpSrch.Height - hTmpl; y++)   //for (y = 0; y < bmpSrch.Height - hTmpl; y++)
                {
                    for (x = 0; x < bmpSrch.Width - wTmpl; x++)  //for (x = 0; x < bmpSrch.Width - wCenter; x++)
                    {
                        listサーチ縦線RGB.Clear();   //リセット

                        x1 = x + wCenter;

                        //このxyをテンプレート画像の原点(左上)とし、縦赤線(hTmpl分)のRGBデータをListに格納する。
                        for (int i = 0; i < hTmpl; i++)
                        {
                            y1 = y + i;  //yを足しこんでサーチ画面座標にする。

                            int pos = y1 * bmpdata.Stride + x1 * 4;  //Format32bppArgbは1ピクセル4バイト
                            //var b = imgdata[pos];
                            //var g = imgdata[pos + 1];
                            var r = imgdata[pos + 2];
                            //var a = imgdata[pos + 3];

                            listサーチ縦線RGB.Add(r);
                            //listサーチ縦線RGB.Add(g);
                            //listサーチ縦線RGB.Add(b);
                        }

                        //RGBを比較する。
                        long result = RGB比較(list縦赤線RGB, listサーチ縦線RGB);


                        //処理パーセンテージを報告
                        検索画素数 += 1;
                        if (検索画素数 % 2000 == 0) //if (y % 50 == 0 && x % 50 == 0) 
                        {
                            int percentage = 検索画素数 * 100 / 総画素数; // 進捗率
                            if (percentage >= 100) { percentage = 100; };
                            progress.Report(percentage);
                        }
                        
                        if (result <= 500)　//【重要】縦赤線一致と判断する
                        {
                            //縦赤線が一致したので、次に横赤線を確認する。

                            listサーチ横線RGB.Clear();   //リセット

                            y1 = y + hCenter;

                            for (int i = 0; i < wTmpl; i++)
                            {
                                x1 = x + i;  //yを足しこんでサーチ画面座標にする。

                                int pos = y1 * bmpdata.Stride + x1 * 4;  //Format32bppArgbは1ピクセル4バイト
                                                                         //var b = imgdata[pos];
                                                                         //var g = imgdata[pos + 1];
                                var r = imgdata[pos + 2];
                                //var a = imgdata[pos + 3];

                                listサーチ横線RGB.Add(r);
                                //listサーチ縦線RGB.Add(g);
                                //listサーチ縦線RGB.Add(b);
                            }

                            //RGBを比較する。
                            result = RGB比較(list横赤線RGB, listサーチ横線RGB);

                            if (result <= 500)　//【重要】横赤線一致と判断する
                            {

                                //【重要】縦赤線と横赤線が共に一致　⇒　マッチング完了

                                画像一致回数 += 1;  //インクリメント

                                int xGlbMouse = x + xLocMouse;    //マウスを移動させるモニタ上のGlobal座標
                                int yGlbMouse = y + yLocMouse;    //マウスを移動させるモニタ上のGlobal座標

                                Console.WriteLine("x: {0} y: {1} 合計値: {2} istサーチ縦線RGB.Count: {3}", x, y, result, listサーチ縦線RGB.Count);

                                SetCursorPos(xGlbMouse, yGlbMouse);    //マウスカーソル移動ここで動かしておく

                                ////一致情報出力
                                //Console.WriteLine("****画像一致↓↓↓↓↓↓");

                                //Console.WriteLine("     テンプレート画像x:" + x.ToString());
                                //Console.WriteLine("     テンプレート画像y:" + y.ToString());

                                //Console.WriteLine("             xLocMouse:" + xLocMouse.ToString());
                                //Console.WriteLine("             yLocMouse:" + yLocMouse.ToString());

                                //Console.WriteLine("           マウス移動x:" + xGlbMouse.ToString());
                                //Console.WriteLine("           マウス移動y:" + yGlbMouse.ToString());


                                //string 一致情報 = "一致情報\n";
                                //一致情報 += "     テンプレート画像x: " + x.ToString() + "\n";
                                //一致情報 += "     テンプレート画像y: " + y.ToString() + "\n";
                                //一致情報 += "             xLocMouse: " + xLocMouse.ToString() + "\n";
                                //一致情報 += "             yLocMouse: " + yLocMouse.ToString() + "\n";
                                //一致情報 += "           マウス移動x: " + xGlbMouse.ToString() + "\n";
                                //一致情報 += "           マウス移動x: " + xGlbMouse.ToString() + "\n";

                                //MessageBox.Show(一致情報); 

                                //Console.WriteLine("****画像一致↑↑↑↑↑↑");

                                //これでスキャン終了!!!☆彡
                                goto SKIP;

                            }
                        }


                        //【キャンセル処理】
                        //待機中のイベントを処理する
                        Application.DoEvents();

                        //キャンセルボタンがクリックされたか調べる
                        if (Formメイン._FormMainInstance._Canceled)
                        {
                            Console.WriteLine("!!!!!!!!キャンセルッ!!!!!!!!");
                            処理結果 = "キャンセル";

                            //break;  //ループを抜ける
                            goto SKIP;
                        }
                    }
                }

            SKIP:;  //<--- 【キャンセル処理】キャンセル時のGOTO先!!!

            }
            finally
            {
                //Console.WriteLine("x: {0} y: {1} 合計値: {2} istサーチ縦線RGB.Count: {3}", x, y, result, listサーチ縦線RGB.Count);
                bmpSrch.UnlockBits(bmpdata);

            }

            Console.WriteLine("-------画像マッチング終了");

            //最終処理
            if (処理結果 == "キャンセル")
            {
                処理結果 = "NG: キャンセルされました。";
            }
            else if(画像一致回数 == 0)
            {
                処理結果 = "NG: テンプレート画像が見つかりませんでした。(一致回数:0) /リトライ" + cntTry + "回";
                if( cntTry <= 2 ){ goto ReTryPoint; } //2回目までNGならリトライ(3回目でもNGなら、NGを返す)
            }
            else if (画像一致回数 > 1)
            {
                処理結果 = "NG: テンプレート画像が複数回一致しました。(一致回数:" + 画像一致回数.ToString() + ")/ リトライ" + cntTry + "回";
                if (cntTry <= 1) { goto ReTryPoint; } //2回目までNGならリトライ(3回目でもNGなら、NGを返す)
            }
            else if (画像一致回数 == 1)
            {
                処理結果 = "OK";
            }


            //1秒強制停止する
            System.Threading.Thread.Sleep(1000);

            Console.WriteLine("処理結果:" + 処理結果);

            return 処理結果;
        }


        private long RGB比較(IReadOnlyList<int> tmpl, IReadOnlyList<int> seach)
        {
            //テンプレート画面のRGBデータと、サーチ画面のRGBデータを1要素毎に比較、
            //その差の絶対値の合計値を戻す。(比較するデータは同じ要素番号同士を比較する。)
            //但し、戻り値が一定数を超える場合は、-1を戻す

            long 合計値 = 0;

            if (seach.Count == tmpl.Count)
            {
                for (int i = 0; i < tmpl.Count; i += 2)  //2ピクセル飛ばし
                {
                    //合計値 += Math.Abs(tmpl[i] - seach[i]);
                    合計値 += (tmpl[i] - seach[i]) * (tmpl[i] - seach[i]);  //【高速化チャレンジ】2乗　⇒　チョット早い？
                }
                return 合計値;
            }
            else
            {
                return -2;
            }
        }
    }
}




```

### ７０．--- 非同期 ---
### ７０-１．非同期/ 初級 .WhenAllで待つ
非同期で同じメソッドを複数起動して、全てが終わるまで待つ。
そして、終わったら戻り値を確認する。
【注意】.WhenAnyで待てば、一番早いタスクの完了まで待つ。(1つ完了すれば次に進む)

```c#:C# Fromクラス
        private void button1_Click(object sender, EventArgs e)
        {
            非同期処理();
        }

        private async void 非同期処理()
        {
            Console.WriteLine("↓↓↓非同期処理()に入りました。↓↓↓↓");

            ClassBigProcess c = new ClassBigProcess();

            Task<string> taskA = Task.Run(() => c._膨大処理(2000, 196));
            Task<string> taskB = Task.Run(() => c._膨大処理(1500, 267));
            Task<string> taskC = Task.Run(() => c._膨大処理( 500, 101));

            await Task.WhenAll(taskA, taskB, taskC);  //ここで３つの終了を待つ

            //結果
            Console.WriteLine("taskA:" + taskA.Result);
            Console.WriteLine("taskB:" + taskB.Result);
            Console.WriteLine("taskC:" + taskC.Result);
            Console.WriteLine("↑↑↑非同期処理()が終了しました。↑↑↑");
        }
```
```c#:C# ClassBigProcess
    class ClassBigProcess
    {
        public async Task<string> _膨大処理(int MSec, int ID)
        {
            //指定ミリ秒停止する
            await Task.Delay(MSec);

            Console.WriteLine("Delay：" + MSec.ToString() + "ミリ秒間停止しました。");

            //エラーは無いと思うので、OKを返す
            return "OK:" + ID;
        }
    }
```
```c#:C# 出力
↓↓↓非同期処理()に入りました。↓↓↓↓
Delay：500ミリ秒間停止しました。
Delay：1500ミリ秒間停止しました。
Delay：2000ミリ秒間停止しました。
taskA:OK:196
taskB:OK:267
taskC:OK:101
↑↑↑非同期処理()が終了しました。↑↑↑
```
### ７０-２．非同期/ 期待する戻り値を得たら、残りのタスクをキャンセルする

[動作概要]
１．button1をクリックすると、非同期処理Asyncメソッドが起動され、そこからtaskA、taskB、taskCが非同期で起動されます。
２．3つのタスクが完了した順に戻り値戻り値を確認し、"OK"が含まれていれば残りのタスクをキャンセルする。
```c#:重い処理
        static string _重い処理(int count, string ID, CancellationToken token)
        {
            DateTime now = DateTime.Now;
            Console.WriteLine($"ID: {ID} ⇒ 開始 {now:HH:mm:ss.fff}");

            //長大処理
            for (int i = 0; i < count; i++)
            {
                if (token.IsCancellationRequested)
                {
                    Console.WriteLine($"{ID}: キャンセル検知！");
                    return $"{ID} / キャンセルされました";
                }
                Thread.Sleep(1000); //1秒停止
            }

            Console.WriteLine($"{ID}: 処理完了（{count}秒）");

            string ans = "";

            if (ID == "1st")        //1stは5秒で完了する。
            {
                ans = ID + "/NG";
            }
            else if (ID == "2nd")   //2ndは3秒で完了する。
            {
                ans = ID + "/OK";   //←ここだけOK！これを待ちたい。
            }
            else if (ID == "3rd")   //3rdは1秒で完了する。
            {
                ans = ID + "/NG";
            }
            return ans;
        }
```
```c#:模範解答１
        using System.Threading;     //CancellationTokenSource利用の為追加要

        private async void buttonサンプルA_Click(object sender, EventArgs e)
        {
            await 非同期処理01Async();
        }

        private static async Task 非同期処理01Async()
        {
            //キャンセルトークン設定
            var cts = new CancellationTokenSource();
            var token = cts.Token;

            Task<string> taskA = Task.Run(() => _重い処理(10, "1st", token), token); //10秒
            Task<string> taskB = Task.Run(() => _重い処理(5, "2nd", token), token);  //5秒
            Task<string> taskC = Task.Run(() => _重い処理(1, "3rd", token), token);  //1秒

            // タスクを実行する　**今回のキモです。汎用処理なので関数化するなり改変するなりしてください。
            await Task.Run(async () =>
            {
                var tasks = new List<Task<string>> { taskA, taskB, taskC };
                var count = tasks.Count;
                var tcs = new TaskCompletionSource<string>(); // 最初にOKを見つけた時点でタスクを解決するため

                // 各タスクを並行して実行
                foreach (var t in tasks)
                {
                    // タスクごとに処理
                    var taskTmp = Task.Run(async () =>
                    {
                        var result = await t;
                        count = count - 1;

                        // 結果に"OK"が含まれていた場合、即時終了
                        if (result.Contains("OK"))
                        {
                            tcs.SetResult(result); // 結果を返す
                        }

                        // すべてのタスクが終了したらcountが0になる
                        if (count <= 0 && !tcs.Task.IsCompleted)
                        {
                            tcs.SetResult("Not Found.");
                        }
                    });
                }
                // 結果を待つ 
                //↓【このreturnについて】Task.Runは関数を引数にとるのでその関数内の戻り値としてreturnを指定している。
                return await tcs.Task;
            });

            // 残りをキャンセル
            cts.Cancel();

            // それぞれの結果取得
            try
            {
                Console.WriteLine($"taskA: {taskA.Result}");
            }
            catch (AggregateException e)
            {
                Console.WriteLine($"taskA: {e.InnerException.Message}");
            }

            try
            {
                Console.WriteLine($"taskB: {taskB.Result}");
            }
            catch (AggregateException e)
            {
                Console.WriteLine($"taskB: {e.InnerException.Message}");
            }

            try
            {
                Console.WriteLine($"taskC: {taskC.Result}");
            }
            catch (AggregateException e)
            {
                Console.WriteLine($"taskC: {e.InnerException.Message}");
            }
        }
```
```c#:模範解答２
        using System.Threading;     //CancellationTokenSource利用の為追加要
        
        private void buttonサンプルB_Click(object sender, EventArgs e)
        {
                非同期処理02Async();
        }

        private async void 非同期処理02Async()
        {
            var cts = new CancellationTokenSource();
            var token = cts.Token;

            var taskA = Task.Run(() => _重い処理(10, "1st", token), token);
            var taskB = Task.Run(() => _重い処理(5, "2nd", token), token);
            var taskC = Task.Run(() => _重い処理(1, "3rd", token), token);

            var tasks = new List<Task<string>> { taskA, taskB, taskC };

            while (0 < tasks.Count)
            {
                var task = await Task.WhenAny(tasks);
                var s = await task;
                if (s.Contains("OK"))
                {
                    break;
                };
                tasks.Remove(task);
            }
            cts.Cancel();
        }
```
### ７０-３．非同期/ 大量タスクをList化して実行
[動作概要]
30個のタスクを発生させ、一番最初に"OK"の戻り値を確認したら、残りの動作途中のタスクをキャンセルする。

但し、めたらやったら非同期でタスクを実行させても、1個のCPUで切替えて使っているだけなので、効果は実験で確認要。
```c#:C#
        private void button1_Click(object sender, EventArgs e)
        {
            非同期処理Async();
        }

        private async void 非同期処理Async()
        {
            var cts = new CancellationTokenSource();
            var token = cts.Token;

            //タスクをList化する。
            var tasks = new List<Task<string>>();
            for (var i = 0; i < 30; i++)
            {
                var val = i;    //【ポイント】iをそのまま使うと正しくiを認識できないので一旦valに移す。
                tasks.Add(Task.Run(() => _重い処理(3, val.ToString() + "回目", token), token));
            }

            //OKが戻ってくるまで待つ
            while (0 < tasks.Count)
            {
                var task = await Task.WhenAny(tasks);
                var s = await task;
                if (s.Contains("OK"))   //OKが戻ってきたのでbrake!
                {
                    DateTime now = DateTime.Now;
                    Console.WriteLine($"=========>OK発見!!!: {task.Result} / 終了{now:HH:mm:ss.fff}");
                    break;
                };
                tasks.Remove(task);     //OKが含まれていないタスクなのでRemove!
            }

            //処理途中のタスクはCancel!
            cts.Cancel();
        }

        static string _重い処理(int count, string ID, CancellationToken token)
        {
            //タスク開始時刻
            DateTime now = DateTime.Now;
            Console.WriteLine($"ID: {ID} ⇒ 開始 {now:HH:mm:ss.fff}");

            //長大処理
            for (int i = 0; i < count; i++)
            {
                if (token.IsCancellationRequested)
                {
                    return $"{ID} / キャンセルされました";
                }
                Thread.Sleep(1000); //1秒停止
            }

            string ans = "";

            if (ID == "29回目")        //29回目だけOK
            {
                ans = ID + "/OK";
            }
            else 
            {
                ans = ID + "/NG";
            }

            return ans;
        }
```

### ８０．--- DB操作／SQLServer ---

### ８０-０．DB接続文字列
```c#:C#
            DB接続文字列 = @"Data Source=サーバのホスト名 or サーバのIPアドレス;
                            Initial Catalog=データベース名;
                            User Id=ユーザ名;
                            Password=パスワード;";
```

### ８０-１．Select
```c#:C#
            using (SqlConnection connection = new SqlConnection(DB接続文字列))
            using (SqlCommand command = new SqlCommand())
            {
                connection.Open();
                command.Connection = connection;

                SQL = "select カラム名 from …";

                command.CommandText = SQL;

                //SQLを実行。
                SqlDataReader reader = command.ExecuteReader();

                //結果受取り
                if (reader.HasRows == true)
                {
                    //レコード有り！
                    while (reader.Read())
                    {
                        フィールド等 = reader["カラム名"].ToString();
                    }
                }
                else
                {
                    //レコード無し
                }
                reader.Close();
            }
```

### ８０-２．Update, Insert, etc
```c#:C#
        public string SQL実行_ExecuteNonQuery(string DB接続文字列, string sql)
        {
            //DB接続文字列とSQL(Update,Insert etc...)をもらって実行する
            
            string Result;

            using (SqlCommand command = new SqlCommand())
            {
                SqlConnection conn = new SqlConnection();
                conn.ConnectionString = DB接続文字列;

                // トランザクションを開始します。
                conn.Open();
                SqlTransaction transaction = conn.BeginTransaction(IsolationLevel.ReadCommitted);
                try
                {
                    command.CommandText = sql;
                    command.Connection = conn;
                    command.Transaction = transaction;
                    command.ExecuteNonQuery();

                    //トランザクションをコミットします。
                    transaction.Commit();
                    Result = "OK";
                }
                catch (System.Exception)
                {
                    //トランザクションをロールバックします。
                    transaction.Rollback();
                    Result = "NG";
                    //throw;
                }
                finally
                {
                    conn.Close();
                }
                return Result;
            }
        }

```

### ８５．--- ファイル操作 ---

### ８５-１．エクセル操作（Range/Cell指定）
アンマネージドのCOMの「Microsoft.Office.Interop.Excel」を使うぞ~。
賛否両論あるけども、会社毎のシステムポリシーとか、開発者の置かれている状況・立場とか、配布先PCの状況もあるので、使える様にしておくのも手かと。
タスクマネージャでメモリーリークしてないこと確認したが…それにしても反応遅し(TT)
できれば使いたくない。

参考にさせて頂いた先生方、感謝。
            *       https://www.ipentec.com/document/csharp-open-read-excel-book-and-sheet
            *       https://qiita.com/tomohideyo/items/f46eab04b86316d917c5
            *       https://qiita.com/ANNEX_IBS/items/8308fb193dd987c0899e

```c#:C#

using Microsoft.Office.Interop.Excel;

        public string _ExcelRead_Cell(string fullPath, string taegetSheet, int row, int col)
        {
            /*【概要】***************************************************************************** 
            *   [参照先]
            *   using Microsoft.Office.Interop.Excel;
            *   
            *   fullPathで指定されたエクセルファイルのsheetで指定されたシートについて、
            *   colStart、rowStartで指定されたセルの値を戻す。
            *   
            *   [戻り値]
            *       正常完了時　⇒指定された値
            *       異常終了時　⇒"NG:エラーメッセージ"
            *        
            **************************************************************************************/

            string result = "OK";

            //エクセルを開く
            Microsoft.Office.Interop.Excel.Application xlApp = new Microsoft.Office.Interop.Excel.Application();
            Workbooks xlBooks = null;
            Workbook  xlBook = null;
            Worksheet xlsheet = null;
            //Range     xlRange = null;
            //Range     rangeW = null;
            Range     rangeR = null;
            try
            {
                //エクセルを見えない状態で開く
                xlApp.Visible = false;

                //ファイルを開く
                xlBooks = xlApp.Workbooks;
                xlBook = xlApp.Workbooks.Open(fullPath);

                //シートの指定
                xlsheet = xlBook.Sheets[taegetSheet];

                //読込***************************************
                //[その１]レンジの指定の場合-----------------
                //xlRange = xlsheet.Range["A1", "B2"];
                //[注意]同一セルだとエラーになる。(ここでのxlsheet.Cellsはセーフ)
                //xlRange = xlsheet.Range[xlsheet.Cells[row, col], xlsheet.Cells[row + 1, col + 1]];
                //object[,] values = xlRange.Value; // 選択した領域の値をメモリー上に格納
                //gotten = values[1, 1].ToString();

                //[その２]セルの指定の場合---------------------
                var cellsR = xlsheet.Cells;
                rangeR = cellsR[row, col];
                result = rangeR.Value;

                ////書込み**************************************
                //var cellsW = xlsheet.Cells;
                //rangeW = cellsW[5, 4] ;
                //rangeW.Value = "書込んだよ99";

                ////保存***************************************
                //xlApp.DisplayAlerts = false;    //アラートの停止
                //xlBook.Save();
            }
            catch(Exception e)
            {
                result = "NG:" + e.ToString();
            }
            finally
            {
                //後処理---------------------------------------------------------------------------------------------------
                // 使用したCOMオブジェクトを解放その１
                if (rangeR != null) System.Runtime.InteropServices.Marshal.ReleaseComObject(rangeR);
                //if (rangeW != null) System.Runtime.InteropServices.Marshal.ReleaseComObject(rangeW);
                //if (xlRange != null) System.Runtime.InteropServices.Marshal.ReleaseComObject(xlRange);
                if (xlsheet != null) System.Runtime.InteropServices.Marshal.ReleaseComObject(xlsheet);

                //【ポイント】強制メモリー破棄
                GC.Collect();
                GC.WaitForPendingFinalizers();　//これなんやろ？いるのかな？
                GC.Collect();

                // Excelのクローズ
                xlBook.Close();
                xlApp.Quit();

                // 使用したCOMオブジェクトを解放その２
                if (xlBook != null) System.Runtime.InteropServices.Marshal.FinalReleaseComObject(xlBook);
                if (xlBooks != null) System.Runtime.InteropServices.Marshal.FinalReleaseComObject(xlBooks);
                if (xlApp != null) System.Runtime.InteropServices.Marshal.FinalReleaseComObject(xlApp);

                //【ポイント】強制メモリー破棄
                GC.Collect();
                GC.WaitForPendingFinalizers();
                GC.Collect();
            }
            return result;
        }
```
【超重要】
エクセルのフォーマットが日付になっていると、少数で受けっとってしまう。
これをDateTimeに戻すのがこれ↓
```c#:C#
        DateTime tmp = DateTime.FromOADate(double.Parse(values[1, k].ToString()));
```


### ８５-２．エクセル操作（Sheetまるごと）
アンマネージドのCOMの「Microsoft.Office.Interop.Excel」を使って…
Sheetを2次元配列にセットして戻すメソッド。
【超重要】
但し、usedRangeメソッドを使うので、「書式変更あり＆値無し」のセルも利用範囲(Range)として切り取ってしまう。
後処理で注意要！


```c#:C#　呼出側

            //引数設定
            string fullPath = "フルパス";
            string sheetName = "Sheet1";
            string errMsg = "";   //文字列無しで渡す
            
            ClassExcel cExcel = new ClassExcel();
            object[,] array = cExcel.エクセル読み込み(fullPath, sheetName, ref errMsg);
            if (array == null)
            {
                //戻り値がnullだった場合の処理
            }
            
            // 戻り値(2次元配列)を別の2次元配列へコピー 
            // 【注意】
            //   ・戻り値(2次元配列)は1始まりの配列となっている!!!(「Microsoft.Office.Interop.Excel」の仕様？)
            //   ・が、Array.Copyした2次元配列添え字は(いつも通りの)0始まりとなる。
            
            object[,] arraySAP = new object[array.GetLength(0), array.GetLength(1)];
            Array.Copy(array, arraySAP, array.Length);

```

```c#:C#　別クラスのメソッド

using System.Runtime.InteropServices;
using Microsoft.Office.Interop.Excel;

        public object[,] エクセル読み込み(string fullPath, string sheetName, ref string errMsg)
        {
            /**************************************************************************************
             * [引数]
             *  fullPath : 対象となるエクセルファイルフルパス
             *  sheetName: 読込み対象となるシート
             *  errMsg: (参照渡し)エラー発生時のコメントを返す
             * [戻り値] 
             *  シート上のデータを2次元配列にセットして返す。
             *  何らかのエラーが派生した場合は、nullの2次元配列を返す。
             *  
             *  【注意】
             *    ①戻り値の添え字は1始まり <---(ﾟﾛﾟ)!!! 
             *    ②2次元配列は参照渡しとなるので、戻り値はローカルの2次元配列に即コピーのこと。
             *    　Arrry.Copyでコピー後の配列は、0始まりとなる。
             * 　
             *************************************************************************************/

            Console.WriteLine("fullPath :" + fullPath);
            Console.WriteLine("sheetName:" + sheetName);

            errMsg = ""; //リセット
            object[,] rangeArray;
            object[,] nullArray = null; 　//エラー時用


            //フルパスで指定されたエクセルファイルが存在するか確認---------------------------------
            if (System.IO.File.Exists(fullPath))    //ファイル存在チェック
            {
                //存在する　⇒　OK
            }
            else
            {
                //存在しない　⇒　おしまい
                rangeArray = nullArray;             //nullの2次元配列を代入
                errMsg += "フルパスで指定されたエクセルファイルが存在しません。";
                Console.WriteLine("フルパスで指定されたエクセルファイルが存在しません。");
                return rangeArray;
            }


            //エクセルファイル読み込み-------------------------------------------------------------
            var excelApplication = new Microsoft.Office.Interop.Excel.Application();
            try
            {
                Workbooks workbooks = excelApplication.Workbooks;
                try
                {
                    Workbook workbook = workbooks.Open(fullPath);
                    try
                    {
                        Sheets worksheets = workbook.Sheets;
                        try
                        {
                            Worksheet worksheet = worksheets[sheetName];
                            try
                            {
                                // 使用範囲を一括で二次元配列にコピー
                                Range usedRange = worksheet.UsedRange;
                                try
                                {
                                    rangeArray = usedRange.Value;
                                }
                                finally { Marshal.ReleaseComObject(usedRange); }

                                // 二次元配列に対してループを回す
                                int lastRow = rangeArray.GetLength(0);
                                int lastCol = rangeArray.GetLength(1);
                                Console.WriteLine("lastRow:" + lastRow);
                                Console.WriteLine("lastCol:" + lastCol);
                                //for (int i = 1; i < lastRow; i++)
                                //{
                                //    Console.WriteLine(rangeArray[i, 1]);
                                //}
                            }
                            finally { Marshal.ReleaseComObject(worksheet); }
                        }
                        catch (Exception e)
                        {
                            rangeArray = nullArray;
                            errMsg += "シート名エラー";
                            Console.WriteLine("シート名エラー e:" + e.ToString());
                        }
                        finally { Marshal.ReleaseComObject(worksheets); }
                    }
                    finally
                    {
                        if (workbook != null)
                        {
                            workbook.Close(false);
                        }
                        Marshal.ReleaseComObject(workbook);
                    }
                }
                catch (Exception e)
                {
                    rangeArray = nullArray;
                    errMsg += "ファイルを開くことができませんでした。";
                    Console.WriteLine("ファイルを開くことができませんでした。 e:" + e.ToString());
                }
                finally { Marshal.ReleaseComObject(workbooks); }
            }
            finally
            {
                if (excelApplication != null)
                {
                    excelApplication.Quit();
                }
                Marshal.ReleaseComObject(excelApplication);
            }
            return rangeArray;
        }
```







### ８５-３．CSVファイル操作
あんまり使わないかもだけど備忘として。
```c#:C#
        public string _AppendOneLineToLast(string fullPath, string header, string line)
        {
            /*-------------------------------------------------------------------------------------
             * 【処理】
             *  指定したファイル(fullPath)がある場合:
             *      ⇒最終行に指定文字(line)を追記する。
             *  指定したファイルが無い場合:
             *      ⇒1)ファイルを作成し、ヘッダー(header)を書込んだ後、2)最終行に指定文字(line)を追記する。   
             *      
             *  例)
             *   header:　"登録日,結果,備考"            
             *   line  :  "2023/05/21 09:04:23,OK,特記なし"
             *  
             ------------------------------------------------------------------------------------*/

            string result = "NG";            
            line += "\r\n"; //改行追加

            //1)ファイルが無い場合、ファイルを作成しヘッダー(header)を書込む 
            if (File.Exists(fullPath))
            {
                //ファイル有り⇒続行
            }
            else
            {
                //ファイル無し⇒ファイルを作成し、ヘッダー(header)を書込む。
                using (StreamWriter swNew = new StreamWriter(fullPath,
                                                            false,  //false:上書き、true:追加
                                                            Encoding.GetEncoding("shift-jis")))
                {
                        swNew.WriteLine(header);
                }
            }

            //2)最終行に指定文字(line)を追記する。
            StreamWriter sw = null;
            try
            {
                sw = new StreamWriter(
                    fullPath,
                    true,
                    System.Text.Encoding.GetEncoding("shift_jis"));

                //書き込む
                sw.Write(line);
                //閉じる
                sw.Close();
                result = "OK";
            }
            catch (Exception e)
            {
                result = "NG:" + e.ToString();
                Console.WriteLine("エラー： " + e.ToString());

            }
            finally
            {
                //閉じる
                if (sw != null) sw.Close();
            }

            return result;
        }
```
指定日より前の行を削除します。
CSVファイルの1列目に登録日時が格納されている前提です。
```c#:C#
        public string _DeleteLinesExcept(string fullPath,string remainDay)
        {
            /*-------------------------------------------------------------------------------------
             *  【処理】
             *  
             *  指定したファイル(fullPath)がある場合、
             * 　    ⇒残す日(remainDay 例."yyyy/MM/dd")より前のレコードを削除する。
             * 　    
             *  指定したファイル(fullPath)がない場合、
             * 　    ⇒スキップ。スキップの場合の戻り値はOK。
             * 　    
             *  [条件]
             *      ファアイルの1列目に日付が入っていること。　
             *          
             *  例)
             *   remainDay:　"2023/08/06"   ←この日より古い行を削除する。    
             *      
             ------------------------------------------------------------------------------------*/

            string result = "NG";

            //ファイル読み込みの定義
            System.IO.StreamReader sr = null;

            //一時ファイルのパス(一旦このファイルに完成形を書込み、最後に元ファイル名にリネームする。) 
            string tmpPath = fullPath.Replace(".csv", "TMP.csv"); 
            //一時ファイル書き込みの定義
            System.IO.StreamWriter sw = null;

            try
            {
                sr = new System.IO.StreamReader(fullPath, Encoding.GetEncoding("shift-jis"));
                sw = new System.IO.StreamWriter(tmpPath, false, Encoding.GetEncoding("shift-jis"));

                bool isFirstLine = true;
                //内容を一行ずつ読み込む
                while (sr.Peek() > -1)
                {
                    //一行読み込む
                    string line = sr.ReadLine();
                    var splitted = line.Split(',');

                    //Console.WriteLine(line);
                    //Console.WriteLine(splitted[0].ToString());

                    //ヘッダー処理(ヘッダーなので残す。)
                    if (isFirstLine == true)
                    {
                        sw.WriteLine(line);　    //一時ファイルに書き込む
                        isFirstLine = false;
                        continue;
                    }

                    //レコード処理
                    DateTime 利用日時 = DateTime.Parse(splitted[0].ToString());  //1列目のデータを取りだす。
                    if (利用日時 < DateTime.Parse(remainDay))
                    {
                        //Console.WriteLine("削除");
                    }
                    else
                    {
                        sw.WriteLine(line);　    //一時ファイルに書き込む
                        //Console.WriteLine("残");
                    }
                }

                //閉じる
                sr.Close();
                sw.Close();

                //一時ファイルと入れ替える
                System.IO.File.Copy(tmpPath, fullPath, true);
                System.IO.File.Delete(tmpPath);

                result = "OK";
            }
            catch (Exception e)
            {
                //MessageBox.Show("【NG】不要データ削除の削除に失敗しました。\n\n" + e.ToString());
                result = "NG:" + e.ToString();
            }
            finally
            {
                //閉じる
                if (sr != null) sr.Close();
                if (sw != null) sw.Close();
            }

            return result;
        }
```

### ８５-４．xmlファイル操作
これ便利！
```xml:xml
[FileInfo.xml]
<?xml version="1.0" encoding="utf-8" ?> 
<!--"utf-8" 又は"shift_jis"を適宜選択のこと-->
<Data>
  <FileInfo>
      <OriginalFullPath>C:\Users…なんとかかんとか</OriginalFullPath>
      <MonitorPath>\\…なんとかかんとか</MonitorPath>
      <MonitorFile>ファイルネーム</MonitorFile>
  </FileInfo>
</Data>  
```

```c#:C#
        private void XMLファイル読み込み()
        {
            //【前提】実行ファイルと同じフォルダにxmlファイルが存在する。
            //自分自身(実行ファイル)のパスを取得する
            string appPath = System.Windows.Forms.Application.StartupPath;
            //XMLファイルのフルパス作成
            string fullPath = appPath + @"\FileInfo.xml";

            //xmlファイルを指定する
            XElement xml = XElement.Load(fullPath);
            //メンバー情報のタグ内の情報を取得する
            IEnumerable<XElement> infos = from item in xml.Elements("FileInfo") select item;

            //メンバー情報分ループして、コンソールに表示
            foreach (XElement info in infos)
            {
                Console.WriteLine(info.Element("OriginalFullPath").Value);
                Console.WriteLine(info.Element("MonitorPath").Value);
                Console.WriteLine(info.Element("MonitorFile").Value);
            }
        }
```






### ９０．フォームの表示
モーダル、モードレスの場合
```c#:C#
            this.Hide();    //自分を隠す-----------------

            //Formクラスのインスタンスを作成する
            Form名称 f = new Form名称();

            //【モードレス】
            //f.Show();

            //【モーダル】
            f.ShowDialog(this);     //オーナーウィンドウにthisを指定する
            //フォームが必要なくなったところで、Disposeを呼び出す
            f.Dispose();

            this.Show();    //自分を現す------------------
```

### 最後．クラス(フォーム)間の値受け渡し


直ぐに忘れるのでメモ、メモ…

①親子関係の無いフォームから親のプロパティを参照する場合(汎用的方法)
```c#:親フォーム
//親インスタンスの定義とプロパティ定義
private static FormMain formMainInstance;
//値の設定はFormMain_Loadにて　⇒　FormMain.formMainInstance = this;) 
public static FormMain _FormMainInstance
{
    get { return formMainInstance;}
    private set   { formMainInstance = value; }
}

//プロパティ定義
private string user
public string _User     
{
    get    { return user; }    //値は別途代入
    private set { }                //空白
}

private void FormMain_Load(object sender, EventArgs e)
{
     //インスタンスの代入
     FormMain.formMainInstance = this;
}
```
```c#:モードレス表示フォーム、孫フォーム
string User  = FormMain._FormMainInstance._User;
```

②モーダル表示された子フォームから親フォームのプロパティを参照する場合のみ
```c#:親フォーム
//プロパティ定義
public string pubUser     
{
    get    { return User; }    //値は別途代入
    private set { }                //空白
}
```
```c#:モーダル表示された子フォーム(Form子)
string User = ((Form親)this.Owner).pubUser;
```