C#Day 16

これから来る未来のC#8.0を覗き見てみよう


最初に

本記事はC#8.0に関する情報を自分なりにまとめて書いたものとなります。

普段C#をメインで使用している人もUnityを使ってゲーム開発している人にも、これからのC#に関する予習となれば幸いです。


C#8.0について

C#は2019年にメジャーアップデートを予定しております。それがC#8.0となります。これら言語機能に関してはVS2019のβ版で試すことが可能となっています。


C#8.0で追加予定の機能一覧


  • null許容参照型

  • 再帰パターンマッチング

  • Switch式

  • new式での型省略

以下の新機能は「.NET Standard 2.1」に含まれるフレームワーク型を基盤としているため、「.NET Standard 2.1」を実装する.NET Core 3.0、Xamarin、Unity、Mono上では動作します。「.NET Framework 4.8」は「.NET Standard 2.1」を実装しないため、使用不可となっています。


  • Interfaceのデフォルト実装

  • 非同期ストリーム

  • Range型

  • Index型


null許容参照型


概要

参照型にて、T(非null許容)T?(null許容)を区別するための機能となります。非null許容型の変数にnullを代入したり、null許容型参照の変数へ逆参照すると、コンパイラから警告が発生するようになります。

この機能は、既存コードに対する警告が唐突に増えて開発者が困惑するのを避けるため、オプトインで有効になるような状態になっています。

null許容参照型の登場によって「null参照例外」のリスクを事前に回避可能となります。もともとC#自体には「対象のオブジェクトがnullを許容するかどうか」という意図を表現するための構文がなかったため、オブジェクトが持つメンバにアクセスしようとした時に例外(System.NullReferenceException)が発生する危険性が常にありました。このnull許容参照型を使用することで「このオブジェクトはnullにはできません」「このオブジェクトは場合によってはnullとなる可能性があり、それは想定の範囲内です」といった考えを示すことができます。

もう一つのメリットとして挙げられるのが、一貫性です。値型では"?"を使用することでnull許容型とすることが出来ていましたが、参照型では規定でnullが許容されていたため、言語としての一貫性に欠けていました。C#言語開発チームは参照型でもnull許容を用意することで、この欠点を改善するように目指しています。


書き方

既に値型でのnull許容に慣れている方なら直感的にわかるように、参照型でもnullを許容する場合は"T?"という構文を使用して、明示的に指定する必要があります。

public void DisplayPersonAge(Person? person)

{
// ここでnullチェックをせずに person を使用しようとすると警告が表示される。
// Console.WriteLine(person.age);
if (person != null)
{
Console.WriteLine(person.age);
}
}

「null参照例外をthrowしない」とコンパイラに明示する、別の書き方もあります。

public void DisplayPersonAge(Person? person)

{
int age = person?.age ?? 0;
Console.WriteLine(age);
}




非null許容型にnull許容型を代入するための書き方も存在します。その場合、null許容型の変数から値を読み込むときにはnullを明示的にチェックするか、"!"演算子を用いることでプログラマがnullではないことを保証するとしてコンパイラに伝えて、警告を抑えることができます。

string? text = null;

text = "abc";

int age = Person!.age;


再帰パターンマッチング

C#8.0からはパターンマッチングのパターンの種類として以下が増える予定です。

パターン
概要

位置パターン

タプルの分解と同じ要領で引数の位置に応じて再帰的にマッチングする。

プロパティパターン
プロパティに対して再帰的にマッチングする。


Switch式

switch文がもっと簡潔で使いやすくなります。具体的にはcaseにあたる部分が式となり、casebreakreturnを消すことができます。

var pet = "猫";

var maxAge = pet switch
{
"猫" => 15,
"犬" => 13,
"鼠" => 5,
_ => 0
}

また、パターンマッチングと組み合わせることで、より柔軟なコードが書けるようになります。

static string PetDetails(Animal animal)

{
return animal switch
{
Cat cat => $"Cat details: {cat.Age} {cat.Cry}",
Dog dog => $"Dog details: {dog.Temper} {dog.Cry}",
Animal(0, _) => "baby",
Animal(Age: var age, Cry: var cry) = > $"details: {age} {cry}",
_ => "nothing"
};
}


new式での型省略

オブジェクトを作成するコードにて、前後関係から型を推論できる場合は型の指定記述を省略できるようになります。

Dictionary<int, string> dictionary = new();


Interfaceのデフォルト実装

C#のインターフェイスにメソッドの実装を含めることが可能となりました。これによってライブラリ等の作成者が既存のクラスが実装しているメソッドを壊さずに、振る舞いを変更して拡張できるメリットが生まれます。また、新たにデフォルト実装を持つインターフェイスを実装するクラスは、独自の実装が必要ないのであれば実装を行う必要はありません

interface IDisplayLog

{
void DisplayLog(string message) { Console.WriteLine(message); }
}

メソッドの他にも以下のボディと静的メンバを追加することができます。


  • インデクサー

  • プロパティ

  • イベント

ただしインスタンスフィールドはインターフェイスでの定義が許可されていません。そのため、バッキングフィールドを宣言する自動プロパティも同様に許可されません。

C#8.0では、この機能においてダイヤモンド継承問題を解決するために曖昧さ回避策もとっています。

具体的には、以下のようになります。


  • 同じ名前のメンバがインターフェイスAとBから継承されている場合は、BがAから派生している場合、Bを優先します。

  • AとBが継承関係ではない場合、開発者は自分が使用するオーバーライドを指定するか、独自の実装を記述する必要があります。


非同期ストリーム

IEnumerable<T>の非同期バージョンのインタフェースであるIAsyncEnumerable<T>が追加されます。C#言語開発チームはこの機能に対し、2015年から取り組んでいたようです。

async Task<int> GetResult()

{
// なんやかんや
}

async IAsyncEnumerable<int> GetResults()
{
foreach await (var result in GetResult())
{
if (result > 0) yield return result;
}
}


Range型

2つのIndex値を使用して、範囲のStart値とEnd値を「x..y」という形で書くことができます。Range型の変数には宣言された範囲を表す構造体が生成されます。

var range = 0..10;




Rangeの書式でパターンマッチングにも使用することができます。

switch (num)

{
case 1..7:
// numの数値が1~7の範囲かどうか
break;
}


Index型

int型の値を使用して、配列の先頭または末尾から数えることができるインデックスとして使用できる型です。プレフィックス演算子"^"を使用することで末尾から数えるよう指定できます。

Index fromBegin = 1;

Index fromEnd = ^2;
int[] num = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 };
// 出力結果は 1,7
Console.WriteLine($"{num[fromBegin]}, {num[fromEnd]}");


最後に

今回C#アドカレ用として記事を書いていたのですが盛大に遅刻しました、誠に申し訳ないです。ただ、これからのC#8が楽しみで調べて書くのが楽しかったです。どんどん便利になっていくC#から目が離せません。また今回の記事では岩永さんの記事をめちゃくちゃ参考にさせて頂きました、有難うございました。


参考