ものがたり(旧)

atsushieno.hatenablog.com に続く

Encoding派生クラス実装の最適戦略

いま.NET 2.0で追加されたEncodingまわりのAPIを埋めているのだけど、MSが一体どういう設計思想に基づいてEncodingを書き換えたのか、いまいち謎である。

Encoding.GetChars()やEncoding.GetBytes()には、ポインタを引数に取るオーバーロードが存在している。.NET 2.0では、EncoderやDecoderにも、ポインタを引数に取るオーバーロードが追加された。(追加されたっていうことは、それまでは存在していなかったということだ。)

ポインタを使用したバージョンは、ユーザーがEncodingクラスを派生させる際にオーバーライドする必要がない、virtualの実装になっている(CLSCompliantでないメソッドをabstractにするわけにはいかないから、これ以外に方法は無い)。派生クラスで実装されるのはabstractなGetChars()やGetBytes()だけだから、必然的にこれらを呼び出すようになっているはずで、ポインタから配列を得るためには、どうしても配列をnewしなければならない。これはリソースの無駄だ。

もう一つ、GetBytes(string)というオーバーロードが存在する。これもユーザーが実装する必要はないvirtualメソッドである。stringからchar[]を取得するには、ToCharArray()を使っていることになる(だろう)。ここでもやはり無駄な配列オブジェクトの生成が生じることになる。

一方、配列からポインタへの変換、stringからchar*への変換はfixedを使用して実現可能だ。であれば、GetBytes()を実装する最適戦略は、GetBytes(char*, int, byte*, int)を実装する、という形になるはずだ。

実際、UTF*Encodingでは、このポインタを引数に取るオーバーロードがEncodingのデフォルト実装ではなく、オーバーライドされているようだ。そして、UTF8Encoding.GetBytes(string)は、.NET 1.xには存在していたものが、2.0では消えて無くなっている。

ではchar*/byte*のオーバーロードを実装すればよいのか、というと、そうでもないようだ。


DecoderFallbackBuffer.Fallback(byte[], int)
こいつは引数が配列なので、結局byte*のオーバーロードで実装していたら、配列を生成してやらなければならなくなってしまう。

DecoderFallbackBufferにはbyte*を受け付けるFallback()のオーバーロードが存在すれば解決する問題なのだが、僕が考えているのと違う実装方針なのか、あまり首尾一貫していないのか、いまいち謎である。*1

とりあえずポインタベースに置き換えたコードをあらかた書き上げたと思った時に気付いて鬱である。

追記: ちなみに、GetBytes()に関しては、ポインタベースのオーバーロードGetBytes(char*, int, byte*, int)で実装しても問題はない、ということだ。DecoderFallbackBuffer.Fallback()と異なり、EncoderFallbackBuffer.Fallback()が受け取る引数はchar(またはhigh surrogate charとlow surrogate char)であり、ここではポインタへの変換は行われない。

*1:もちろん、mscorlib.dllの内部でそのようなFallback()のオーバーロードをもつことは理論上は可能だが、派生クラスでFallbackをオーバーライドされ、それがmscorlib.dll内のEncoding実装に対して設定されうることを考えると、内部実装に依存するコードは書けないはずだ。