A-Nest 2021/11/03 12:41

開発者向け - インターフェイスの使い方1

どうも、最近あまり一般受けしない感じの記事ばっかり書いてるプログラマのゆっくりみかんです。
僕の趣味もあってどうもこういう話ばかりになっちゃっていますが、いずれは作品の進捗も載せられると思うので、しばしお待ちを。

さて、今回はC#におけるインターフェイスや抽象クラス等々の使い方についてです。
何というか、理解出来ると素晴らしく便利で綺麗にコードが書ける様になる概念なのですが、どうも理解が難しい。
かく言う僕も、最初文章を読んだ時点では、理解は出来るもののどこで使うのがいいのやら? という感じでした。

しかし、こういうものは斜め読みでも参考書や仕様書を何度も何度も読んでいれば、ある日突然
「あれ、こう使えばいいんじゃね?」
と天啓の元になるものです。諦めずに頑張ってみましょう。
というか、インターフェイスとか抽象クラスの解説って、大体抽象的な物言いになってて実際の利用シーンをイメージしたサンプルになってない事が多いのも原因な気がします。
そこら辺を分かりやすく書けたらなぁ、と。

ちなみに、僕の解説は完全に正しいかというとそうでもない気がします。
おかしいと思った所があれば、コメントなりでツッコミを頂ければ幸いです。
ただ、オブジェクト指向の説明で自動販売機や動物などを使う時、ああいう例えに
「正確ではない」
と言う方がいたりしますが、そういうタイプのツッコミは不要です。
正しさよりも、まず使える様にならないと話にならないと思っている人間なので・・・
あと、記事に書くコードは実行を確認しておりません。
説明としては正しい物を書く様気を付けてはいるので、ご容赦を。

本解説は、既にC#をある程度扱えている人向けです。

インターフェイス

インターフェイスとは、一言で言うと「クラスの機能を明示的に宣言して、共通の形で扱える様にするもの」です。
インターフェイスそれ自体に実装はなく、単に
「こういうメソッドあるよ」
「こういうプロパティあるよ」
と宣言するのみで、実装は継承した先のクラスで行います。
(最新のC#だとインターフェイスのデフォルト実装という概念がありますが、それについては割愛します)

C#では基本的に最初に大文字の「I」を付けるルールになっており、Visual Studio等々の入力候補で「I」を打ってみると標準のインターフェイスが色々ぞろぞろ出てくる事でしょう。
標準的な物としては、

  • IDisposable(破棄可能である事を示すインターフェイス)
  • IEnumerable(列挙可能である事を示すインターフェイス)

等がありますね。
特にこの二つを挙げたのは、C#の言語仕様に食い込んでいるインターフェイスであり、分かりやすいからです。

IDisposable

例えば、IDisposableを例に挙げてみましょう。

//一時的なインスタンスマテリアルを保持するテストクラス
public class DisposableMaterialTest : IDisposable {
    private Material _material;	//保持するインスタンスマテリアル
    public Material Material => _material;	//保持するインスタンスマテリアルを公開するプロパティ

	//レンダラを引数に取るコンストラクタ
	void DisposableTest(Renderer re){
    	_material = re.material;	//レンダラに割り当てられたマテリアルを一時的なインスタンスマテリアルとして取得する
    }
    
    //IDisposableインターフェイスを継承した場合、このメソッドを実装しなければならない。
    //実装していないと、エラーになり、コンパイル出来なくなる。
    //また、Disposeは何度呼んでもエラーにならない様に実装しなければならない。
    //つまり、一度解放が済んだら次に呼び出した時は何もしない様にする。
    public void Dispose(){
    	if(_material != null) {
    		UnityEngine.Object.Destroy(_material);
        }
    }
}

実装はこんな感じですね。概ね、コメントに内容は記述しています。
さて、これをどう使えば良いのかと言うと・・・

//説明用の適当クラス
public class Hoge : MonoBehaviour {
	[SerializeField]
    private Renderer _renderer;	//何らかのレンダラが割り当てられているものとする

	public void Fuga(){
    	using(var testMaterial = new DisposableMaterialTest(_renderer)){
        	//マテリアルを使う何らかの処理
        }
    }
}

こんな風に使います。
using構文は、括弧内で変数の宣言を行い、そのブロック内から外に出た時に宣言した変数のDisposeメソッドを勝手に呼び出してくれる構文です。
つまり、先ほどのusingは次の例と等価です。

public void Fuga(){
	var testMaterial = new DisposableMaterialTest(_renderer);

	//マテリアルを使う何らかの処理
    
	testMaterial.Dispose();
}

Unityではあまり見かけないですが、使いこなすと色々便利な局面があるかも知れません。
Unity以外では、アプリ開発とかでファイル等のStream関連を扱う時に特によく使う構文ですね。
Disposeの呼び忘れを確実に防いでくれる、素晴らしい構文です。

さて、この様にIDisposableを実装するとusing構文で自作のクラスを活用出来る様になります。
IDisposableを実装していないと、当然using構文で使う事は出来ません。
つまり、IDisposableは「破棄可能である」という機能を示すインターフェイスで、そのインターフェイスを実装する事によって自分のクラスが「破棄可能である」事をC#のコンパイラに対して明示的に示しているわけです。
その様に示した事によって、using構文を使う事が出来るわけです。

IEnumerable

さて、お次はIEnumerableについてです。
このクラスもIDisposableの様に実装するとC#の特定の構文で使える様になります。
その構文は超便利な「foreach」です。
IEnumerable<T>というジェネリック版のインターフェイスもありますが、通常版の使い方がわかったら少し追加で調べるだけでジェネリック版の使い方もわかると思います。

public class TestEnumerator : IEnumerable {
	//IEnumerableインターフェイスを継承する場合、これを実装しなければならない。
	//このクラスがforeachされた時に、どういうデータを返すかを実装する。
	IEnumerator GetEnumerator() {
		//yield returnはイテレータと呼ばれる特別な構文。
		//通常のreturnはその時点で実行を終了するのに対し、yield returnは値を返した後も処理をそのまま続行する様なイメージ。
		yield return "Ringo";
		yield return "Gorira";
		yield return "Rappa";
	}
}

はい、これで実装は終わりです。
これをforeachするとどうなるでしょうか?

public class Hoge() {
	public void Fuga() {
    	var test = new TestEnumerator();
    
    	foreach(var str in test) {
        	Debug.Log(str);
        }
    }
}

こんな感じで使う事になりますが出力としては

Ringo
Gorira
Rappa

になります。自作メソッドが見事にforeachに対応しました。
説明の為に簡単な例にしましたが、実際に使う際はこの様に使う事が多いと思われます。

public class TestEnumerator : IEnumerable {
	private List<string> _testList;	//テスト用リスト
    public List<string> TestList => _testList;	//テスト用リストを公開するプロパティ
    
    //コンストラクタでインスタンス化しつつ文字列追加
    public TestEnumerator() {
    	_testList = new List<string>() {"Ringo", "Gorira", "Rappa"};
    }

	//IEnumerableインターフェイスを継承する場合、これを実装しなければならない。
	//このクラスがforeachされた時に、どういうデータを返すかを実装する。
	IEnumerator GetEnumerator() {
    	//面倒な場合はforeachで済ませるが、頻繁に呼び出す場合は
        //for(int i = 0; i < _testList.Count; i++){}
        //の形にした方が、わずかに軽くなる。
		foreach(var str in _testList) {
			yield return str;
        }
	}
}

こちらでも先ほどのクラスと同じような結果が得られます。
何が違うかというと、文字列をリストで管理している事ですね。
ListはIEnumerableを実装しているので当然foreachが使えます。
なので、このクラスの場合はこういう形でforeachする事も可能です。

public class Hoge() {
	public void Fuga() {
    	var test = new TestEnumerator();
    
    	foreach(var str in test.TestList) {
        	Debug.Log(str);
        }
    }
}

ただ、一々公開用のプロパティを作らないとこういう形には出来ませんし(変数をpublicにすればって? HAHA! ナイスジョーク!)、見た目にもきちんとIEnumerableを実装してそれ自体をforeachで回す方がスマートな見た目になりますよね。

IEnumerableおまけ

余談ですが、今回解説したIEnumerableの実装は簡易化されたもので、ちょっと書くのが面倒な実装のやり方が他にもあります。
ただ、これは余程の拘りが無い限り使う事はないと思われるので、割愛します。
逆に、クラス内のリスト等を単純に出したいだけであれば、もっと簡単な書き方もあったりします。

//テスト用クラス
public class Hoge : IEnumerable {
	//テスト用のリスト
	private List<string> _testList = new ();
    
    //インターフェイスの実装
	public IEnumerator GetEnumerator() => _testList.GetEnumerator();
}

これだけで終わりです。簡単!
これは何をしているかというと、ListクラスはIEnumerableを実装している為、そのListクラスの実装をそのまま使っているだけです。
これで事足りる場合も多いでしょう。

一段落

さて、こんな感じでインターフェイスのサンプルを色々書いてみました。
ただ、これは参考書にもよくある程度のもので、理屈はわかったけどゲームでどうやって使うんだってばよ!? って感じになる方も多いでしょう。
思ったより分量が多くなってしまったので、続きはまた次回とさせて頂きます。

それでは、引き続き作品作り頑張ります!

この記事が良かったらチップを贈って支援しましょう!

チップを贈るにはユーザー登録が必要です。チップについてはこちら

最新の記事

月別アーカイブ

記事を検索