テキストボックスとInt32型のバインディング [プログラム]
M-V-VM関係の話をよく聞くのでテスト。
TextBoxふたつはっつけて,ボタンを押すと割り算の結果をTextBlockに表示するだけの簡単なプログラム。
あれ?intを直接バインドすると,"a"とかの時に,確認するすべがない……というのは困るのでさがしていると,八巻さん@グレープシティのデブサミ資料を発見。 変換は,ViewModelでやるのか。なるほど。
やってると,Windows Formsアプリケーションとも共通化したくなりますが,ICommandインターフェースがWPFのものだし,いろいろ対応していないので断念。 ここらへんだけは,WinFormsでは手で書かないといけなさそうです。 でも,WinFormsでも,M-V-VMっぽいことはできそう。
C#とWordとPowerPointと [プログラム]
あいも変わらずC#でWordとPowerPointをいじくっています。
さて,要求事項にWord 2000-2007対応とかあって,その中の文字列引っ張ってくるだとか,該当する箇所の背景色かえるだとか,起動中はWord閉じるなとかの要求があるわけです。
まず,Word 2000と2002 (XP)以降で,Canvasの存在の有無という違いがあります。 また,Word 2002およびそれ以前とWord 2003および以降では,各種イベントのキャンセルがC#からできるかという違いもあります。
そこで採った方法が……ラッパライブラリ。当然,各バージョン用。バージョンによってget_ItemだったりItemだったりインデクサだったり……。 さらに,object以外に共通クラスが無いので,各バージョン用にファイルごとコピペ……。
まぁ,WordとPowerPointを弄っていると,双方の考え方の違いが分かってきます。
WordはMissing.Value,つまりはNothingを多用し,ref objectな引数たくさん。インデクサの実装とかでは結構面倒なことも多いです。 それに対して,PowerPointはrefを基本的に使いません。2値にはBooleanではなくMsoTriStateを使います。
このあたりの思想の違い,いまさら整合はとれないのでしょうが……。
PowerPointのオブジェクトは基本的にSnapshot [プログラム]
COMを使ってPowerPointを制御するプログラムを作っているのですが,TextRangeオブジェクトを弄っていると,問題発見。
ひとつのTextRangeのインスタンスからCharactersメソッドで複数の部位のTextRangeのインスタンスを作成し,真ん中あたりのTextRangeのTextプロパティに値を設定しました。 結果,そのインスタンスより後ろのインスタンスの値がずれました (StartやCountが不正になっている)。 また,場合によっては,自インスタンスの値すらずれることが……。
結局の所,PowerPointのTextRangeは取得した時点のSnapshotということみたいです。 まぁ,±2の範囲を調べて調整するようにして,自力修正をすることで対応しましたが……。
XNA GS 3.0 [プログラム]
XNA Game Studio 3.0が出たということで,インストールしてみました。
で,.NET Framewok 3.5ベースなので,LINQが使えるはず。試してみました……LINQ to Object,XBox 360実機でもOKでした。どこで使うんだ……。
XNAなのにSQL Server Compact実装して,LINQ to SQLが使えたら……笑うしかないですけどね。
コードカバレッジ気にしたらコードがひん曲がった [プログラム]
VS 2008のテストの,コードカバレッジ機能使って,未テスト箇所を作らないようにしていったのですが……。
Enum testValue = null; foreach (Enum enumValue in Enum.GetValues(type)) { testValue = worker.DynamicInvoke(value, enumValue, testValue); }
なんてコードで未テスト箇所が2箇所発生。 ちなみに,typeは任意のEnum型 (Enum型の引数valueに対して,GetType()で取得した),workerはLambdaExpression.Compileの結果。
コメントアウトしながら探すと,どうもforeach自体が問題っぽい。 foreachはIDisposableとか,色々中でやっているからなぁ,とか思いつつ,強制的に未テスト箇所を潰してやれとばかりに,できあがったコードが……。
Enum testValue = null; foreach (Enum enumValue in (Enum.GetValues(type) as IEnumerable).OfType<Enum>()) { testValue = worker.DynamicInvoke(value, enumValue, testValue); }
何かを間違えた気がしてなりません……。 というか,foreachの中で未テスト箇所作られても困るのですが……。
VS2008のテスト機能を試す [プログラム]
Visual Studio 2008のテスト機能を使ってみているのですが,なんか,普通に使う分にはNUnitとかわらないような……。 まぁ,統合されている分の優位性もあるとは思いますが,Constraint Modelが使えないのが非常に悲しい……。
統合されているとはいえ,TestDriven.NETは必須ですね。 メソッド一つの単位で,エディタ上から選択出来るのが便利です。
Connectにあげた [プログラム]
%入りのフォントはWPFで使えないの内容ですが,WPFとExpressionの両方にフィードバックしました。 あまりこれに引っかかる方はいないでしょうけれど,引っかかっている方 (一太郎をお使いの方など) は,是非ともVoteをお願いします。
%入りのフォントはWPFで使えない [プログラム]
一太郎などに付いてくるフォントに,"%Century Oldstyle"というものがあります。 これを,WPFでFontFamilyに指定すると,System.UriがUriFormatExceptionを起こします。
%をUri用にエスケープして,"%25Century Oldstyle"としてやると,何事もなかったように通ります。 例外スタックを見る限り,Uri使ってキャッシュしているようです。
それはそうと,Expression Blendのフォントの指定,上記の理由から全て落ちます。 原因は,ほぼ%Century Oldstyleで確定なのですが……。
UriのQueryのパース [プログラム]
ExtremeSwankとOpenIDとHttpListenerRequest書いた時は見つけていなかったので,Uriのクエリ部分のパースを自力で書いたのですが,実は,HttpUtility.ParseQueryStringという便利なメソッドがありました。 これを使えば,件のコードよりもまともな解析ができるはずです。
ORDER BYの速度 [データベース]
環境
- CPU
- Core 2 E6600
- Physical Memory
- 2GB
- OS
- Windows XP SP3
- MySQL Version
- 5.0.27
- MySQL Table Type
- InnoDB
- .NET Framework Version
- 2.0(SP1)
- ADO.NET Provider
- Connector/Net 5.1.6
MySqlのバージョンが古いのは,単に昔やったプロジェクトで入っていたものを再利用しているからです。
前準備
- こんなテーブルを作る。
CREATE TABLE test1 ( ID INT PRIMARY KEY, Value1 INT NOT NUL UNIQUE, Value2 INT NOT NULL )
- test1に,ID = Value1 = Value2になるように,[1, 10000]の値を突っ込む。
計測
- 以下のプログラムを実行する。
using System; using System.Diagnostics; using MySql.Data.MySqlClient; class Program { static readonly string[] _commands = { "SELECT * FROM test1 ORDER BY ID", "SELECT * FROM test1 ORDER BY ID limit 10", "SELECT * FROM test1 ORDER BY Value1", "SELECT * FROM test1 ORDER BY Value1 limit 10", "SELECT * FROM test1 ORDER BY Value2", "SELECT * FROM test1 ORDER BY Value2 limit 10", "SELECT COUNT(ID) FROM test1" }; static void Main (string[] args) { long[] ticks = new long[_commands.Length]; Stopwatch sw = new Stopwatch(); for (int i = 0; i < 10; ++i) { using (MySqlConnection conn = new MySqlConnection( @"Server=localhost;Database=test;Uid=uid;Pwd=pwd")) { conn.Open(); using (MySqlCommand comm = new MySqlCommand("SELECT 10", conn)) { comm.ExecuteNonQuery(); } for (int j = 0; j < _commands.Length; ++j) { string commandStr = _commands[j]; sw.Reset(); using (MySqlCommand comm = new MySqlCommand(commandStr, conn)) { sw.Start(); comm.ExecuteNonQuery(); sw.Stop(); } ticks[j] += sw.ElapsedTicks; } conn.Close(); } } for (int j = 0; j < _commands.Length; ++j) { Console.WriteLine("{0,10}ticks : {1}", ticks[j], _commands[j]); } } }
さて,普通に考えたら,IDとValue1にはインデックスがあるので,ORDER BY Value1の方がORDER BY Value2よりも時間はかからなさそうです。 しかし……。
- SELECT * FROM test1 ORDER BY ID
- 213535503ticks
- SELECT * FROM test1 ORDER BY ID limit 10
- 5208264ticks
- SELECT * FROM test1 ORDER BY Value1
- 465056280ticks
- SELECT * FROM test1 ORDER BY Value1 limit 10
- 5790636ticks
- SELECT * FROM test1 ORDER BY Value2
- 283178781ticks
- SELECT * FROM test1 ORDER BY Value2 limit 10
- 120534615ticks
- SELECT COUNT(ID) FROM test1
- 68597829ticks
……なんでValue2でソートした方が速くなるんだ……。念のためExplainしてみましたが,
id select_type table type possible_keys key key_len ref rows Extra 1 SIMPLE test1 index NULL PRIMARY 4 NULL 10339 id select_type table type possible_keys key key_len ref rows Extra 1 SIMPLE test1 index NULL PRIMARY 4 NULL 10339 id select_type table type possible_keys key key_len ref rows Extra 1 SIMPLE test1 index NULL Value1 4 NULL 10339 id select_type table type possible_keys key key_len ref rows Extra 1 SIMPLE test1 index NULL Value1 4 NULL 10339 id select_type table type possible_keys key key_len ref rows Extra 1 SIMPLE test1 ALL NULL NULL NULL NULL 10339 Using filesort id select_type table type possible_keys key key_len ref rows Extra 1 SIMPLE test1 ALL NULL NULL NULL NULL 10339 Using filesort id select_type table type possible_keys key key_len ref rows Extra 1 SIMPLE test1 index NULL PRIMARY 4 NULL 10339 Using index
Value1はちゃんとインデックス使っている……。最新なら直っているかな?というわけで,5.0.51bを上書きでインストール。結果は……
- SELECT * FROM test1 ORDER BY ID
- 207209592ticks
- SELECT * FROM test1 ORDER BY ID limit 10
- 4652703ticks
- SELECT * FROM test1 ORDER BY Value1
- 472087971ticks
- SELECT * FROM test1 ORDER BY Value1 limit 10
- 5696766ticks
- SELECT * FROM test1 ORDER BY Value2
- 275851026ticks
- SELECT * FROM test1 ORDER BY Value2 limit 10
- 124287543ticks
- SELECT COUNT(ID) FROM test1
- 67732911ticks
変わらない……。データを読ませれば変わるかと,ExecuteNonQueryの呼び出しを以下に修正。
using (MySqlDataReader reader = comm.ExecuteReader()) { while (reader.Read()) { for (int k = 0; k < reader.FieldCount; ++k) { object o = reader[k]; } } }
- SELECT * FROM test1 ORDER BY ID
- 497410938ticks
- SELECT * FROM test1 ORDER BY ID limit 10
- 5393538ticks
- SELECT * FROM test1 ORDER BY Value1
- 535218507ticks
- SELECT * FROM test1 ORDER BY Value1 limit 10
- 6257565ticks
- SELECT * FROM test1 ORDER BY Value2
- 622938447ticks
- SELECT * FROM test1 ORDER BY Value2 limit 10
- 124877898ticks
- SELECT COUNT(ID) FROM test1
- 67883031ticks
やっと逆転。でもなんかすっきりしない……。