KLab Engineer Advent Calendar 2018 の15日目の記事です
##環境
Unity5.5以降
.NET 3.5 Equivalent
上記の環境ならプロジェクト設定は通常C#4.0のはずですが、ごく一部の新しい記法が使えます。
##実は使えるC#6.0の記法
####・getter のみの自動プロパティ、構造体の自動プロパティ初期化
public class Hoge
{
// getter のみの自動プロパティ
public int Hogehoge{ get; }
public Hoge(int hoge){
Hogehoge = hoge;
}
}
public struct Fuga
{
public int Fugafuga{ get; private set; }
public Fuga(int fuga){
// 構造体の自動プロパティ初期化
Fugafuga = fuga;
}
}
これはC#6.0からの記法でreadonlyなプロパティが定義できます。
ただ、C#6.0のプロパティ初期化子は使えないためかならずコンストラクタで初期化する必要があります。
public class Hoge
{
// これはエラーになる
public int Hogehoge{ get; } = 10;
}
####・列挙型の基底型
enum Hogehoge : System.Int32
{
Hoge, Fuga,
}
この記法自分はほとんど書いたことがなかったんですが、参考資料によると、
C# 5.0までは、この基底型の指定は「sbyte、 byte、 short、 ushort、 int、 uint、 long、 ulong、 char のいずれか」 という仕様になっていました。 つまり、同じintを指しているはずの、System.Int32という書き方は受け付けられませんでした。
これが、C# 6では受け付けられるようになりました。
らしいです。
####・変数の「意味不変」ルール
class Hoge
{
double hoge;
void Fuga(bool b)
{
hoge = 1.0;
if (b)
{
int hoge; // C# 5.0まではエラーに
hoge = 1;
}
}
}
こちらの仕様変更も参考資料によると、
上記コードのように、ifの外ではフィールドhogeを使っていて、ifの中では同名の変数hogeを定義して使うということすらエラーにしていました。
ところが、C# 6では、この前半のような判定は、大変な割にメリットが少ないということで、判定しない(エラーにならない)よう変更されました。
とのことです。
####・オーバーロード解決の改善
int Hoge(Func<Func<int>> f) { return f()(); }
int Hoge(Func<Func<int?>> f) { return f()() ?? 0; }
float Hoge(Func<Func<float>> f) { return f()(); }
void Main(){
// C#5.0まではこのように型を明示する必要があったが
Hoge((Func<Func<int>>)(() => ()=> 10));
// C#6.0では正常にオーバーロードされる
Hoge(() => ()=> 10);
Hoge(() => ()=> null);
Hoge(() => ()=> 1.5f);
}
C# 5.0まではFunc<Func<int>>
というような、入れ子になったジェネリックはオーバーロードが正常に行われなかったんですが、それがC#6.0で改善されました。
####・拡張メソッドでコレクション初期化子
class Point
{
public int X { get; set; }
public int Y { get; set; }
}
static class PointExtensions
{
public static void Add(this List<Point> list, int x, int y)
{
list.Add(new Point { X = x, Y = y });
}
}
class Hoge
{
void Main()
{
var points = new List<Point>
{
// PointExtensions.Add が呼ばれる
{ 1, 2 },
{ 4, 6 },
{ 0, 3 },
};
}
}
コレクション初期化子自体はC#3.0からの機能ですがAdd関数が通常のメソッドでないと動作しませんでした。
これがC#6.0から拡張メソッドの実装でも動作するようになりました。
####・「確実な初期化」の判定改善
int x;
if (false && x == 3) // C# 5.0まではエラーに
{
x = x + 1; // ここはC# 5.0まででもOK
}
参考資料によると
C#は、未初期化領域の問題を避けるため、「変数は確実に初期化してからでないと値を読み出せない」という仕様になっています。この「確実な初期化」(definite assignment)がされたかどうかの判定は、ある程度コードの流れを追って判定してくれます。例えばifやswitchで分岐がある場合でも、すべての分岐先で初期化してあれば「確実な初期化」済みと見なされます。
また、絶対に通らない場所は判定外です。 例えば、if (false) { }の中(絶対にこの中は通らない)では、未初期化変数を読みだしていてもエラーにはなりません(どうせ通らないので問題ない)。
上記のコードは&&の性質(左側が偽だったら右側は評価しない)上、「絶対に通らない場所なので判定外」としてもいいはずですが、C# 5.0まではエラーになっていました。C# 6ではエラーになりません。
とのことです。
##まとめ
C#6.0には他にもいろいろ更新がありますが、
ここまで紹介した記法のみUnity5.5以降の.NET 3.5 Equivalentでも動作します!
ただ、IDEにはエラー構文として検知されてしまう場合もあるので自己責任でご使用ください。
##おまけ
当時Unity5.5でコンパイラが更新されたときに大きく取り上げられたのは
Listをforeachしたときに発生していたGCが改善されたとか、
C#5.0 foreach仕様変更 (参考資料)とかがありましたが、
C#6.0が一部使えたことを自分は最近まで知りませんでした。
Unity2018.3にはもうC#7.2が使えるようですが、色んな事情によりUnityのバージョンアップができなかったり、.NET 4.x Equivalentに更新できなかったりしてもC#6.0が(一部だけ)実は使えたので、できたら活用していきたいです。