C#:【メソッドシグネチャ】に死ぬほどこだわって下さい

C#

この記事は初球から中級にレベルアップしたいプログラマーの方向けの内容です。
また、特にチームで開発している方は、共通で知っておきたい内容になっています。

今回は、メソッドとコレクションが絡むものになっています。
今までよく分かっていなかったコレクション系の事や、インターフェースの大事さについて理解していただけたらと思います。

そしてズバリ言いたいことは、『メソッドシグネチャに死ぬほどこだわれ!』
という事です

スポンサードサーチ

メソッドシグネチャってなんだ

メソッドシグネチャとは、メソッドの『引数』『戻り値』のことです。
単語は難しそうですが、中身はなんてことありません。

実は、この『引数』と『戻り値』を工夫して、『メソッド概要を語ることができる』のです。

つまり、関数の入口と出口のパラメータを工夫することで、チームのメンバーに読みやすいコードを書けるようになるという事です。

こんな実装をしていませんか?

まずは自分がどのような実装をしているのかチェックしてみましょう。

今からメソッドを一つ作るとします。
処理の中身は、引数であるList<class1>をもとにデータを加工し、別のオブジェクトに入れ直し、戻り値をList<class2>として返すとします。

また、戻り値のリストは表示用のデータとして使われるそうです。
中身の加工する部分はすでにで出来ているとして、さて、あなただったらどのようなメソッドシグネチャにしますか?

『引数も戻り値もListって指定なんだから、当然Listじゃん』
ということで、引数はList、戻り値はListとしますか?実際に書いてみるとこんな感じでしょうか。


// 引数と戻り値に注目する
public List <class2> TestMethod ( List <class1> listItem ) {
  
    // ...中身
    
    // result の型は List <class2>()   
    return result ;  
}

どうでしょうか?
あなたが上記のようなシチュエーションでメソッドを作るとしたらこんな感じでしょうか?もし、こんな形であれば、ぜひ次の解説を読んで、レベルアップしてみましょう。

レベルアップするために考えるべきこと

初心者のうちは上記ができていればもちろん大丈夫です。
まずは動くプログラムを作ることが第一です。ですが、もしレベルアップしたいと思うのであれば、次のように考えてみましょう。

ポイントは以下の3つです。

  • 引数のオブジェクトは変更されるか、されないか?
  • 戻り値のオブジェクトは使われることを想定する
  • 引数:IEnumerable<…>, 戻り値:IList<…>にしておく

では、ひとつずつ見ていきましょう。

引数のオブジェクトは変更されるか、されないか?

まずはメソッドの引数に注目してみます。そして考えることはひとつです。
『そのメソッドは変更されますか?』という事です。

もし、変更されず、かつ、コレクションをfroeach( )で見るだけであれば、ぜひ『IEnumerable<…>』にしましょう。

なぜこんな手間を加えるかというと、Listのままではメソッド内で値が変わる可能性があるということです。
『いや、引数で渡しているんだし、値変わったとしてもいいじゃん』と思っていませんか?

引数で渡すListは参照型です。
参照型ということは、メソッド内で値が変わる可能性は大です。参照型がよく分からない人は、こちらの記事をみてぜひ復習してください。
>> C#:アセった時に読む【値型と参照型】

変更するつもりが無かったとしても、間違えて変更してしまう恐れがあるのです。
特にチームで開発しているときは、他人が作ったメソッドを変更するときもあるでしょう。その時に、間違えて引数のListを変更してしまう…という事は容易に想像できます。

それを防止するためにも、特に変更が無いのならばReadOnlライクにすべきなのです。そのためにもIEnumerableの形にしましょう。

ちなみに、ICollectionやIListですと、Addメソッドが入っていますので、
『変更される可能性を含む』という事を他のプログラマーに意識させてしまいます。

以上、ここでの話をまとめますと、以下のような形になりますね。


// 引数のみ、適切な形にした
public List <class2> TestMethod ( IEnumerable<class1> listItem ) {
  
    // ...中身
    
    // result の型は List <class2>()   
    return result ;  
}

スポンサードサーチ

戻り値のオブジェクトは使われることを想定する

さて、戻り値に関しても同じように考えましょう。
そのメソッドはどのように使われるのでしょうか?見るだけなのか、それとも追加や削除されてしまうのか。はたまた、コレクションに対しソートをする前提なのか。ケースに応じた書き方は以下のようになります。

  • 見るだけ& Contain(), Count()したい    ⇒ IEnumerable にする
  • 追加&削除をしたい             ⇒ ICollection にする
  • インデクサアクセス, 追加 & 削除をしたい  ⇒  IList にする
  • コレクションの途中でInsert, Sortしたい  ⇒ List にする

という形になります。

というものの、大抵は、IListかIEnumerableで事足ります。もちろん、途中のInsertなどをしたいときは、Listしか出来ません。
では、IEnumerable or IListにした場合の特徴をそれぞれ見ていきましょう。

IEnumerableにする

IEnumerableにしておけば、呼び出し元でCount()やContain()メソッドを使えます。
これは、Enumerableクラスで拡張メソッドとして用意されているため使えるのです。

IListにする

IListにした場合はどうでしょうか。
呼び出し元の方で、追加や削除はもちろんのこと、インデックスアクセスもできるようになります。また、インデックスアクセスは配列の時に使われる形ですね。

一般に、追加・削除・インデックスアクセスの機能をもつIListに設定しておけば大概は事足ります。
どういうことかというと、例えば、現段階ではReadOnlyぽいIEnumerableで戻り値を設定したとしても、途中で『あ、やっぱAddしたいわ』となるもしれません。その場合、IEnumerableに対してToList( )をしなければ、Add( )メソッドは使えません。



// paramItemはList<class1>

var items = TestMethod (paramItem);
// items は IEnumerable<class2>

// 追加したくなったとき
items.ToList(); // ← ToList()がいる
items.Add( ... ); // 何か追加

また、IListにして初めて、具象クラスである、
ListなのかArray(配列)を選ぶことができます。

ちなみにですが、IEnumerableのみを持つ具象クラス・ICollectionまでをもつ具象クラスはありません。

『ICollectionを具象化したクラスがArray(配列)で、IListを具象化したクラスがListで…』という事でもありません。
詳しくは、こちらをご参照ください。
>> C#:ArrayとListのパンドラの箱を開けてみた

色々話が出てきましたが、
まとめると、チーム開発を考慮して戻り値はIListにした方が無難というわけです。

まとめ

  • 引数:IEnumerable
  • 戻り値:IList

にしておけば大概事足りる。
後は、状況に応じて変えていきましょう。

スポンサードサーチ

小ネタ

本編としてはこれで終わりなのですが、
ここではちょっとした小ネタについて解説しようと思います。ズバリ、IListと配列に関する実験です。

C#:ArrayとListのパンドラの箱を開けてみたの記事を見てくださった方は分かると思いますが、Array(配列)は少々特殊です。

IListを継承しておきながら、追加・削除ができません。しようものならエラーを返してきます。

では、ちょっと実験をしてみましょう。
戻り値の型をIListにして、実際に返す値はToArray( )をしたものを返すとします。
つまり、


public IList <class2> TestMethod ( IEnumerable<class1> listItem ) {
  
    // ...中身
    
    // result の型は List <class2>()   
    return result.ToArray( ) ;  
}

// どこかで呼び出す。
var result = TestMethod(paramItems); // ←resultの中身は配列
result.Add(...); //←これは可能なのか 

という事ですね。

『戻り値の型としてはIListだけれども、実際はArray(配列)を送っているので、その後にAddメソッドができるのか?』っていうか話ですね。

これは実際に試してほしいのですが、ちゃんとエラーが出て落ちます。
私はこの部分を確認しているときはちょっと感動しました。予想通りで良かったです。

また、この実験を通して分かるように、戻り値の型をIListにしたとしても、returnしたオブジェクがそのまま使われていることが分かります。
(難しく言っていますが、当たり前のことを言っています。)
たとえば、

  • 1:return hoge.ToList( )  ・・・ 戻り値の型 IList
  • 2:return hoge.ToArray( ) ・・・ 戻り値の型 IList

としたとき、1における戻り値の正体はListだけれども、呼び出し元で使われる型はIListが実装する機能だけ、という事です。同じように2においても、戻り値の正体はArray(配列)だけれども、呼び出し元で使われる型はIListということです。

なので、Array(配列)はAddでエラーを吐く設計にしていたため、呼び出し元では『IListを実装するオブジェクト』に対してAddをしたとき、Array(配列)だったからきちんと例外を出してくれたのですね。

インターフェースという事を考えたら当たり前のことなのですが、当たり前のことなのかもしれませんが、しっかり押さえておきたいですね。