2008年3月10日月曜日

ヌルオブジェクトの導入

ヌルオブジェクトの導入

RegexGroupCollection.cs

namespace System.Text.RegularExpressions {

public class Group : Capture {

internal static Group _emptygroup = new Group(String.Empty, new int[0], 0);

internal Group GetGroup(int groupnum) {
if (_captureMap != null) {
Object o;

o = _captureMap[groupnum];
if (o == null)
return Group._emptygroup;
//throw new ArgumentOutOfRangeException("groupnum");

return GetGroupImpl((int)o);
}
else {
//if (groupnum >= _match._regex.CapSize || groupnum < 0)
// throw new ArgumentOutOfRangeException("groupnum");
if (groupnum >= _match._matchcount.Length || groupnum < 0)
return Group._emptygroup;

return GetGroupImpl(groupnum);
}
}
}
}

ここで、ヌルオブジェクトが作られているがどれかわかるでしょうか?
そ う、_emptygroupです。staticで作るのがポイントで、そうすると、Group._emptygroupとクラス名から呼び出すことができ る。this._emptygroupと呼び出すことはしていないことに注意してください。ヌルオブジェクトは一つで十分なので、このようにするのが常套 手段です。

はじめは例外を投げるコードが描かれているが、Group._emptygroupを返すように変えられています。
なぜでしょうか?

このGetGroup関数は、以下の2か所で使われています。
public Group this[int groupnum]
{
get {
return GetGroup(groupnum);
}
}

public Group this[String groupname] {
get {
if (_match._regex == null)
return Group._emptygroup;

return GetGroup(_match._regex.GroupNumberFromName(groupname));
}
}

2番目のStringが入ってきた場合のプロパティの中身を見てください。
GroupNumberFromNameとは何でしょう?
関数の中身を以下に示します。

public class Regex : ISerializable {

/*
* Given a group name, maps it to a group number. Note that nubmered
* groups automatically get a group name that is the decimal string
* equivalent of its number.
*
* Returns -1 if the name is not a recognized group name.
*/
///
///
/// Returns a group number that corresponds to a group name.
///

///

public int GroupNumberFromName(String name) {
int result = -1;

if (name == null)
throw new ArgumentNullException("name");

################################(1)
// look up name if we have a hashtable of names
if (capnames != null) {
Object ret = capnames[name];

if (ret == null)
return -1;

return(int)ret;
}

################################(2)
// convert to an int if it looks like a number
result = 0;
for (int i = 0; i < name.Length; i++) {
char ch = name[i];

if (ch > '9' || ch < '0')
return -1;

result *= 10;
result += (ch - '0');
}

// return int if it's in range
if (result >= 0 && result < capsize)
return result;

return -1;
}
}

(1)で、名前付きキャプチャが存在するときにはそのテーブルの中から探しています。
しかし、名前付きキャプチャが存在しないときには(2)で、入ってきたStringが数字かもしれないとしてパースしています。数字だったらその数字を返すのです。
これはいかにも不格好な実装だと思うかもしれません。確かにそうです。
しかしながら、正規表現というのはもともと文字列のみで表現するものです。そのなかに名前でキャプチャされた文字列を取り出す場合と数字で取り出す場合があり、.NETの正規表現ではどちらも同じ表記法を取っているため、致し方ないといえるでしょう。
そうすると、たまたま検索された文字列中にキャプチャされないグループがあった場合、それを検索しようとして例外が発生されてはユーザも困ってしまいます。そのため例外を発生させるのではなく、_emptyGroupというヌルオブジェクトを返すようにしているのです。
この例から、ヌルオブジェクトを使う一つの指針が出てきます。

揺れた表記を受け入れるときにヌルオブジェクトを使う

ちょうど強い型付けのプログラミング言語を使っていて、それくらいわかってよ!と言いたくなる時に動的型付けの言語が恋しくなるように、そんなところで例外を発生させないでよ!というときにヌルオブジェクトを使うといいのです。

0 件のコメント: