C# [tip's]

C# (というか.Net Framework) ならではのコーディングに関する覚え書きです。
更新日 2016-02-13

配列・マップ

C# にはJava のような便利な配列・マップクラスがあります。

Dictionary

Dictionary クラスはJava でいうところのHashMap クラスと同等です。
#using System.Collections.Generic;

Dictionary<String, int> dic = new Dictionary<String, int>();
{
	dic.Add( "A", 1);
	dic.Add( "B", 2);

	dic["C"] = 3;
	dic["D"] = 4;
}

マルチスレッド

C# もC++ もマルチスレッドのコーディングは根本的には同じです。

タイマー

旧来のWM_TIMER メッセージを使うタイマー(System.Windows.Forms.Timer クラス)は、もちろんマルチスレッドではありません。 しかしC# では新しく、ワーカースレッドからメソッドを呼び出せるタイマー(System.Threading.Time クラスやSystem.Timers.Timer クラス) が用意されています。

BackgroundWoker コンポーネント

バックグラウンドで動くワーカースレッドを簡単に実装できます。

デリゲートを用いて別スレッドで実行する。

特定のメソッドをデリゲート経由で呼び出す事で簡単にワーカースレッドを作成できます。通常の関数のように 引数も取れます。
private void Form1_Load(object sender, EventArgs e)
{
	Debug.WriteLine("ThreadID = [" + Thread.CurrentThread.ManagedThreadId + "]");

	String[] vecPair = { "USDJPY", "EURJPY", "EURUSD", "AUDJPY" };

	List<IAsyncResult> listIAsyncResult = new List<IAsyncResult>();

	// Startup
	foreach (String strPair in vecPair)
	{
		SubSimulationWorkerDelegate subSimulationWorkerDelegate = 
			new SubSimulationWorkerDelegate(this.SubSimulationWorker);

		// Call procedure		
    	IAsyncResult ar = subSimulationWorkerDelegate.BeginInvoke(strPair);
    	listIAsyncResult.Add(ar);
	}

	// Waiting.
	while (listIAsyncResult.Count > 0)
	{
		if (listIAsyncResult[0].IsCompleted)
		{
			listIAsyncResult.RemoveAt(0);
		}
		else
		{
			System.Threading.Thread.Sleep(50);
		}
	}
}

// Delegate 宣言
public delegate void SubSimulationWorkerDelegate(String strPair);

public void SubSimulationWorker(String strPair)
{
	Debug.Write(strPair + ": ");
	Debug.WriteLine("ThreadID = [" + Thread.CurrentThread.ManagedThreadId + "]");

	Random rand = new Random();
	
	Thread.Sleep(rand.Next(4000) + 4000);
}

コントロールの操作はメインスレッドから

メインスレッドで初期化配置されたコントロールのメンバやプロパティを扱うのに、ワーカースレッドから直接アクセスすると例外が出ます。 これはスレッド間排他処理に起因するバグを事前に回避する目的で、課せられるべくして実装された仕様です。そこでデリゲートの出番です。
IAsyncResult ar = this.BeginInvoke(new MethodInvoker(delegate
{
	this.m_toolStripStatusLabel_OCRText.Text = "Now offline time";
}));
this.EndInvoke(ar);
上記のブロックスコープ内の処理は全てメインスレッドにて実行されます。EndInvoke() で、その処理の完了を待機しています。まともに実装しようと したら大変面倒ですから、このような仕組みが用意されている訳です。

マルチスレッド(.NET4 〜)

マルチスレッドにまつわる煩雑なスレッド管理の実装作業が、.Net4.0 からはついにプログラマから解放されました。実際に作成されるスレッドやマルチコアプロセッサの活用 調整は、OS や.Net フレームワークに一任されます。

Parallel.Invoke() を使う [超おすすめ!]

実にエレガントです。ブロックスコープ単位が、別スレッドで同時実行されるのです。もちろん親スコープ内の変数にも普通にアクセスできます。
void ParallelInvokeTest()
{
    Random rnd = new Random();

    // ブロックスコープ単位で別スレッド
    Parallel.Invoke(
        ()=>
        {
            Thread.Sleep(rnd.Next(100, 500));
            Debug.WriteLine("Job A");
        },
        ()=>
        {
            Thread.Sleep(rnd.Next(100, 500));
            Debug.WriteLine("Job B");
        },
        ()=>
        {
            Thread.Sleep(rnd.Next(100, 500));
            Debug.WriteLine("Job C");
        }
    );
    // 全てのスレッドが完了するまではもちろん待機

    // 関数名を直接記述する事もできる
    Parallel.Invoke( procA, procB, procC);
}

Parallel.ForEach() を使う

コレクションのそれぞれの要素に処理を行いたい場合、foreach 文でシーケンシャルにアクセスします。Parallel.ForEach() では、これが単純に マルチスレッド化されます。結果的に要素に対してはシーケンシャルアクセスでは無くなるので、処理内容によっては利用はできません。
void ParallelForEachTest()
{
    Random rnd = new Random();

    List<int> listValue = new List<int>();
    for (int n = 0; n < 100; n++)
    {
        listValue.Add(n);
    }

    // コレクションの要素毎にスレッドが事なる
    Parallel.ForEach(listValue, (nValue)=>
    {
        Thread.Sleep(rnd.Next(10, 100));
        Debug.WriteLine(nValue);
    });
}

画像

Bitmap クラスで基本のみサポートされてます。

グレースケールにする

C# でもポインタが使えます。高速とは言えませんが、多少はマシな結果が出ます。
Bitmap bmp = new Bitmap("D:\\test.bmp");

BitmapData bmpData = bmp.LockBits(new Rectangle(0, 0, bmp.Width, bmp.Height),
    ImageLockMode.ReadWrite,
    PixelFormat.Format32bppArgb);

unsafe
{
    IntPtr p = bmpData.Scan0;
    void* lpVoid = p.ToPointer();
    int nStride = bmpData.Stride;

    for (int y = 0; y < bmp.Height; y++)
    {
        for (int x = 0; x < bmp.Width; x++)
        {
            int* lpPixel = ((int*)lpVoid + ((nStride / 4) * y) + x);

            int nA = 0xff;
            int nR = (*lpPixel & 0x00ff0000) >> 16;
            int nG = (*lpPixel & 0x0000ff00) >> 8;
            int nB = (*lpPixel & 0x000000ff) >> 0;

            if (!(nR == nG && nG == nB))
            {
                int nGray = (nG * 59 + nR * 30 + nB * 11) / 100;
                if (nGray > 0xff)
                {
                    nGray = 0xff;
                }

                *lpPixel = (nA * 0x1000000) + (nGray * 0x10000) + 
							(nGray * 0x100) + nGray;
            }
        }
    }
}
bmp.UnlockBits(bmpData);

画像が含まれるかどうか判定する

bmpMain の中にbmpPiece と同じ画像があるかどうか調べる
private Boolean IsContainBitmap(Bitmap bmpMain, Bitmap bmpPiece)
{
    Boolean bContain = false;

    if (bmpMain.Width >= bmpPiece.Width &&
        bmpMain.Height >= bmpPiece.Height
       )
    {
        System.Drawing.Imaging.BitmapData bmpDataPiece = bmpPiece.LockBits(
			new Rectangle(0, 0, bmpPiece.Width, bmpPiece.Height),
            System.Drawing.Imaging.ImageLockMode.ReadOnly,
            System.Drawing.Imaging.PixelFormat.Format32bppArgb);

        System.Drawing.Imaging.BitmapData bmpDataMain = bmpMain.LockBits(
			new Rectangle(0, 0, bmpMain.Width, bmpMain.Height),
            System.Drawing.Imaging.ImageLockMode.ReadOnly,
            System.Drawing.Imaging.PixelFormat.Format32bppArgb);

        unsafe
        {
            int nStridePiece = bmpDataPiece.Stride;
            int nStrideMain = bmpDataMain.Stride;

            void* lpVoidPiece = bmpDataPiece.Scan0.ToPointer();
            void* lpVoidMain = bmpDataMain.Scan0.ToPointer();

            int nScanWidth = bmpMain.Width - bmpPiece.Width;
            int nScanHeight = bmpMain.Height - bmpPiece.Height;

            uint*[,] vecPixelSrc_TopLeft = 
            {
                {(uint*)lpVoidPiece, (uint*)lpVoidPiece + 1},
                {(uint*)lpVoidPiece + ((nStridePiece / 4) * 1),
				 (uint*)lpVoidPiece + ((nStridePiece / 4) * 1) + 1}
            };


            for (int nX = 0; nX < nScanWidth; nX++)
            {
                for (int nY = 0; nY < nScanHeight; nY++)
                {
                    if (*((uint*)lpVoidMain + ((nStrideMain / 4) * nY) + nX) == 
							*vecPixelSrc_TopLeft[0, 0] &&
                        *((uint*)lpVoidMain + ((nStrideMain / 4) * nY) + (nX + 1)) == 
							*vecPixelSrc_TopLeft[0, 1] &&
                        *((uint*)lpVoidMain + ((nStrideMain / 4) * (nY + 1)) + nX) == 
							*vecPixelSrc_TopLeft[1, 0] &&
                        *((uint*)lpVoidMain + ((nStrideMain/4) * (nY+1)) + (nX+1)) == 
							*vecPixelSrc_TopLeft[1, 1]
                       )                           
                    {
                        Boolean bFoundDiff = false;

                        for (int nSrcX = 0; nSrcX < bmpPiece.Width; nSrcX++)
                        {
                            for (int nSrcY = 0; nSrcY < bmpPiece.Height; nSrcY++)
                            {
                                uint* lpPixelSrc = ((uint*)lpVoidPiece + 
											((nStridePiece / 4) * nSrcY) + nSrcX);
                                uint* lpPixelDst = ((uint*)lpVoidMain + 
									((nStrideMain / 4) * (nY + nSrcY)) + (nX + nSrcX));
                                if (*lpPixelSrc != *lpPixelDst)
                                {
                                    bFoundDiff = true;
                                    break;
                                }
                            }

                            if (bFoundDiff)
                            {
                                break;
                            }
                        }

                        if (bFoundDiff == false)
                        {
                            bContain = true;
                            break;
                        }
                    }
                }

                if (bContain)
                {
                    break;
                }
            }
        }
        bmpPiece.UnlockBits(bmpDataPiece);
        bmpMain.UnlockBits(bmpDataMain);
    }

    return bContain;
}

ファイル入出力

UTF-8 とBOM

Vista のメモ帳でもStreamWriter でもファイル先頭にはBOM (UTF-8の場合は EF BB BF) が付けられる。FileStream のようなbyte 入出力ではもちろん 勝手には付かないので注意が必要。
// BOM(UTF-8の場合は EF BB BF)
fs.WriteByte(0xEF);
fs.WriteByte(0xBB);
fs.WriteByte(0xBF);

トラブルシューティング

VisualStudio で起こるマイナートラブルについてです。

データヒントが出なくなる

デバッグ時に変数の上にマウスカーソルを乗せるとデータヒント(ツールヒント?)というポップアップが出て内容を確認できます。 これが時として出なくなります。
「現在のコンテキストでは無効です」みたいな場合、一行ステップ実行すると正常に確認できたりします。デバッグで停止時間が長いと 生じたりします。これもどうかと思うんですが・・・。
もう1つ、全然出なくなる事があります。これは大変困るんです。ひどく長い記述のある関数内で、特定のブロックを一時的に無効にしたり すると、その後の記述部でデータヒントが出なくなったりしました。
public void test()
{
	・・・ // とても長い記述 (データヒントが出る)
	
	if (false) // 一時的に無効にしてたり
	{
		・・・ // とても長い記述
	}

	・・・ // とても長い記述 (データヒントが出ない)
}
VisualStudio のデバッガのバグでしょうか。かなり2010 以前のバージョンでもデータヒント関係で悩まされた事があります。マイナートラブル でしょうか。