はじめに
今日は12月10日。12月も中旬。2013年もあと21日。アドベントカレンダーも10日目です。
さて自分は,
「JavadでAndroidアプリを作っていて,今はC#でUnityゲーム作っている自分のC#のメモ(長め)」
と題して,C#の文法・機能などを紹介したいと思います。
Unityのアドベントカレンダーですので,Unityの機能・クラスやインスペクタービュー,Monobehaviorのサブクラスでよく書く処理も交えて紹介していきます。
自分は少し前までJava言語を用いてAndroidアプリを作成していました。
今はC#でUnityでゲームを開発しています。
そんな自分の独断と偏見で,ここはJavaと違うな,ここは便利だなと思ったC#の文法・機能を中心に紹介します。
夢中になって書いていて,気がついたら非常に長い投稿になってしまいました。次の節の項目リストの中で面白いと思ったところだけでも見ていただければと思います。
この文書が
- 学校でJavaは習ったけれどもC#は習っていない学生の方
- 業務でJava経験があるので,Unityでよく使ったり便利なC#の文法・機能をさくっと眺めたい
- Unityの参考書を買ったけど,ここのC#の書き方がわからない
などなどの方に,もし少しでも,ほんの少しでも役にたつことがあれば嬉しいです。
メモ
項目リスト
- 命名・レイアウトなどのコーティング規則がJavaと違う
- 入れ子の形でも書ける名前空間
- コードが読みやすくなる名前付き引数
- アノーテションに似ている属性(アトリビュート)
- as演算子でキャストする
- 内部的には整数の列挙型
- 構造体がある。Vector3やQuaternionは構造体
- インスタンスを作らないクラス,静的クラス
- varキーワードで型推論
- 演算子のオーバーロードができる
- 拡張メソッドで継承禁止クラス,列挙型,インタフェースにメソッドの実装追加
- 反復子とyieldキーワード。そしてStartCoroutineメソッド
- イベント駆動のプログラミングが簡潔に。event・デリゲート
- コレクション初期化子を使ってコレクションを見やすく初期化
- うっかりしているとはまりそう。virtual修飾子・override修飾子(仮想関数)
- やっぱり便利!ラムダ式とLINQ
- 匿名型がある(Javaの匿名クラスとは違う)
命名・レイアウトなどのコーティング規則Javaと違う
メソッドの先頭が大文字だったり,定数がアッパーキャメルだったりということに自分は最初は違和感がありました。
C#の言語仕様としてはコーティング規則は定義されていないようですが,郷に入っては郷に従えという言葉がありますね。
入れ子の形でも書ける名前空間
Javaと同じように,C#にも名前空間があります。
キーワードはnamespaceです。
namespace Mrstar.NameSpaceSample
{
public class SampleA
{
}
}
SampleAの完全修飾名は,Mrstar.NameSpaceSample.SampleAです。
また以下のように,入れ子にもできます。
namespace Mrstar
{
namespace NameSpaceSample
{
public class SampleB
{
}
}
}
SampleBの完全修飾名は,Mrstar.NameSpaceSample.SampleBです。
利用側は,
Mrstar.NameSpaceSample.SampleA sampleA = new Mrstar.NameSpaceSample.SampleA ();
Mrstar.NameSpaceSample.SampleB sampleB = new Mrstar.NameSpaceSample.SampleB ();
とも書けますが,
using Mrstar.NameSpaceSample;
というusingディレクティブを用いれば,
SampleA sampleA = new SampleA ();
SampleB sampleB = new SampleB ();
というように短く書けます。
Monobehaviorのサブクラスは,Unity 4.0より前バージョンでは名前空間以下に置けませんでしたが,4.0から置けるようになったようです。
コードが読みやすくなる名前付き引数
UnityのEditor拡張などで用いるEditorUtilityというクラスに,string型のインスタンスを4つ引数にとり,ファイル保存ダイアログを開く,SaveFilePanelというクラスメソッドがあります。
Unity公式のEditorUtilityのSaveFilePanelメソッドのリファレンスでは,次のように呼び出しています。
EditorUtility.SaveFilePanel ("Save texture as PNG", "", texture.name + ".png", "png");
パっと見ただけではそれぞれの引数の意味や,引数がどう使われるかが分かりません。
C#には名前付き引数があります。これを使って書いてみます。
EditorUtility.SaveFilePanel (
title: "Save texture as PNG",
directory: "",
defaultName: texture.name + ".png",
extension: "png");
クラス名,メソッド名,引数の名前からそれぞれの引数の使われ方が想像出来るのではないでしょうか。
次に,bool型の引数を1つとる例を考えます。
下記のようなsampleAnimationという名前のインスタンスが,Playというメソッドを呼び出しているとします。
sampleAnimation.Play (true);
このbool型の引数は繰り返しの有無の判定に使われるかもしれません。もしくは正方向のアニメーション実行か逆方向のアニメーション実行かを制御するためのものかもしれません。
sampleAnimation.Play (isOneShot: true);
上のように名前付き引数を使って呼び出した場合,このPlayというメソッドのbool型の引数は1回のみ実行かループするのかの制御に使われているということが分かりますね。sampleAnimationインスタンスのクラスの中身を見なくてもいいですね。
名前付き引数はコードを書いた本人が書いているまさにその時には,あまり効果を発揮しないかもしれません。しかし他者がコードリビューをする際や,時間を空けてコードを読み返す時などは,とても効果的ではないでしょうか。
他にもC#のメソッドには省略可能な引数,ref修飾子のついた引数,out修飾子のついた引数などがあります。
また,Javaにもありましたが可変長引数もあります。
アノーテションに似ている属性(アトリビュート)
C#にはJavaのアノテーションに似たアトリビュート(属性)があります。
Javaのアノテーションのように,クラスやメソッドなどに追加情報を与えることができます。
Unity独自の属性もいくつもあります。例えば,MonobehaviorのサブクラスでReauireComponentという属性を使うことで,自動的に必要なコンポーネントを付与することが可能になります。
またMonobehaviorクラスのサブクラスのインスペクタービューでの表示を制御するものもあります。次のようなAttributeSampleというサンプルクラスを使って紹介します。
using UnityEngine;
public class AttributeSample : MonoBehaviour
{
public int publicField;
private int privateField;
}
このクラスをGameObjectにコンポーネントとして付与した場合,インスペクターは次のようになります。
int型やfloat型,GameObject型やVector3型などのpublicなフィールドはインスペクターに表示され,そこから値・参照を設定可能です。privateなフィールドやstaticなフィールドは表示されません。
ここで,AttributeSampleを次のように書き換えます。
using UnityEngine;
public class AttributeSample : MonoBehaviour
{
[HideInInspector]
public int publicField;
[SerializeField]
private int privateField;
}
このように書き換えると,インスペクターは次のようになります。
publicなフィールドでもHideInInspector属性がついたものはインスペクターには表示されなくなり,
逆にprivateなフィールドでもSerializeField属性がついたものはインスペクターに表示されます。
as演算子でキャストする
Instanitiateというメソッドがあります。これはプレファブをインスタンス化する際に用いるメソッドです。このメソッドはUnityにまだ少ししか触っていないという方でも,コインドーザを作るハンズオンや様々なチュートリアルで見たことがあるかもしれません。
さて,このInstanitiateメソッドの返り値の型はUnityEngine.Object型です。
インスタンス化したオブジェクトに何か処理をしたい場合GameObject型にしなくてはいけないことが多いのではないでしょうか。
その際,
using UnityEngine;
public class AsSample : MonoBehaviour
{
public GameObject prefab;
void Start ()
{
GameObject created = Instantiate (prefab) as GameObject;
created.name = "creted object";
}
}
という用に,as演算子を用いてキャストすることが可能です。
さて,Javaのように
GameObject created = (GameObject) Instantiate (prefab);
というようにキャストすることも可能です。
キャストに失敗した場合,as演算子はnullになりますが、([型名])を用いてのキャストはInvalidCastExceptionが投げられるなど違いがあります。
またJavaのinstanceof演算子のように,型が一致しているかどうかをboolで返すis演算子というのもあります。型関連では更に,前述のRequireComponentという属性で用いるtypeofという演算子もあります。
内部的には整数の列挙型
Javaの列挙型はクラスでした。メソッドを定義出来ましたし,フィールドやメソッド,クラス内に定数を定義したり,クラスメソッドも定義できました。
一方でC#の列挙型は内部的には整数型です。
public enum Size
{
S,
M,
L,
}
列挙型の値は,次のように[型名].[メンバー名]でアクセスします。
Size size = Size.L;
Debug.Log (Size.S); // S
Debug.Log (Size.M); // M
Debug.Log (Size.L); // L
整数型と列挙型の間で型変換もできます。
int s = (int)Size.S;
int m = (int)Size.M;
int l = (int)Size.L;
Debug.Log (s); // 0
Debug.Log (m); // 1
Debug.Log (l); // 2
Size sSize = (Size)0;
Size mSize = (Size)1;
Size lSize = (Size)2;
Debug.Log (sSize); // S
Debug.Log (mSize); // M
Debug.Log (lSize); // L
列挙型を定義する際に基になる整数型を指定することも可能です。(long, uintなど)
また,Sは内部的に0、Mは内部的には1になっていますが,このメンバーの値も指定できます。
独自のメソッドは通常の方法では定義できませんが,後述する拡張メソッドを使えば列挙型でもメソッドを定義することが可能です。
またUnityにでは,列挙型のpublicフィールドはインスペクター上では次の画像のように,ドロップダウンボックスで表示され,設定しやすくなっています。
構造体がある。Vector3やQuaternionは構造体
C#には構造体があります。
Unityではよく見る,位置や方向に用いるVector3型や回転・姿勢に用いるQuaternion型は構造体です。
構造体は,クラスと同じようにフィールドやメソッドを持てます。
しかしクラスと構造体は様々な違いがあります。
構造体の変数への代入はコピーになります。二つの変数の間でデータは共有されません。
public class IntWrappingClass
{
public int IntValue;
}
上のクラスで,まずクラスの変数への代入を見てみます。
IntWrappingClass intWrappingClass = new IntWrappingClass ();
intWrappingClass.IntValue = 1;
Debug.Log (intWrappingClass.IntValue); // 1
IntWrappingClass otherVariable = intWrappingClass;
otherVariable.IntValue = 2;
Debug.Log (intWrappingClass.IntValue); // 2
Debug.Log (otherVariable.IntValue); // 2
二つの変数の参照先のオブジェクトは同じなので,どちらもIntValueの値は2になります。
次に,構造体の例を見てみます。
public struct IntWrappingStruct
{
public int IntValue;
}
この構造体を使って,クラスと同じことをしてみます。
IntWrappingStruct intWrappingStruct = new IntWrappingStruct ();
intWrappingStruct.IntValue = 1;
Debug.Log (intWrappingStruct.IntValue); // 1
IntWrappingStruct otherVariable = intWrappingStruct;
otherVariable.IntValue = 2;
Debug.Log (intWrappingStruct.IntValue); // 1
Debug.Log (otherVariable.IntValue); // 2
こちらはintWrappingStruct.IntValueは1のままです。
これは構造体は変数への代入時にコピーされるためです。
クラスと構造体には他にもたくさん違いがあります。
例えば,構造体の変数にはnullを代入できなかったり,継承できなかったり,抽象メソッドを持てなかったり,引数なしのコンストラクタが持てなかったり,配列などの扱いなどがあります。
インスタンスを作らないクラス,静的クラス
Utility系のクラスなど,インスタンスを作らないで,定数やstaticなメソッドのみをもつクラスを作ることもあるのではないでしょうか。
そのような場合,Javaでは次のようにコンストラクタをprivateにして実装をすることもあると思います。
package com.mrstar.sample.utility_example;
public class SampleUtility {
public static final int SAMPLE_INT = 1;
public static int calulate(int input) {
return 0;
}
private SampleUtility() {
}
}
C#では,次のようにclassの前にstaticとつけるとインスタンスを作れない静的クラスとなります。
public static class SampleUtility
{
public static readonly int SampleInt = 1;
public static int Calulate (int input)
{
return 0;
}
}
また,C#の静的クラスでは,staticでないメソッドやフィールドを定義するとコンパイルエラーになります。
varキーワードで型推論
Dictionary<int, List<string>> dict = new Dictionary<int, List<string>> ();
長いですよね。無駄に長いですよね。
インスタンス側生成側で型を指定して、変数宣言側で型を指定して。
ここで,varを使います。
var dict = new Dictionary<int, List<string>> ();
C#ではこのように型推論によりすっきりと記述できます。
var creted = Instantiate(monster) as GameObject;
as演算子やキャストを使った場合、コードを読んでいて明らかにその型であることがわかるので,varを使ったほうが読みやすいかと思います。
ただ全てローカル変数をvarにするのはよくないと思います。コードを読む上で、変数の型が表す情報は多く,それがない場合何をしているかがわかりにくくなることがあると思います。
しかしインスタンス化、キャスト後や明らかにわかる場合はvarを使うのはいかがでしょうか。
演算子のオーバーロードができる
C#では演算子のオーバーロードができます。
オーバーロード可能な演算子としては,+演算子や,*演算子などの加減乗除の演算子やtrue演算子,false演算子,==演算子などがあります。
UnityでもVector3型やQuaternion型などでは,いくつかの演算子がオーバーロードされており直感的にそれぞれの型を扱うことができます。
Vector3 vectorAdded = new Vector3 (0, 1, 2) + new Vector3 (3, 4, 5);
Vector3 vectorMultiplied = 1.5F * new Vector3 (1, 1, 0);
拡張メソッドで継承禁止クラス,列挙型,インタフェースにメソッドの実装追加
2013年のUnity3Dのアドベントカレンダー2日目の記事を書かれた方も,この拡張メソッドを利用して,Transformを拡張する記事を書かれていました。
拡張メソッドを用いれば,Unityでよく使うGameObjectやTransformなどのクラスに対して,自分で定義したメソッドを追加・拡張して,継承などを用いずに[インスタンス名].[メソッド名()]という形でメソッドを呼び出すことができます。拡張メソッドを使うと,既存のクラスを変更しないで,呼び出し可能なメソッドを増やすことができます。
以下のようにExtensionTargetという何もメソッドの無いクラスに,拡張メソッドを追加してみます。
public class ExtensionTarget
{
}
このクラスに拡張メソッドを追加するために,以下のようのクラスを定義します。
using UnityEngine;
public static class ExtensionTargetExtensions
{
public static void SampleMethod (this ExtensionTarget extensionTarget)
{
Debug.Log ("SampleMethod");
}
}
利用側は次のようになります。
ExtensionTarget extensionTarget = new ExtensionTarget ();
extensionTarget.SampleMethod (); // SampleMethod
ExtensionTarget型は,何もメソッドを持っていません。
ExtensionTargetExntensionsクラスでメソッドを定義します。
これにより,extensionTarget.SampleMethod ();と言う風に,拡張メソッドを呼び出すことができます。
拡張メソッドを定義する側は
- 静的クラスでないと行けない
- 拡張メソッドはstaticメソッドで定義する
- 第一引数にthisをつける
- 第一引数の型がメソッドを追加する型
を満たさなくてはいけません。
しかし継承と違って,拡張メソッドはprotectedなフィールドやメソッドにアクセスすることはできません。(もちろんprivateにも)また,既存のメソッドをオーバーライドしているわけでもなく,処理を書き換えているわけでもありません。
繰り返しになりますがUnityではGameObjectやTransformなどに,"こんなメソッドがあれば便利","よく書く複数行にわたる処理をどうにかしたい"などの状況で,拡張メソッドは有効だと思います。
これを用いることで、継承禁止のクラスや列挙型にもメソッドを追加することが可能です。
ちなみに,
public interface ISample { }
using UnityEngine;
public static class ISampleExtensions
{
public static void SampleMethod (this ISample iSample)
{
Debug.Log ("SampleMethod");
}
}
public class Sample : ISample { }
とすると,ミックスインのようなこともできるようです。
反復子とyieldキーワード。そしてStartCoroutineメソッド
次のコードは,StartCoroutineメソッドを使って1秒ごとにデバッグログを出力するコードです。
Unityを始めたばかりの方の中にも,コインドーザなど一定時間間隔でオブジェクトを出現させるオブジェクト(Spawner)などのコードで,StartCorouineメソッドを使ったサンプルを見た方もいるかと思います。
using UnityEngine;
using System.Collections;
public class DebugCoroutine Sample : MonoBehaviour
{
void Start ()
{
StartCoroutine (DebugCoroutine ());
}
private IEnumerator DebugCoroutine ()
{
while (true) {
Debug.Log ("Logging");
yield return new WaitForSeconds (1.0F);
}
}
}
StartCoroutineメソッドは,IEnumerator型のオブジェクトを引数にとります。(string型とobject型をとるオーバーロードもあります。)このメソッドは,MonobehaviorクラスのメソッドでありUnity独自の物です。
一方で,IEnumerator型やyield return というキーワードはUnity独自の物ではなくて,もともとはC#のものです。
yield return文とyield breakという文を含むブロックは反復子ブロック(イテレーターブロック)と言います。反復子(イテレーター)を使うと,foreach文などで利用できる列挙可能なオブジェクトを簡潔に記述できます。
using System.Collections.Generic;
public class FromTo
{
private int from;
private int to;
public FromTo (int from, int to)
{
this.from = from;
this.to = to;
}
public IEnumerator<int> GetEnumerator ()
{
for (int i = from; i <= to; i++) {
yield return i;
}
}
}
利用側は次の通りです。
FromTo fromTo = new FromTo (0, 20);
foreach (int i in fromTo) {
Debug.Log (i);
}
自分の場合は,Coroutine関係でStartCoroutineメソッド,yield,IEnumeratorなどキーワードが一気に出過ぎて,混乱してしまいました。まずyieldなど反復子について勉強してから,別途StartCoroutineメソッドを理解するのが,理解への近道なのかもしれないと思います。
StartCoroutineメソッドで疑問に思ったら,まず「C# yield 反復子 イテレーター」などを調べてはいかがでしょう。
イベント駆動のプログラミングが簡潔に。event・デリゲート
Javaを書いていて面倒なことの一つに,イベントリスナーを登録するため,イベントリスナーのインタフェースを実装した無名クラスをいちいちインスタンス化して記述することだと思います。
Androidにおける例をあげます。
ボタンをクリックした際に何かしらの処理をするコードです。
button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
// TODO implement here
}
});
この中で大切な記述は,
- buttonという変数の参照するオブジェクトに対して,
- リスナーを登録する(setOnClickListener),
- 今は「TODO implement here」となっている箇所の実際の処理
で,他の記述は定型文になっていて,
- new View.OnClickListener() {
- public void onClick(View v) {
という記述は毎回書かなくてはいけませんが,冗長に思えます。
C#では似たようなことをする際に,次のように書けます。
button.Click += (sender, e) => {
// TODO implement here
};
非常に無駄が無く簡潔に記述できます。上記は,System.Windows.Controls.Buttonを用いた例です。
C#が持つevent、デリゲート,ラムダ式などの構文を用いることで簡潔にイベント駆動のコードが記述することが可能です。
getterやsetterの変わりにプロパティを使える
オブジェクト初期化子での初期化
プロパティとオブジェクト初期化子を一緒に紹介します。
整数型のlevelと文字列型のnameというフィールドと,それぞれのsetter・getterをもつクラスPlayerがあります。
Javaでは,getterとsetterを用いてアクセスするのが定石です。(Effective Java 項目14
publicのクラスでは,publicのフィールドでなく,アクセッサーメソッドを使う)
// これはJavaのコード
public class Player {
private int level;
private String name;
public Player(int level, String name) {
this.level = level;
this.name = name;
}
public int getLevel() {
return level;
}
public void setLevel(int level) {
this.level = level;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
さて,これをC#で書いてみます。
C#ではJavaのようにgetter・setterを定義することもできますが,プロパティーを利用するのが一般的です。(Effective
C# 4.0 項目1 アクセス可能なデータメンバーの変わりに常にプロパティを使用すること)
public class Player
{
private int level;
private string name;
public Player (int level, string name)
{
this.level = level;
this.name = name;
}
public int Level {
get { return level; }
set { level = value; }
}
public string Name {
get { return name; }
set { name = value; }
}
}
です。利用側は
Player player = new Player (0, "Taro");
Debug.Log (player.Name); // Taro
Debug.Log (player.Level); // 0
player.Level = 1;
Debug.Log (player.Level); // 1
例えばこのようになります。まるで,フィールドにアクセスするように記述しています。
Javaのgetter・setterのように,値をget・setする時に同時に任意の処理をすることもできます。
またgetterにはpublic,setterにはprivateなどそれぞれに異なるアクセス修飾子をつけるなどJavaでもやりますが,プロパティでも同様なことができます。
このPlayer.csというクラスを更に短くしてみます。
自動実装プロパティ用いることで,内部的なlevelとnameというフィールドを消してみます。またコンストラクターを消し,初期化に関してはオブジェクト初期化でやります。
public class Player
{
public int Level { get; set; } // 自動実装プロパティ
public string Name { get; set; }
}
利用側は
Player player = new Player { // オブジェクト初期化子による初期化
Name = "Taro",
Level = 0,
};
です。
先に述べた名前付き引数と同じように,読みやすいのではないでしょうか。
コレクション初期化子を使ってコレクションを見やすく初期化
JavaにおいてArrayListを初期化をする際に、次のように書く必要があります。多少面倒です。
// これはJavaコードです。
List<String> nameList = new ArrayList<String>();
nameList.add("Taro");
nameList.add("Jiro");
nameList.add("Saburo");
nameList.add("Shiro");
nameList.add("Goro");
addメソッドの要素の数だけ呼び出す必要があります。
次のような、配列の初期化の方が記述が簡潔で読みやすいです。
// これはJavaコードです。
String[] names = new String[]{"Taro", "Jiro", "Saburo", "Shiro", "Goro"};
C#では,コレクションの初期化を配列のようにできるコレクション初期化子が存在します。
// これはC#コードです。
List<string> names = new List<string>() {"Taro", "Jiro", "Saburo", "Shiro", "Goro"};
このように,配列のように初期化することが可能です。
C#のDictionary(JavaでいうMap)も次のように読みやすい形式で初期化することが可能です。
Dictionary<int, string> dict = new Dictionary<string, int> {
{ 1, "taro" },
{ 2, "jiro" },
{ 3, "saburo" },
};
コレクション初期化子は、実際はAddメソッドのシンタックスシュガーになっています。
さて,実はJavaも他のクラスや他のライブラリを用いて,C#のコレクション初期化子のように記述することも可能です。
Arraysクラスを経由する方法ではこのように,
List<String> nameList = new ArrayList<String>(Arrays.asList("Taro", "Jiro", "Saburo", "Shiro", "Goro"));
GuavaライブラリのListsクラスのstaticメソッドのLists.newArrayListメソッドでは以下のようにかけます。
List<String> nameList = Lists.newArrayList("Taro", "Jiro", "Saburo", "Shiro", "Goro");
うっかりしているとはまりそう。virtual修飾子・override修飾子(仮想関数)
class Super
{
public void Hello ()
{
Debug.Log ("Hello, this is Super class.");
}
}
class Sub : Super
{
public void Hello ()
{
Debug.Log ("Hello, this is Sub class.");
}
}
上のようなクラス考えます。
Super super = new Super();
super.Hello(); // "Hello, this is Super class."
Sub sub = new Sub();
sub.Hello(); /// Hello, this is Sub class.
Super subInSuper = new Sub();
subInSuper.Hello(); // "Hello, this is Super class." <- 注目
利用側、最後のsubInSuper.Hello();の結果について注目です。
自分がC#を書き始めたころ,この結果はJavaと同じような挙動をして,"Hello, this is Sub class!"と表示されることを期待していました。しかし,実際は違いました。
次のように書き換えます。
class Super
{
public virtual void Hello () // <- 注目 virtual修飾子が加わっている
{
Debug.Log ("Hello, this is Super class.");
}
}
class Sub : Super
{
public override void Hello () // <- 注目 override修飾子が加わっている
{
Debug.Log ("Hello, this is Sub class.");
}
}
Super super = new Super();
super.Hello(); // "Hello, this is Super class."
Sub sub = new Sub();
sub.Hello(); /// Hello, this is Sub class.
Super subInSuper = new Sub();
subInSuper.Hello(); // "Hello, this is Sub class." <- 注目
Javaと同じような挙動をしています。
仮想関数という概念があります。C#ではスーパークラスにvirtualメソッドをつけて、サブクラスにoverrideをつけると、挙動を変更できます。実はJavaではデフォルトでこのような仮想関数になっています。
やっぱり便利!ラムダ式とLINQ
LINQです。ラムダ式です。
C#の本だったらそれぞれで1章もしくは章をまたいで紹介・説明されています。
LINQだけの本もいくつか出ています。
JavaでAndroidアプリを作っていた自分は、最初LINQの良さ・すごさがわかりませんでした。UnityでゲームをつくるためにC#を書き始めてしばらくして,こわごわLINQを使ってみて,少しずつ少しずつその良さ,すごさが分かり始めてきました。
前述のPlayerというクラスのリスト,playersがあります。
このリストの要素のインスタンスの中で,levelが80以上であるものの名前(name)のリストを作成するコードを例にLINQの良さを紹介します。
int level = 80;
List<String> highLevelPlayerNames = new ArrayList<String>();
for (Player player : players) {
if (player.getLevel() >= level) {
highLevelPlayerNames.add(player.getName());
}
}
このようなコードになるかと思います。
すぐに「ああ,これは80以上のレベルのプレイヤーの名前のリストを作っているのだな」と分かるかと思います。
しかし,実際コードとしてはやっている処理は,
- 出力結果のhighLevelPlayerNamesというリストを作成して,
- playersリスト全体を反復しその中の要素のplayerの,
- レベル(level)が80以上なPlayerは
- 名前(name)を取得して,
- それをhighLevelPlayerNamesというリストに追加する
ということをしています。
このような記述に慣れているので,このようなコードを見たときに「ああ,これは80以上のレベルのプレイヤーの名前のリストを作っているのだな」と分かるかと思います。が,それは慣れているからであり,直感的・直接的に「80以上のレベルのプレイヤーの名前のリストを作る」ということをコードは述べていないと思います。どのように処理をするかは1〜5のようにはっきりコードは述べていますが,それにより何をしたいかは埋もれているのではないでしょうか。
次にC#でLINQを用いて同じように書いてみます。
int level = 80;
List<string> highLevelPlayerNames = players.Where (p => p.Level >= level)
.Select (p => p.Name)
.ToList ();
少し説明します。
Whereはplayersの中身の要素をフィルタリングします。条件を指定してその条件を満たしたものだけにします。
p => p.Level > level
は条件を表すラムダ式です。少し書き換えてみます。
(Person person) => {
bool isHighLavel = person.Level > level;
return isHighLavel;
}
少しメソッドに近い形になりました。
「Person型のpersonを貰って,person.Levelがlevel(80)以上の物ならば,条件を満たすので真を返す。」という意味です。
Selectは要素の中身を加工・変換します。ここではPersonをそのプロパティの名前に変換します。
C#での例を説明すると,playersの要素の中で,レベルが80以上のもののみ(Whereの部分),Playerの名前に変換して(Selectの部分),(IEnumerable<string>を)ToListでList<string>にする。という記述になっています。
Javaでのコード例に比べると,C#でLINQを用いたコードはやりたいことを直接コードで記述しています。
余談ですが,ToListはいらない場合も多いようです。ToListの前はIEnumerable<string>型になっています。もしList型での変数が必要ならばToListの呼び出しは必要ですが,状況によってはIEnumerable<string>型で済む場合もあるようです。
自分は最初,ラムダ式の記述方法やLINQでの処理に慣れませんでしたが,その簡潔で無駄な記述が少ない書き方や,やりたいことが直接読み取れるコードに今ではラムダ式・LINQがとても好きになりました。
匿名型がある(Javaの匿名クラスとは違う)【追記】
下記はAndroidで,View.OnClickListenerインターフェースを実装した匿名クラスのインスタンスを用いて,ボタンにリスナーを設定するコードです。
Button button = (Button)findViewById(R.id.button);
button.findViewById(R.id.button).setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
// ここにボタンが押されたときの処理
}
});
Javaでは,このようにコールバックやリスナーを匿名クラスを用いて実現することがあります。
C#には匿名型という機能が存在しますが,上記のようなJavaの匿名クラスとは用途・役割が異なります。
こちらの投稿にまとめたので,興味がある方は見てください。
さいごに
以上です。
大変長くなってしまいました。
今回の内容は私の独断と偏見でピックアップし紹介させて頂きました。
なんでこれないの,これおかしくない,などご意見やご感想があればぜひお願いします。
また,間違い等ありましたらそちらも申し訳ありませんが,お願いします。