プリプロセッサを作って C# 2.0 で Mix-in を実現しようという記事を .NET Expert #02 でやねうらおさんが書いていた。
フッと思い出したので、もっとお手軽にできないだろうかと考えてみた。
多重継承ができない言語に Mix-in が欲しいというのは当然の欲求だ。Mix-in がなければ無駄に同じようなコードを書き殴らないといけない。
Mix-in を知らない人は各自調べて欲しい。非常に便利である事が分かると思う。私が知っている Mix-in の実装系は Ruby だ。D 言語にも実装されている
らしい。Mix-in
を実装している言語は数多くある。
単純に言ってしまえば、Mix-in
とは他のクラスの実装を取り込む事だ。これは通常は多重継承で可能だ。もちろん、多重継承は実装を取り込む事だけが目的ではないが。
C# 2.0 や VB 8.0 では多重継承が禁止されているので、Mix-in を使って実装を取り込まなければならないが、これらの言語は Mix-in
もできない。「interface を使えば多重継承が可能じゃないか?」というのは違う。interface
はあくまで「シグネチャの明言」であって実装を継承する事ができない。
しかし、よくよく考えれば C# 2.0 及び VB 8.0 から導入された partial class は Mix-in
のように他の定義から実装を取り込んでいるように振る舞う。違いといえば、partial class はそのクラス専用になる事だ。
ところが、この partial クラスも「クラス名」の箇所だけテキスト置換できれば他のクラスにも適用できる。そして、Visual Studio 2005 から導入された code snippet
はこの目的ににうってつけだ。
code snippet を Mix-in ライブラリとして使ってしまうと割り切れば、非常に簡単に Mix-in ライブラリを作る事が可能となる。
簡単な例を以下に挙げよう。
今回の Mix-in の機能は「Mix-in Comparable」だ。この
Mix-inをクラスが取り込むと、クラスは比較演算子「<、<=、>、>=」を実装した事になる。以下が code snippet
の定義だ。
<?xml
version="1.0"
encoding="utf-8"?>
<CodeSnippets
xmlns="http://schemas.microsoft.com/VisualStudio/2005/CodeSnippet">
<CodeSnippet
Format="1.0.0">
<Header>
<Title>
Mix-in Comparable
</Title>
</Header>
<Snippet>
<Declarations>
<Literal>
<ID>Class</ID>
<ToolTip>Class
名。</ToolTip>
</Literal>
</Declarations>
<Code
Language="CSharp">
<![CDATA[
#region Mix-in Comparable
partial class $Class$ : IComparable<$Class$>
{
public static bool operator <($Class$
lhs, $Class$ rhs)
{
if(lhs.CompareTo(rhs) < 0)
{
return true;
}
else
{
return false;
}
}
public static bool operator <=($Class$
lhs, $Class$ rhs)
{
if(lhs.CompareTo(rhs) <= 0)
{
return true;
}
else
{
return false;
}
}
public static bool operator >($Class$
lhs, $Class$ rhs)
{
if(lhs.CompareTo(rhs) > 0)
{
return true;
}
else
{
return false;
}
}
public static bool operator >=($Class$
lhs, $Class$ rhs)
{
if(lhs.CompareTo(rhs) > 0)
{
return true;
}
else
{
return false;
}
}
}
#endregion
]]>
</Code>
</Snippet>
</CodeSnippet>
</CodeSnippets>
Mix-in Comparable を取り込んでみよう。
public class Test
{
private int
_id;
public int
ID
{
get {
return _id; }
set { _id =
value; }
}
}
Test クラスの定義の下に「Mix-in Comparable」snippet を挿入する。
#region Comparable Mix-in
partial class
Test :
IComparable<Test>
{
public
static bool
operator <(Test
lhs, Test rhs)
{
if(lhs.CompareTo(rhs) <
0)
{
return
true;
}
else
{
return
false;
}
}
public
static bool
operator <=(Test
lhs, Test rhs)
{
if(lhs.CompareTo(rhs) <=
0)
{
return
true;
}
else
{
return
false;
}
}
public
static bool
operator >(Test
lhs, Test rhs)
{
if(lhs.CompareTo(rhs) >
0)
{
return
true;
}
else
{
return
false;
}
}
public
static bool
operator >=(Test
lhs, Test rhs)
{
if(lhs.CompareTo(rhs) >
0)
{
return
true;
}
else
{
return
false;
}
}
}
#endregion
ここまででコンパイルすると、「Test クラスを partial にしなさい。Test クラスで IComparable
インターフェースの実装をしなさい」と言われるのでその通りにする。
public partial
class Test
{
private int
_id;
public int
ID
{
get {
return _id; }
set { _id =
value; }
}
public int
CompareTo(Test other)
{
if(_id < other._id)
{
return -1;
}
else if(_id
> other._id)
{
return
1;
}
else
{
return
0;
}
}
}
これで完成だ。code snippet のおかげで、この Mix-in Comparable はどのクラスにも取り込む事ができ、しかも非常に簡単にできる。
欠点は、言語ごとに snippet ファイルを用意しなければいけない点だ。
今回の実装はあまり良くない。等値演算子の扱いが CompareTo が 0 のとき
true にするか、Equals が true のとき true にするかで曖昧になったので、等値演算子は無視したからだ。どちらにしろ、<、<=
等の比較演算子をオーバーロードするのはあまり宜しくないかもしれない。
機会があれば、様々な Mix-in snippet ライブラリを随時公開していきたい。