以前 C# でゲームを作っていて、Mixin がないのは不便だなーと思っていたのですが、既存の構文でもそれっぽいことができたのでメモしておきます。
こちらの記事に載っているように、C# では拡張メソッドを使うことで、インターフェースに実装を持たせることが可能です。しかし、インターフェースはメンバー変数を持つことができないので、少し使い勝手が悪い感が残ります。
ところが、C# には自動プロパティと呼ばれる機能があり、これを使えばインターフェースにメンバー変数(っぽいもの)を持たせることができるのです。
というわけで今回は、インターフェースに対して、拡張メソッドを使ってメソッドに実装を持たせ、自動プロパティを使ってメンバー変数もどきを持たせることで、Mixin っぽく使うやり方を書いていこうと思います。
プロパティとフィールド
プロパティの自動生成について紹介するには、まず、C# においてプロパティと呼ばれるものが、具体的に何を指すのかを知る必要があります。
以下のコードを見てください。
class Hoge
{
private int _x;
public int getX() {
return _x;
}
public void setX(int value) {
_x = value;
}
}
これはアクセサと呼ばれるもので、フィールドの実装を隠蔽するのに用いられます。
C# では、アクセサは以下のように書くことができます。
class Hoge
{
private int _x;
public int X
{
get { return _x; }
set { _x = value; }
}
}
_x
という変数に対して、大文字の X
がアクセサの役割をしています。この記法を使えば、外部からアクセサの存在を意識せずに、hoge.X = 100
のように書くことができます。
また、このときの _x
をフィールド、大文字の X
をプロパティと呼び、区別します。
自動プロパティ
C# には「自動プロパティ」と呼ばれる機能があり、上のような単純なプロパティであれば、以下のように簡略化して書くことができます。
class Hoge
{
public int X { get; set; }
}
こうすることで、内部的に先ほどの _x
のような変数が定義され、使用されます。
今回はこの自動プロパティをつかって、アクセサであるプロパティ(関数)を定義したように見せかけて、フィールド(変数)を宣言してしまいます。
具体的なコードはこちら。
public interface IDrawable
{
float X { get; set; }
float Y { get; set; }
float W { get; set; }
float H { get; set; }
Image Image { get; set; }
}
これで一応、インターフェースにメンバー変数っぽいものを定義することができました。
拡張メソッド
次に、冒頭で紹介した記事に載っている拡張メソッドを使った方法で、IDrawable
に Draw()
というメソッドを追加します。
public static class Extensions
{
public static void Draw(this IDrawable self)
{
GraphicsContext.DrawImage(self.Image, self.X, self.Y, self.W, self.H);
}
}
こうすることで、IDrawable
なオブジェクトに対して Draw()
というメソッドを定義することができます。(GraphicsContext
は今回はグローバルに定義されていると考えてください。)
なお、拡張メソッドがやっているのは、単純に IDrawable
なオブジェクトの参照を受け取ってプロパティにアクセスしているだけなので、IDrawable
の実装は、後で出てくるコードのように public にする必要があります。
実際に使ってみる
これで Mixin もどきの実装はおわりです。
実際に使うときには、次のようにします。
public class Enemy : IDrawable
{
public float X { get; set; }
public float Y { get; set; }
public float W { get; set; }
public float H { get; set; }
public Image Image { get; set; }
public Enemy()
{
this.Image = Image.FromFile(@"path\to\image");
}
}
class GameScene
{
private List<IDrawable> _drawableObjects;
public void Startup()
{
_drawableObjects = new List<IDrawable>();
_drawableObjects.Add(new Enemy() { X = 100; Y = 100; W = 100; H = 100; });
}
public void Update()
{
foreach(var obj in _drawableObjects)
{
obj.Draw();
}
}
}
これだけだとあまりありがたみが湧きませんが、もう少し複雑な処理をするようになると、ちょっと便利な気がしてくるんじゃないでしょうか。
ただ、悲しいことに、メソッドのオーバーライドはできません。
トリックを使って mixin ぽくみせかけているだけで、実際はただの拡張メソッドなので仕方ないですね。
というわけで、C# で Mixin もどきでした。
おかしな点や補足などありましたら、ご指摘お待ちしております。