Graphics#DrawStringとTextRenderer.DrawTextとTextOut

.NETで文字列をコントロールに描画するためのメソッドとして一番使うのはGraphicsクラスのDrawStringメソッドだと思います。
ほかにもSystem.Windows.Forms名前空間にTextRendererというクラスがありそのstaticメソッドであるDrawTextというメソッドを使うという方法もあります。
もうひとつ、これは.NETではないのですがWin32APIではよく使うTextOutという関数もコントロールに文字列を描画することができます。


最後のTextOutは少し違いますが、これだけ種類があるということはそれぞれに使いどころがあるということです。
実際TextRendererのDrawTextメソッドは印刷をサポートしていません。(詳細はMSDNを参照してね)


今回はそれぞれの詳細に踏み込むのではなく、単純にコントロール上に文字列を描画する速度を測ってみます。
早い話がどれが一番速くてどれが一番遅いのかということです。


テスト方法は単純。
"abcdefghijklmnopqrstuvwxyz"という文字列を各メソッドで一定回数描画するのにかかる時間を計るだけです。特別なフォーマット指定はナシです。
ちなみに今回は10000回の描画にかかる時間を計りました。


まずは結果。

メソッドかかった時間
Graphics#DrawString約1100ms
TextRenderer.DrawText約800ms
TextOut約40ms
ダントツでTextOutが速いです。比べ物になりません。
次いでDrawText、最後にDrawStringの順です。
より制約が大きく汎用的でないものが少し速いという結果でしょうか。


次に表示結果。

上から、
・Graphics#DrawString
・TextRenderer.DrawText
・TextOut
の表示結果です。


見てわかるように、TextOutだけ左に寄っています。
もちろん指定しているx座標はすべて同じなのですが、内部での扱いが少し違うのかもしれません。
フォントはすべて同じものを指定しているので、これはすべて同じ見た目になっています。


最後にテストに使ったコードの抜粋

//Graphics#DrawStringの実験コード
Graphics grfx = panel1.CreateGraphics();

String str = "abcdefghijklmnopqrstuvwxyz";
Brush brush = new SolidBrush(this.ForeColor);
Point pt = new Point();

StopWatch.Start();

for(int i = 0;i < count;++i)
	grfx.DrawString(str, this.Font, brush, pt);

StopWatch.Stop();
grfx.Dispose();

MessageBox.Show(StopWatch.time.ToString());

//TextRenderer.DrawTextの実験コード
Graphics grfx = panel1.CreateGraphics();

String str = "abcdefghijklmnopqrstuvwxyz";
Point pt = new Point(0,30);

StopWatch.Start();

for(int i = 0;i < count;++i)
	TextRenderer.DrawText(grfx, str, this.Font, pt, this.ForeColor);

StopWatch.Stop();
grfx.Dispose();

MessageBox.Show(StopWatch.time.ToString());

//TextOutの実験コード
Graphics grfx = panel1.CreateGraphics();

String str = "abcdefghijklmnopqrstuvwxyz";
int len = str.Length;
IntPtr hDC = grfx.GetHdc();
IntPtr hFont = this.Font.ToHfont();
IntPtr hOldFont = SelectObject(hDC, hFont);
			
StopWatch.Start();

for(int i = 0;i < count;++i)
	TextOut(hDC, 0,60, str, len);

StopWatch.Stop();

DeleteObject(SelectObject(hDC, hOldFont));
grfx.ReleaseHdc(hDC);
grfx.Dispose();

MessageBox.Show(StopWatch.time.ToString());

ちなみにTextOutの実験コードの部分で使っているSelectObjectとTextOutとDeleteObjectはネイティブな関数なので別な場所でDllImportしてあります。


総評として、
速度的にはTextOutが文句なしの1位です。
ですがこれはあくまでネイティブな関数を使っているからであり少し反則的かもしれません。
Graphics#DrawStringとTextRenderer.DrawTextは少し速度差が出ましたが、実験内容にもあるようにこの結果は10000回描画するのにかかった時間です。
つまり1回や2回の描画では正直大差ないといわけです。
普通の使い方をする場合そこまで気にして神経をとがらせてしまっては胃に穴があいてしまいます。


それにTextOutでは細かいStringFormat指定ができないので、細かなフォーマット指定が必要な場合は他の二つを使う必要があります(ネイティブにもDrawText関数というものがありますが)。


結論としてどれを使ってもいいんじゃない?という元も子もないようなものになってしました。
ではなぜわざわざこのような実験をしたのかというと、先に書いたように1回や2回文字列を描画する普通の使い方をする場合は気にしなくてもいいのですが、ちょっと普通じゃない使い方が必要なプログラムを最近書いたからです。


そのプログラムでは猛烈に文字列出力をしまくるのですが、詳細はいずれまた。
という感じのお茶を濁した感じで今回は終わり。