特集:C# 3.0入門 特別編

C#で実感する「LINQ to XML」の素晴らしさ

株式会社ピーデー 川俣 晶
2009/01/16
Page1 Page2

LINQ to XMLというブレークスルー

 その答えは、ズバリ「LINQ to XML」ということになる。LINQ to XMLとは、LINQというフレームワークをXMLに対応させたもの……という理解では不十分である。それは、単なるクエリのフレームワークを超えて、XML文書を扱うAPIそのものの大幅な改善手段として用意されている。

 ただし、E4Xのように、言語仕様にXML対応を組み込むほどの過激さは見せていない。言語仕様はあくまでXMLに対して独立を貫いている。だが、その範囲内で可能な限りコンパクトにソース・コードを記述できるように、さまざまな工夫が凝らされている。

 実際に、上記のリスト1やリスト2と同じ機能を記述したサンプル・コードを見てみよう。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Xml.Linq;

class Program
{
  (XML文書の定義はリスト1と同じなので省略)

  static void Main(string[] args)
  {
    var doc = XElement.Parse(xmldoc);
    XNamespace ex =
        "http://www.piedey.co.jp/example/linqtoxml200811";

    var query = from n in doc.Descendants(ex + "name")
                where n.Attribute("id").Value == "M"
                select n;

    foreach (var elem in query)
    {
      Console.WriteLine(elem.Value);
    }
  }
}
リスト5 LINQ to XMLでリスト1を書き直した例

 まず長い名前がコード上から消えたことを確認しよう。最も長いものが「Descendants」の11文字、次が「XNamespace」の10文字である。この程度なら、長さで苦痛を感じることはない。ちなみに、Descendantsメソッドは、子孫要素のうち指定された名前の要素を列挙する機能を持つ。この場合はドキュメントのルート要素以外のすべての要素が対象になる。

 次に、暗黙の型変換や演算子のオーバーロードが活用されている点に注意を払おう。例えば、以下の行はURIが含まれている文字列を XNamespace型オブジェクト(XML名前空間を保持するオブジェクト)に変換するXNamespaceクラスのImplicit演算子によって動作する。

XNamespace ex = "http://www.piedey.co.jp/example/linqtoxml200811";

 この記述のメリットは、XPath式を使った例でこれに相当する次のような行と比較すると分かるだろう。

nsmgr.AddNamespace(
    "ex", "http://www.piedey.co.jp/example/linqtoxml200811");

 そう。ここで「ex」というキーワードは、文字列ではなく変数名になったのである。ということは、名前の不整合はコンパイル時にチェック可能となったのである。

 もう1つ、リスト5の「ex + "name"」という部分にも注目してほしい。これは足し算を行っているわけではなく、XNamespace オブジェクトとローカル名を結合して、XName(XMLで使われる名前を保持するオブジェクト)を作成するXNamespaceクラスのAddition演算子を実行させているものである。つまり、「名前空間URI+ローカル名」を持つ名前オブジェクトを作り出しているわけである。

 これを見て、「加算を意味する“+”をそれ以外の目的で使うのはソース・コードの直感的な分かりやすさを損なうからイカン」と怒り心頭の読者も多いと思うが、そのように脊髄(せきずい)反射的に怒るのは早計というものだ。

 なぜなら、この「名前の生成」という処理は大量に書き込む可能性が高いからだ。それ故に以下のことが要求される。

  • 出現回数が多いので、書く手間が少なく、簡潔な表現が望ましい
  • 扱う回数が多いので、プログラマーはすぐに表記に慣れる。パッと見の分かりやすさよりも、簡潔さが優先される
  • 頻出する表記なので書き間違いが入り込む可能性も高い。より的確なエラー・チェックができるとよい
  • 名前の生成はたいていの場合、本質的に重要な処理ではない。なので、重要な処理が目立つよう、シンプルな表記が好ましい

 具体的に以下の例を見ると、このような説明の趣旨が何となく分かるのではないかと思う。これは、LINQ to XMLのAPIを用いてXML文書を生成するサンプルである。

using System;
using System.Xml.Linq;

class Program
{
  static void Main(string[] args)
  {
    XNamespace ex =
        "http://www.piedey.co.jp/example/linqtoxml200811";

    XElement doc =new XElement(ex + "person",
      new XElement(ex + "name", "Wong Fei Fong"),
      new XElement(ex + "age", "18"),
      new XElement(ex + "address", "Village of Lahan")
    );
    Console.WriteLine(doc.ToString());
  }
}
リスト6 名前を生成する“+”の多用

<person xmlns="http://www.piedey.co.jp/example/linqtoxml200811">
  <name>Wong Fei Fong</name>
  <age>18</age>
  <address>Village of Lahan</address>
</person>
リスト6の実行結果

 名前を生成する“+”は4回出現しているが、いずれも異なる結果を得るためのもので、生成した値を使い回すことはできない。つまり、名前の生成処理の回数そのものは減らすことはできない。逆に、これらの名前生成は、あくまで主役ではなく「new XElement」で生成される要素の名前指定としての役割を果たすだけである。簡潔に目立たず、書き間違えれば確実にエラーを出力する役割を担って記述されている。

 このように、実際に手を動かしてコードを記述し続けるという状況から見れば、+演算子で名前を生成する仕掛けは使い勝手がよいのである。

 しかし、このサンプル・コードにはもう1つ別の見どころがある。恐らく、DOMに精通したプログラマーであれば、このコードはかなり意外性があると思う。この結果を得るにしては、あまりにも簡潔すぎるのである。

単純化されたXML文書生成

 LINQ to XMLの恩恵は、何もクエリだけで発揮されるわけではない。上記リスト6のサンプル・コードは、単にXML文書を生成するだけのプログラムだが、DOMを使って正攻法でXML文書を構築する方法と比較して、大幅なソース・コードの簡素化が図られている。

 実際に以下のリスト7と比較すると、いかに改善されているかが分かるだろう。

using System;
using System.Xml;

class Program
{
  static void Main(string[] args)
  {
    string ns = "http://www.piedey.co.jp/example/linqtoxml200811";

    var doc = new XmlDocument();
    var person = doc.CreateElement("person",ns);
    doc.AppendChild( person );

    var name = doc.CreateElement("name",ns);
    name.InnerText = "Wong Fei Fong";
    person.AppendChild( name );

    var age = doc.CreateElement("age",ns);
    age.InnerText = "18";
    person.AppendChild( age );

    var address = doc.CreateElement("address",ns);
    address.InnerText = "Village of Lahan";
    person.AppendChild( address );

    Console.WriteLine(doc.OuterXml);
  }
}
リスト7 DOMにより正攻法で記述した例

<person xmlns="http://www.piedey.co.jp/example/linqtoxml200811"><name>Wong Fei Fong</name><age>18</age><address>Village of Lahan</address></person>
リスト7の実行結果

 これを見て、嫌になった読者もいると思うし、自分ならこのようなコードは書かないと思った読者もいると思う。もちろん、筆者も通常の開発であればこのようなコードは書かない。

 ちなみに、どのような仕掛けで簡潔なXML文書の生成ができるのか、構造を簡単に紹介しておこう。

 XElementクラスのコンストラクタはいくつかのバリエーションがあるが、person要素生成で使用しているもの(リスト6の「new XElement(ex + "person",……」の部分)は任意の数の引数を受け取ることができる。最初の引数は名前だが2番目以降は、属性(XAttribute)、要素(XElement)、テキスト(string)をいくつでも並べることができ、それらが順に子ノードとして生成される。

 これにより、コンストラクタの引数に列挙するだけで、必要な子ノードをすべてまとめて生成することができる。もちろん、それらの子ノードのコンストラクタも同じように子ノードを列挙できるので、ネストした形でたった1回のコンストラクタ実行式を書けば、それだけで任意のXML文書を生成させることができる。

 このような工夫のおかげで、C# 3.0はECMAScriptやVisual Basicと異なり、XML専用の構文を導入していないにもかかわらず、従来とは比較にならないほど簡潔にXML文書を処理するコードを記述可能となっている。

まとめ

 従来、C#は比較的XMLを扱いやすい言語であった。標準でXML関連のクラス・ライブラリが提供され、文字列もISO 10646/Unicodeであり、文字のエンコードもXMLと同じIANA名準拠だったため、安心してXML文書を扱うことができた。また、DOMにも初期のころからW3C非準拠の便利な機能が追加されていて、コーディング時のストレス感もそれほど大きくはなかった。

 しかし、それでもC#でXMLを扱うとき、筆者の場合、一息ついて「やるぞ」と気合いを入れる必要があったのも事実である。だから、XMLを扱う機能は特定のクラスに集約し、ソース・コードの大半はXMLのことを忘れて記述できるような構造をいつも採っていた。これがC# 2.0までの状況であった。しかし、その風向きはC# 3.0になって完全に変わった感がある。LINQ to XMLがあれば、気合いを入れなくとも、気楽にXMLを扱うコードが書けるのである。

 これにより、従来はXMLを使わねば処理できない部分だけにXMLを使っていたが、これからはXMLを使わなくとも処理できる部分にもXMLを使っていくことになるかもしれない。そのような構造を採ることは、データをプログラム外に持ち出しやすくなることを意味し、ネットワーク経由での分散処理の時代への1つの布石になるかもしれない。End of Article

 

 INDEX
  C# 3.0入門 特別編
  C#で実感する「LINQ to XML」の素晴らしさ
    1.XML最大の災厄/DOMの憂うつ/E4XのXMLサポート
  2.LINQ to XMLというブレークスルー/単純化されたXML文書生成


Insider.NET フォーラム 新着記事
  • 第2回 簡潔なコーディングのために (2017/7/26)
     ラムダ式で記述できるメンバの増加、throw式、out変数、タプルなど、C# 7には以前よりもコードを簡潔に記述できるような機能が導入されている
  • 第1回 Visual Studio Codeデバッグの基礎知識 (2017/7/21)
     Node.jsプログラムをデバッグしながら、Visual Studio Codeに統合されているデバッグ機能の基本の「キ」をマスターしよう
  • 第1回 明瞭なコーディングのために (2017/7/19)
     C# 7で追加された新機能の中から、「数値リテラル構文の改善」と「ローカル関数」を紹介する。これらは分かりやすいコードを記述するのに使える
  • Presentation Translator (2017/7/18)
     Presentation TranslatorはPowerPoint用のアドイン。プレゼンテーション時の字幕の付加や、多言語での質疑応答、スライドの翻訳を行える
@ITメールマガジン 新着情報やスタッフのコラムがメールで届きます(無料)

注目のテーマ

Insider.NET 記事ランキング

本日 月間