今風のC#コード書き方を学んでみよう
-
2024年3月12日
こんにちは。私はココネのクライアントエンジニアSです。
私はゲーム業界で働いて約20年ほど経ちましたが、Unityエンジンに触れ始めたのはたった4年前で、それまでは主にC++で開発していました。
その頃の私は自分がC++から手放すことは永遠にないだろうと思っていましたが、会社が積極的にUnityエンジンを導入した以来、今までずっとC#一辺倒で開発を続けています。
(pointer管理地獄から解放されたときの感動が一番印象に残っていますね)
C#にだんだんと慣れながら、STLよりもLINQ文が馴染んできてる頃に、ふと自分が書いているコーディングスタイルが非常に時代遅れだと気づき、私のように古いスタイルでコードを書く人々に役立つような幾つかのC#文法を紹介したいと思います。
ラムダ式の活用
ラムダ式はメソッドを定義せずインスタントで書いて使うので、コードがより簡潔になります。
これを応用して関数を綺麗にまとめることができます。
// 既存の書き方
for (int i = 0; i < 10; i++)
{
System.Console.Write(i);
}
// 今風の書き方
Enumerable.Range(0, 10).ToList().ForEach(i => System.Console.Write(i));
関数だけではなくプロパティの「getter、setter」にも使えます。
//既存の書き方
private float weight; // 0f ~ 1f
public float Percentage
{
get
{
return weight * 100f;
}
set
{
weight = value / 100f;
}
}
//今風の書き方
private float weight; // 0f ~ 1f
public float Percentage
{
get => weight * 100f;
set => weight = value / 100f;
}
特にgetterのみの場合はこのような縮約も可能です。
private float weight; // 0f ~ 1f
public float Percentage => weight * 100;
超スリムな感じ
パターンマッチ式
変数の比較文(特にenumタイプ)を書くとき、「if」文と「==」演算で分岐するケースが多いですが、複数の比較文は「is」と「or、and」を使うと更に簡潔に書けます。
enum Animal
{
Dog, Cat, Horse, Chicken, Duck
}
// 既存の書き方
bool HasWing(Animal animal)
{
return animal == Animal.Chicken || animal == Animal.Duck;
}
//今風の書き方
bool HasWing(Animal animal)
{
return animal is Animal.Chicken or Animal.Duck;
}
同じ変数を繰り返して書かなくても良いところが気持ち良いですね
ちなみに、「is」演算はオブジェクトのタイプチェックの時よく使われていますが、タイプ変換演算「as」の機能も兼ねるので、1ヶ所でタイプチェックと変換を同時に行えます。
class Animal
{
public void Move();
}
class Bird : Animal
{
public void Fly();
}
static void Main()
{
Animal unknownAnimal = CreateAnimal(); //Classタイプは不明
//既存の書き方
var bird = unknownAnimal as Bird;
if (bird != null) bird.Fly();
//今風の書き方
if (unknownAnimal is Bird bird) bird.Fly();
}
はてなマーク「?」でnullチェック
nullチェック文はコードを汚くさせる常習犯ですが、どのコードがいつクラッシュをさせるか分からないので、エンジニアは常にnullチェックをコードに入れないといけないです。
C#では「?」演算を使ってnullチェック文をより簡潔に書けます。
// メソッドを呼び出す前にnullチェック
myHandler?.DoSomething();
// => if (myHandler != null) myHandler.DoSomething();
そして、ダブルはてな「??」を使えば前方の値がnullの場合、次の値を設定することが可能です。(3項演算子と)
// myItemがnullのの場合0を使用
var itemCount = itemInfo?.Count ?? 0;
// => var itemCount = (itemInfo != null) ? itemInfo.Count : 0;
// itemInfoがnullの場合は新規生成
itemInfo ??= new ItemInfo();
// => if (itemInfo == null) itemInfo = new ItemInfo();
※ここで注意
Unityが提供しているオブジェクト(UnityEngine.Object系)は内部で「== null」演算がオーバーライドされていで、実際の値がnullの時とゲーム中に無効化された場合(例:シーンからDestroyされた場合)も「== null」で判定できます。この場合、上の「?、??」演算だと正しい無効化チェックができないので、Unity系オブジェクトのnullチェックは「== null」で明確に書く必要があります。
(これは非常に残念ですが、いつかUnity側で対応してくれることを祈りましょう)
配列演算の活用
C#の配列は「^」を使って語尾からカウントすることができます。
var myArray = new int[] {1, 2, 3, 4, 5};
var lastElement = myArray[^1]; // 5
// => var lastElement = myArray[myArray.Length - 1];
var thirdElementFromLast = myArray[^3]; // 3
// => var thirdElementFromLast = myArray[myArray.Length - 3];
配列の一部領域を返す「..」演算も便利です。使い方は「..」の前後にスタート地点、エンド地点のインデックスを設定します。
int[] myArray = new int[] {1, 2, 3, 4, 5};
// 2番目から4番目までの配列を取り出す(indexは0が先頭)
var subArray1 = myArray[1..4]; // {2, 3, 4}
// ^を使って最初と最後を切り取る
var subArray2 = myArray[1..^1]; // {2, 3, 4}
// 境界のindexを省略すると配列の両端になる
var subArray3 = myArray[..3]; // {1, 2, 3} 最初から3番目まで
var subArray4 = myArray[2..]; // {3, 4, 5} 3番目から最後まで
この演算の素晴らしいところは、文字列も内部的には「char」の配列なので、これを活用して文字の加工ができます。
string myText = "Apple, Banana and Cherry";
//..演算で文字の一部を切り取る
string appleText = myText[..5]; // "Apple"
string cherryText = myText[^6..]; // "Cherry"
もうSubstringなんか使わなくても生きていける時代になりました。
「switch case」文の新しい書き方
「switch case」で変数の値を代入したり関数の中で「switch case」の結果を返す時、より簡潔に書く方法があります。
//序数のつけ語(1st, 2nd)を返す(number <= 10)
//既存の書き方
string postFix;
switch(number)
{
case 1: postFix = "st"; break;
case 2: postFix = "nd"; break;
case 3: postFix = "rd"; break;
default: postFix = "th"; break;
};
//今風の書き方
var postFix = number switch
{
1 => "st",
2 => "nd",
3 => "rd",
_ => "th"
};
なんか、格好良いですね。
最後に…
この全てを教えてくださったRider先生に沢山の感謝の気持ちをお伝えします。