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」という型が作られる。これを真似すると、遅延実行ができるはず(ネストした時に遅くならずにすむ)