Unity2020.2から使えるC#8の機能をまとめました。
- Unity2020.2からC#8の機能が使えるようになりました
- readonly members
- Switchが式として使えるように
- パターンマッチングの強化
- using宣言
- 静的ローカル関数
- 破棄可能なref構造体
- null参照許容型
- null合体割り当て演算子
- Genericな構造体のアンマネージドの取り扱い
- 文字列挿入の書き方
- 関連
- 参考
Unity2020.2.3
Unity2020.2からC#8の機能が使えるようになりました
Unity2020.2のアップデートで、C#8の機能の大部分が使えるようになりました。
以下のページに列挙されている機能のうち一部機能を除いたものが使えます。
使えない機能
ライブラリ依存やCLIを解釈するランタイムの制限により以下の機能は使えないようです。
- インターフェースのデフォルト実装(Default interface methods)
- 非同期ストリーム(Asynchronous streams)
- 非同期Disposable(Asynchronous disposable)
- 範囲アクセス(Indices and ranges)
readonly members
C#8では構造体のメソッドに以下のようにreadonlyキーワードを付けられるようになりました。
using System; using UnityEngine; public struct Position { public float X; public float Y; public readonly void LogDistance() { Debug.Log(Math.Sqrt(X * X + Y * Y)); } }
これは「そのメソッドが構造体の変数を書き換えないことを保証する」ためのものです。
readonlyなメソッド内では変数を書き換えることができません。
またreadonlyなメソッドからreadonlyではないメソッドを呼び出すと、以下のような警告が表示されます。
warning CS8656: Call to non-readonly member 'Position.Distance.get' from a 'readonly' member results in an implicit copy of 'this'.
using System; using UnityEngine; public struct Position { public float X; public float Y; public double Distance => Math.Sqrt(X * X + Y * Y); public readonly void LogDistance() { // 書き換えはできない X = 1; // readonlyじゃないメソッドを呼び出すと警告 Debug.Log(Distance); } }
この機能を使って変数の書き換えを行わないメソッドをreadonlyとして定義しておくことにより、
構造体のDefensive Copy(防御的なコピー)を防ぐことができます。
Switchが式として使えるように
Switchが以下のように式として使えるようになりました。
using System; using UnityEngine; public static class Example { public enum MyColor { Red, Green, Blue } // Switchが式として使えるようになった public static Color ToColor(this MyColor color) => color switch { MyColor.Red => Color.red, MyColor.Green => Color.green, MyColor.Blue => Color.blue, _ => throw new ArgumentException("invalid enum value", nameof(color)) }; }
パターンマッチングの強化
C#8ではパターンマッチングが強化されました。
具体的には以下のようなことができるようになりました。
プロパティパターン
プロパティパターンを使うとプロパティやフィールドの値を使って以下のようなSwitch文の判定ができます。
public class Example { public class Person { public string Name; public int Age; } public bool CanDrink(Person person) { switch (person) { // プロパティやフィールドを使って判定できる case { Name: "Taro" }: return false; case { Age: var age } when age >= 20: return true; default: return false; } } }
もちろん上述のSwitch式と組み合わせることも可能です。
public bool CanDrink(Person person) { // Switch式 return person switch { { Name: "taro" } => false, { Age: var age } when age >= 20 => true, _ => false }; }
タプルパターン
タプルパターンを使うとC#7で追加されたタプル型でSwitchの条件分岐を判定できます。
public string GetCommand(bool shift, string keyCode) { switch (shift, keyCode) { // タプル型で判定できる case (true, "S"): return "Save"; case (true, "Z"): return "Undo";; case (true, "Y"): return "Redo"; default: return null; } }
Switch式で書く場合はこんな感じです。
public string GetCommand(bool shift, string keyCode) => (shift, keyCode) switch { (true, "S") => "Save", (true, "Z") => "Undo", (true, "Y") => "Redo", _ => null };
なおタプルなどC#7の主要機能については以下の記事にまとめています。
位置指定パターン(Positional Patterns)
Deconstructメソッドを定義してあるクラスは分解しつつSwitchすることができます。
タプルと似ていますがこれは位置指定パターンといいます。
public struct Input { public bool Shift; public string Key; // Deconstructメソッドで分解できるように public void Deconstruct(out bool shift, out string key) { (shift, key) = (Shift, Key); } } public string GetCommand(Input input) { switch (input) { case (true, "S"): return "Save"; case (true, "Z"): return "Undo";; case (true, "Y"): return "Redo"; default: return null; } }
Switch式で書く場合はこんな感じです。
public string GetCommand(Input input) => input switch { (true, "S") => "Save", (true, "Z") => "Undo", (true, "Y") => "Redo", _ => null };
using宣言
変数をusing付きで宣言できるようになりました。
using付きで宣言した変数は、そのスコープを抜けた時にDisposeされます。
要は今まで↓のように書いていたのが
private class FooDisposable : IDisposable { public void Dispose() { Debug.Log("Dispose"); } } private void Foo() { using (var foo = new FooDisposable()) { // 何らかの処理 // 抜けるときにfooはDisposeされる } }
以下のように書けるようになります。
private class FooDisposable : IDisposable { public void Dispose() { Debug.Log("Dispose"); } } private void Foo() { // using付きで変数宣言 using var foo = new FooDisposable(); // 何らかの処理 // 抜けるときにfooはDisposeされる }
静的ローカル関数
C#7で追加されたローカル関数に、C#8でstatic修飾子が付けられるようになりました。
staticなローカル関数は、その外側の変数をキャプチャできなくなります。
private void Foo() { var bar = 1; static void SomeStaticLocalMethod() { // これが出来ない var barCopy = bar; } }
ローカル関数や匿名メソッドが外側の変数をキャプチャするとメモリのアロケーションが発生します。
使いどころを誤るとパフォーマンスの低下につながるため、キャプチャを行わないことを明示的に表せる静的ローカル関数は有用です。
なお匿名メソッドによるメモリ割り当てについては以下の記事に少し書いています。
破棄可能なref構造体
今までref構造体にはインターフェースの実装ができないためDisposeメソッドを実装できませんでした。
C#8からはref構造体に関してはpublicなDisposeメソッドを定義しておけばusingステートメントを使えるようになりました。
private ref struct FooStruct // IDisposableは不要(というかできない) { // publicなDisposeメソッドを実装しておく public void Dispose() { Debug.Log("Dispose"); } }
null参照許容型
C#の参照型はデフォルトでnullを許容します。
null参照許容型を使うとnullを許容するかしないかを明示的に指定できるようになります。
使用するためには#nullable enable
ディレクティブを記述する必要があります。
// 有効化するためにコレを書いておく必要がある #nullable enable public class Example { // not nullableな参照型を定義 // 初期化されていないと警告が表示される public string NotNullableText; // こっちはnullableなので警告なし public string? NullableText; private Example() { // nullを代入しようとすると警告が表示される NotNullableText = null; } }
null合体割り当て演算子
「参照型変数がnullだったらインスタンスを生成して代入する」みたいなケースでnull合体割り当て演算子が使えるようになりました。
以下のように??=
と書いて使用します。
public void Foo() { List<int> nums = null; // numsがnullだったらListを作っていれる nums ??= new List<int>(); // これと同じ nums = nums ?? new List<int>(); nums.Add(1); nums.Add(2); nums.Add(3); }
Genericな構造体のアンマネージドの取り扱い
C#8からはGenericな構造体であっても、アンマネージド型だけを持つ場合にはアンマネージドとして取り扱えるようになりました。
public struct Foo<T> { public T Bar; } // こう使う場合はアンマネージド型になる private Foo<int> _foo;
文字列挿入の書き方
C#6から文字列の前に$を付けることでstring.Formatのような書き方ができるようになりました。
さらに$@を付けることで複数行の文字列に対してこれを適用できました。
C#8からはこの「$@」を「@$」とも書けるようになりました。