不透明URIのためのSystem.UriParserの派生クラスを実装する場合の注意点

本記事の内容は，.Net Framework 4.6で動作確認をしています．

# 結論

不透明URIに対しては，`System.GenericUriParser`を基底クラスにするのではなく，`System.UriParser`を基底クラスにするのがよい．

# `new Uri("data:,%30")`の結果がおかしい

まず最初に書いておかないといけないが，`System.Uri`はdata URIスキームをサポートしていない．

ただ，サポートしていないURIスキームであっても，何となく処理してくれる．

```csharp
var uri = new Uri("data:,0");
Console.WriteLine(uri);
// stdout> data:,0
```

ところが，パーセントエンコードするとおかしくなる．

```csharp
// 以下のコードには，パーセントエンコードを実行したときの文字エンコーディングが何だったのかという問題があるが，
// ひとまず無視しておく
// （ちなみに，C#はUTF-8だと思ってパーセントエンコードをデコードするようだ）
var uri = new Uri("data:,%30");
Console.WriteLine(uri);
// stdout> data:%30,0
```

単なるバグだと思うが，どういう理屈でこのような結果になるのか理解に苦しむ．．

やはり非サポートURIスキームなのに，無理やり`System.Uri`に食わせたのがよくない．仕方ないのでちゃんと実装しよう．．

# どうやって実装するんだ？

`System.UriParser.Register()`を使う．
https://msdn.microsoft.com/ja-jp/library/system.uriparser.register%28v=vs.110%29.aspx

これらをキーワードにGoogle検索すると，少ないが情報が見つかる．

1. http://www.ageektrapped.com/blog/writing-your-own-uriparser
2. http://www.ageektrapped.com/blog/writing-your-own-uriparser-part-2-implementing-getcomponents/
3. http://www.codeproject.com/Articles/13773/Writing-a-custom-UriParser-for-NET

1と2は続き物で，3がその集大成と言ったところか．どちらの場合も`System.GenericUriParser`を基底クラスとするサンプルが書かれている．

しかし，data URIのような不透明URIを追加する場合，`System.GenericUriParser`を基底クラスにするのは適切ではない．なぜならば，本家のAPIドキュメントにこう書かれているからだ．
https://msdn.microsoft.com/ja-jp/library/system.genericuriparser%28v=vs.110%29.aspx

> A customizable parser for a hierarchical URI

`System.GenericUriParser`は階層URIに対するものだと明確に書かれている．実際，以下のようなコードを書いて

```csharp
class DataUriParser : GenericUriParser
{
    public DataUriParser() : base(..省略..)
    {
        System.Diagnostics.Debugger.Break();
    }
}
```

`this`の非公開メンバー変数`m_Flags`を確認すれば，APIドキュメントに書かれてあることが正しいとわかる．

> MustHaveAuthority | ..

ちなみに，`m_Flags`の値は`GenericUriParserOptions.AllowEmptyAuthority`などを渡しても変化しない．

このような設定になっている結果，不透明URIに対して`System.GenericUriParser`を基底クラスにするといろいろと面倒なことになる．

Stack Overflowに`System.Reflection`を使って`m_Flags`を強引に書き換えるという荒業も紹介されているが，正直どうかと思う．

# 素直に`System.UriParser`を基底クラスにしよう

実際にやってみたところ，`m_Flags`は`MayHavePath`となっており，不透明URIにはちょうどよいことが分かった．

`System.UriParser`は抽象クラスなので，必ず発生クラスを作る必要があり，実装するURIスキームの仕様に合わせて`System.UriParser.InitializeAndValidate()`をオーバーライドするのがよいだろう．

```csharp
class DataUriParser : UriParser
{
    protected override void InitializeAndValidate(Uri uri, out UriFormatException parsingError)
    {
        // uri.OriginalStringなどを再度解析して，不正な形式ならparsingErrorを設定
    }
}
```

# 余談

`System.UriParser`の設計は，かなり難解だ．正直，ちゃんと理解できたという自信はない．

最初，`System.GenericUriParser`を基底クラスにしていろいろ試していたが，

* `System.GenericUriParser.InitializeAndValidate()`を必ずオーバーライドしないと例外発生
* `System.GenericUriParser.InitializeAndValidate()`をオーバーライドした場合は，`System.GenericUriParser.GetComponents()`も必ずオーバーライドが必要っぽい

どうやら`new Uri("...")`しただけでは文字列解析は十分に行われることはなく，`System.Uri.Query`などURIの各パーツへのアクセスが発生しないと派生クラスは機能しないようだ．アクセスが発生すると上記のメソッドが呼び出される．

`System.GenericUriParser.GetComponents()`の呼び出しのたびに文字列を解析するのは非効率なので，そう考えるとクラスのメンバー変数に解析結果を保持するのが妥当だが，そうするなら`System.GenericUriParser.OnNewUri()`もオーバーライドが必要だろう．

あと，`System.GenericUriParser`のコンストラクターには`System.GenericUriParserOptions`を渡すのだが，`System.GenericUriParser.GetComponents()`をオーバーライドしている場合，コンストラクターに指定したフラグ値を考慮した実装を行う必要があるようだ．

例えば，コンストラクターに`System.GenericUriParser.NoQuery`を含めた場合，`System.GenericUriParser.GetComponents()`は"scheme://authority/path?query=value"を"scheme://authority/path%3Fquery=value"と変換する．しかし，`System.GenericUriParser.GetComponents()`をオーバーライドすることで，このフラグ値やメソッド引数の指示を無視して任意の文字列を返すことが可能だ．このとき，例外などは一切発生しない．