« [SL3] Silverlight で RSS リーダーを作る | トップページ | [.NET] WPF は ILMerge できない (続) »

2010年1月20日 (水)

[.NET] private な拡張メソッドでメソッド チェーン

拡張メソッド (C#, VB) は、 .NET Framework 3.5 から使えるようになった機能です。 元のクラスを修正することなく、 あるクラスに新しいメソッドを追加できる (厳密に言えば違うけど) ので、 けっこう便利です。 実際、 LINQ は、 拡張メソッドに大きく依存しています。 (というか、 LINQ のために拡張メソッドを導入する必要があったんじゃ?)

ところが、 拡張メソッドは濫用すると混乱を招きます。 開発チームのメンバーが勝手に拡張メソッドを作り始めたらどうなるでしょう? たとえば string クラスの拡張メソッドを、 A君はアセンブリ a に入れ、 B君はアセンブリ b の中に作ったり。 あるいは同じ名前の拡張メソッド、 たとえば string.WordCount() が複数出来てしまったり。
そこで、 拡張メソッドを作るときのルールを決めて、 チーム内できちんと運用する必要が出てきます。 「思いつき」で、 ぱっぱっと拡張メソッドを実装することは許されなくなります。

拡張メソッドを使うとメソッド チェーンが書けます。 しかし、 勝手に拡張メソッドを実装できないルールの下では、 「あ、 ここはメソッド チェーンにしたい!」 と思っても事実上不可能です。 拡張メソッドを追加するためのチーム内の手続きを行う時間よりはるかに早く、 メソッド チェーンを使わない実装が書けてしまうからです。

ですが private な拡張メソッドなら、 どうでしょう? メソッド チェーンを書きたいクラスの中に、 private な拡張メソッドを定義して使う。 それなら混乱を招くことは無いのではないでしょうか。
そのためには static なクラスにしなければならないので、 採用できる場面はあまり多くないとは思いますけれど。 ひとつサンプル コードを提示しておきます。

とある処理をするためのクラスがあって、 それは内部状態を持たず、 渡されたデータを加工するだけで、 継承させたり DI したりする必要性も考えられないので、 static なクラスにしてしまってよいとします。
次の例は、 RSS フィードの XML データを受け取って、 SyndicationFeed オブジェクトに加工するものです。

internal static class RssConverter
{
    internal static SyndicationFeed ConvertFromRss20(string rss)
    {
        SyndicationFeed feed;
        using (StringReader stringReader
                = new StringReader(rss)){
            XmlReader xmlReader
                = XmlReader.Create(stringReader);
            feed = SyndicationFeed.Load(xmlReader);
        }

        feed = AdjustTimeOffset(feed);
        feed = RemoveBTagFromTitle(feed);
        return feed;
    }
    //以下省略…

渡されたデータ rss を SyndicationFeed オブジェクト feed に変換するだけでなく、 さらにデータ内の時差の調整と、 タイトル文字列から <b> タグの削除処理を行っています。 その度にいちいち feed という変数に受け取っています。
これを、 メソッド チェーンの形に書き直せば、 Load() して AdjustTimeOffset() して RemoveBTagFromTitle() する、 という処理の連鎖が一目で分かるようになります。

internal static SyndicationFeed ConvertFromRss20(string rss)
{
    using (StringReader stringReader
            = new StringReader(rss)) {
        XmlReader xmlReader
            = XmlReader.Create(stringReader);

        return SyndicationFeed.Load(xmlReader)
                    .AdjustTimeOffset()
                    .RemoveBTagFromTitle();
    }
}

private static SyndicationFeed AdjustTimeOffset(this SyndicationFeed feed)
{
    TimeSpan localOffset
        = TimeZoneInfo.Local.BaseUtcOffset;
    foreach (SyndicationItem item in feed.Items) {
        item.PublishDate
            = item.PublishDate.ToOffset(localOffset);
    }
    return feed;
}

private static SyndicationFeed RemoveBTagFromTitle(this SyndicationFeed feed)
{
    foreach (SyndicationItem item in feed.Items)
    {
        TextSyndicationContent title = item.Title;
        string titleText
            = Regex.Replace(
                title.Text,
                "</?b>",
                string.Empty);
        item.Title
            = new TextSyndicationContent(titleText);
    }
    return feed;
}

なお、 それぞれの拡張メソッド内で、 同じ foreach ループを廻しています。 理解のしやすさ、 メンテのしやすさから、 このように書いています。 これで実際にパフォーマンスに問題が出たのならば、 そのときはもちろん書き直すべきですが、 数百件程度のデータならまず問題になりません。 それよりも、 最初のメソッド ConvertFromRss20() で、 データにどのような加工をしているかがすぐに分かることが重要だと思います。

|

« [SL3] Silverlight で RSS リーダーを作る | トップページ | [.NET] WPF は ILMerge できない (続) »

プログラミング」カテゴリの記事

コメント

この記事へのコメントは終了しました。

トラックバック


この記事へのトラックバック一覧です: [.NET] private な拡張メソッドでメソッド チェーン:

« [SL3] Silverlight で RSS リーダーを作る | トップページ | [.NET] WPF は ILMerge できない (続) »