ExDoc を Silverlight でも動くようにちまちまと書き換えている途中。
moonmile/ExDoc – GitHub
https://github.com/moonmile/ExDoc
既に NUnit を使ってテストコードを書いていたものを MSTest に書き換えています。本当は NUnit のまま動かしたかったのですが、Silverlight 上で動かん…と言うか、MSTest の場合も Siliverlight のクラスライブラリは対象にしていない(???)という感じで、結局、Visual Studio 2010 の単体テストコードの雰囲気を感じるのも兼ねて、MSTest を利用しています。
さて、その中でちょっと面白いテストコードを追加しました。
public void TestEqualTwoValueLinq() { EXDocument doc = new EXDocument(); doc.LoadXML(@" <members> <person name='masuda' age='40'>masuda tomoaki</person> <person name='yamada' age='20'>yamada taro</person> </members> "); var els = from t in doc / "members" / "person" where t % "name" == "masuda" && t % "age" == "40" select t; EXElement el = els.First(); Assert.AreEqual("masuda tomoaki", el.Value); }
ExDoc は、暗黙のキャストと演算子のオーバーライドを派手にやっていて、「==」演算子もオーバーライドして EXElements クラス(要素のコレクションクラス)を返すようにしています。このコレクションは、List で定義してあるので、List ジェネリックのメソッドが使えます…つーか、LINQ の文法に直接載せることができます。
じゃあ、LINQ to XML で記述したほうがいいのではないか?という話は脇において。
ExDoc は、直観的にXMLのツリー構造を辿れることを目的としているので、「doc / “members” / “person”」というのは、XPath で云えば「/members/person」と同じことです。
じゃあ、XPath で書けばいいじゃん!という話は脇において。
属性を比較する場合は、「t % “name” == “masuda”」のように書けるのがミソです。
なので、ExDoc と LINQ を組み合わせて、上のような不思議な文法で書けます。
ちなみに、単一の属性の比較の場合は、一行で書けます。
public void TestEqualValue() { EXDocument doc = new EXDocument(); doc.LoadXML(@" <members> <person name='masuda'>masuda tomoaki</person> <person name='yamada'>yamada taro</person> </members> "); EXElements els = doc / "members" / "person" == "yamada taro"; Assert.AreEqual(1, els.Count); Assert.AreEqual("yamada", (string)(els % "name")); EXElement el = doc / "members" / "person" == "yamada taro"; Assert.AreEqual("yamada", (string)(el % "name")); }
まぁ、このあたりのワンライナーが作りたかったのが ExDoc の目的です。
ただし、実装的に毎回 foreach で検索して EXElemnt の配列を返しているのでパフォーマンスは良くないんですよね。LINQ の実装と同じく、遅延実行をするようにして
- 「/」演算子で取得する部分は、イテレーターの作成のみ
- string 型にキャストするか、Value プロパティを参照した時に、探索を実行する。
という工夫が必要なはなずです。まぁ、それは後程。
再考すると「where t % “name” == “masuda”」のところの「==」演算子は、LINQ のもので、オーバーライドしたものではない。「%」演算子は t の型(EXElements型)のものを使うけど、「==」演算子は LINQ で解析されたものを使う?のだと思う。ちょっと不思議。
ちなみ、EXElements 型の「==」演算子は、bool を返さずに EXElements を返すようにしてある極悪品w。なので、if ( t % “name” == “masuda” ) のようなことはできないのだが…うーん、bool 型に暗黙に変換されるようにすれば、これも OK なのか?
LINQ は 「System.Linq.Enumerable.WhereListIterator」という型が作られる。これを真似すると、遅延実行ができるはず(ネストした時に遅くならずにすむ)