C# 8.0 Preview
in Visual Studio 2019 (16.0)
岩永 信之
今日の話
• C# 8.0の現状
• Visual Studio 2019 (16.0)はRTMだけど、C# 8.0はプレビュー
• プレビュー機能の有効化方法
• プレビューでも安定している機能としてない機能がある
• どれが安定していて、どれが安定していないか
• 主要なものを中心に新機能紹介
時期的な話
スケジュール感(過去2年分くらい)
VS 15.0 VS 15.3
C# 7.1
VS 15.5
C# 7.0
.NET Core 2.0
VS 15.1 VS 15.2 VS 15.4
C# 7.2
VS 15.6 VS 15.7
C# 7.3
.NET Core 2.1
VS 15.9VS 15.8
.NET Core 2.2
VS 16.0
C# 8.0 Preview
.NET Core 3.0
C# 8.0
(今年後半を予定)(今ここ)
ちょっと延び気味?
• まあ、メジャー リリースなので
• 元々間隔広め
• C# 7.Xの時、しんどかったのかも?
• 7.2とか7.3辺り、バグが多かったから…
• 「プレビュー」という段階を経たい
• 機能盛りすぎかも
• スケジュール管理の仕方次第では「.NET Core 2.3」と「C# 7.4」が
あってもよかった疑惑
• といっても今更区切る余裕はない
• .NET Coreの方は切りどころが難しそう
• C#の方には切りどころあったと思う
• ただ、.NET Coreと足並み揃えたそう
C#プレビュー
• C# 7.2のとき
• C#チーム「プレビューとして出したい」
• Visual Studio「有効化しちゃった」
• C# 8.0では
• ちゃんと、プレビューを使うかどうか選択式に
• LangVersion (C#のバージョン指定オプション)の挙動も変更
• 元々あんなにハイペースでリリース
するつもりではなさそうだった
• 実際、結構バグが多かった
プレビュー機能の有効化
プレビュー機能の有効化方法
• Visual Studio 16.0でC# 8.0を使う方法
• 言語バージョン(LangVersion)を明示
• 8.0の明示
• 「Preview」指定
• プレビューのSDKを使用
• プレビューな.NET Core/.NET Standardをターゲットにすると、既定動作がC# 8.0
• netcoreapp3.0 (.NET Core 3.0)
• netstandard2.1 (.NET Standard 2.1)
• Visual Studio側のオプション指定も必要
言語バージョン (Visual StudioのUI)
プロジェクトのプロパティ
Build
Advanced...
Language version
場所
指定する値
• 8.0 (beta)
• preview of next C# version
バージョンを明示
プレビューを含む最新版
言語バージョン (LangVersion)
• C#コンパイラー オプション
• csproj設定
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<LangVersion>preview</LangVersion>
</PropertyGroup>
</Project>
csc –langversion:preview
LangVersionに関する変更
• 今まで
• default : 最新のメジャー バージョン
• latest : 最新のマイナー バージョン
• 新ルール
• default : 最新のマイナー バージョン(latestと同じ)
• latest : 最新のマイナー バージョン(正式リリースのみ)
• latestMajor : 最新のメジャー バージョン
• preview : (プレビューを含めた)最新バージョン
• 既定動作(未指定時の挙動、default)がlatestと同じに
• latestMajor、previewを新設
プレビューなSDK
• C# 8.0は.NET Core 3.0と同時リリース予定
• .NET Core 3.0にとってはC# 8.0は「プレビュー」ではない
• TargetFrameworkを.NET Core 3.0にするとC# 8.0がdefaultに
• .NET Standard 2.1でもC# 8.0がdefault
• TargetFrameworkを変えるだけでSDK自体が変わる
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netcoreapp3.0</TargetFramework>
</PropertyGroup>
</Project>
ターゲットを.NET Core 3.0にするだけで、
C# 8.0が有効化される
プレビューなSDK (Visual Studioの設定)
• Visual Studio 2019上だとオプション指定が必要
C# 8.0の現状と概要
安定度
• C# 8.0はプレビューという段階を経たい
• 「C# 7.4」があり得たかもしれないくらい、機能ごとに:
• 安定度にばらつきあり
• .NET Core 3.0への依存の有無あり
• C#チーム自身のドッグフーディング(Roslynへの適用)でも
「この機能なら現時点でも使っていい」区分あり※
※ 「Enable C# 8.0 in the code base」参照
• Visual Studio 16.0の時点で
凡例
〇
△
△
◎
×
… 割と安定。大きな変更はなさそう
… 割と不安定。使えるけど少し注意が必要
… 不安定。使えるけど、だいぶ人柱
… 安定。今後そんなに変更もなさそう
… 未実装。.NET Core 3.0正式リリースまでには実装されるはず
割と安定してそうな機能
• 再帰パターン
• switch式
• usingの改善
• using変数宣言
• パターン ベースusing
• 静的ローカル関数
• ローカル関数での引数のシャドーイング
• その他こまごま
• null合体代入
• @$ (順序緩和)
この辺りは全部「Roslyn内
で使っていいもの」扱い
◎
〇 16.1で変更予定あり(演算子の優先度)
〇 16.1で変更予定あり(値型の時の挙動)
不安定な機能
• .NET Core 3.0依存なものは多少怖い
(依存先がプレビューな以上、これから変更が掛かる可能性がある)
• 非同期ストリーム
• await/yield return混在
• await foreach/await using
• インデックス範囲構文(Index型/Range型)
• 単純にタスク山積みで不安定なやつ
• null許容参照型
使っていいけどpublicな
ところでは避けて
まだ使わない
△
△
まだ使わない
そもそもVS2019時点で実装がない(1)
• 16.1で有効化されそうなもの
• インターフェイスのデフォルト実装
• unmanaged generic structs
• 分解右辺のdefault
• readonly関数メンバー
×
そもそもVS2019時点で実装がない(2)
• 予定にはあるけど本当に実装するのか怪しい
• caller expression attribute
• target-typed new
• generic attributes
• stackalloc in nested contexts
• refとpartialの順序緩和
×
さすがに今日は
完全にノータッチ
詳しくはLanguage Feature Statusを参照
大きなものだけまとめ
• 再帰パターン
• switch式
• usingの改善
• 非同期ストリーム
• インデックス範囲構文
• null許容参照型
• インターフェイスのデフォルト実装
◎
〇
◎
△
△
△
×
気持ち的には「C# 7.4」
くらいの感覚で使える
メジャー アップデート
にふさわしい大型機能
ちょっと将来の機能紹介
null許容参照型(NRT)
• nullable reference type、略してNRT
• 参照型でもnullの有無の区別を付けたい
• ? を付けたときだけnullを認める
△
// null を意図していないメソッド
int M1(string s) => s.Length;
// null を意図しているメソッド
int? M2(string s) => s?.Length;
これまで(C# 7.3以前)
区別がつかない
// null を意図していないメソッド
int M1(string s) => s.Length;
// null を意図しているメソッド
int? M2(string? s) => s?.Length;
これから(C# 8.0以降、オプション指定)
nullを認めない
nullを認める
NRTは破壊的変更
• 既存コードの意味が変わる(破壊的変更)
• 単に参照型Tと書いたとき
• 今まで : nullがあり得た
• これから : nullを渡そうとすると警告
• なので、オプションを明示した時だけ有効に
• C# ソースコード中に#nullable
• csproj中にNullableContextOptions
△
#nullable enable
<PropertyGroup>
<NullableContextOptions>Enable</NullableContextOptions>
</PropertyGroup>
NRTは空前絶後の変更
• オプションの有無で文法が変わる
• C#史上初だし、おそらくこの先も二度とない予定
• C#は「昔書いたコードは今のバージョンでも動く」が原則
• 文法の「分岐」も好まない
• 原則を破ってでも入れたい修正
• それほど「billion-dollar mistake※」が大きい
• エラーにはしない
• 全部「警告」
• 警告のもみ消し手段も用意
• #nullable disable
• 後置き ! 演算子
△
※ null参照を最初に発明した人本人が「10億ドルにも相当する私の誤りだ」と明言
インターフェイスのデフォルト実装(DIM)
• default interface method、略してDIM
• インターフェイス内の関数メンバーに実装を持てるように
• インターフェイスに後からメンバーを足せるように
• Java/Swiftとの相互運用(Xamarin)でも必要
×
interface I
{
// 普通の(実装を持たない)メンバー
int M1();
// 実装を持つメンバー
public int M2() => 0;
}
インターフェイスを実装している
既存のクラス側に変更を求めない
DIMはランタイム側の変更が必要
• C#コンパイラーだけではできない機能
• TargetFramework によっては使えない
• 現状では.NET Core 3.0のみが対応
• WPFとかも.NET Core 3.0対応するので、必要なら移行を
• Xamarinはすぐに対応するはず
• Java/Swift相互運用のために必要
• 対応しているランタイムにはRuntimeFeatureクラス※に
DefaultImplementationsOfInterfacesプロパティがある
×
※ System.Runtime.CompilerServices名前空間
ランタイム側の変更は久しぶり
• .NET Framework 2.0 (2005年)以来の「ランタイム側の修正」
• .NET Coreが落ち着いたからできた
• やっと「移植で手一杯」のフェーズが終わった
• 他にも、Hardware Intrinsics※とか、ランタイム特殊対応が増えた
• いろいろなインストール形態ができるからこそ
• OS全体に影響を及ぼすようなインストール形態では怖くてできない
• .NET Coreなら、OS全体/アプリ単位を選べる
×
※ 特定CPU向けの専用命令を出力できるようにする仕組み
今使えそうな機能紹介
安定してて有用そうなのを紹介
• switch式
• 再帰パターン
• using変数宣言/パターン ベースusing
• 静的ローカル関数
• ローカル関数での引数のシャドーイング
• null合体代入
• 非同期ストリーム
◎
〇
◎
△
◎
◎
◎
switch式
• これまで(switchはステートメントだけ)
int y;
switch (e)
{
case 明治: y = 1868; break;
case 大正: y = 1912; break;
case 昭和: y = 1926; break;
case 平成: y = 1989; break;
case 令和: y = 2019; break;
default: throw new InvalidOperationException();
}
// y を使って何か
• caseとか代入とかbreakとかが並んでつらい
• ステートメントなので書ける場所に制約あり
〇
switch式
• switchを式で書けるように(後置き switch + =>)
var y = e switch
{
明治 => 1868,
大正 => 1912,
昭和 => 1926,
平成 => 1989,
令和 => 2019,
_ => throw new InvalidOperationException()
};
// y を使って何か
〇
switc式(注意点)
• 優先度が変わる
• a + x switch { ... } みたいなのの意味が変わる
• 今 : 比較演算子(< とか <= とか)と同じ辺り(かなり低め)
• 予定 : 単項演算子の直後(かなり高め)
• 型推論強化
• 代入先からの推論
or
• 共通基底推論
A x = y switch {… }
を A 扱い
AとBに共通基底Baseがあるとき
x switch { … => new A(), … => new B(), … }
を Base 扱い
〇
再帰パターン
• 例としてこんな型があるとして
• プロパティ2個
• プロパティを初期化するコンストラクター
• プロパティから値を取り出すDeconstruct
◎
readonly struct Point
{
public int X { get; }
public int Y { get; }
public Point(int x, int y) => (X, Y) = (x, y);
public void Deconstruct(int x, int y) => (x, y) = (X, Y);
}
再帰パターン
• 使えるパターンが増えた
• 対称性
◎
static int M(object obj) => obj switch
{
Point _ => 1,
_ => 0,
};
こういうのはC# 7.0からある
static int M(object obj) => obj switch
{
Point(1, 2) => 1,
Point { X: 2, Y: 3 } => 2,
Point _ => 1,
_ => 0,
};
位置パターン(Deconstructを利用)
プロパティ パターン
破棄パターン
var p = new Point(1, 2) p is Point(1, 2)
var p = new Point(x: 1, y: 2) p is Point(x: 1, y: 2)
var p = new Point { X = 1, Y = 2) p is Point { X: 1, Y: 2 }
コンストラクター/初期化子 位置パターン/プロパティ パターン
再帰パターン
• 名前通り、再帰的に書ける
• 型が自明の時には () や {} だけで書ける
• () と {} の混在もできる
◎
x is Line { A: Point (1, var a), B: Point (2, _) }
var p = new Point(1, 2);
p is (1, 2)
p is (1, _) { Y: 2 }
再帰パターン
• タプルとの組み合わせが結構有効
◎
static int CompareTo(int? x, int? y)
=> (x, y) switch
{
(null, null) => 0,
({}, null) => 1,
(null, {}) => -1,
({} x1, {} y1) => x1.CompareTo(y1),
};
using変数宣言
• 変数宣言時にusingを付けると、Disposeが呼ばれる
• 変数のスコープがそのままusingのスコープになる
◎
using var file = File.OpenWrite("out.txt");
file.Write(data);
{
using var x = someDisposable;
...
...
}
...
...
Disposeが呼ばれるのはここ
using変数宣言
• 変数宣言時にusingを付けると、Disposeが呼ばれる
• うかつに使うとDisposeのタイミングが遅れるので注意
◎
using (var file = File.OpenWrite("out.txt"))
{
file.Write(data);
}
// この後だいぶ長い処理
こういうのをusing変数宣言に書き換えると
Dispose呼び出しが遅れてまずい
using変数宣言
• 変数宣言時にusingを付けると、Disposeが呼ばれる
• うかつに使うとDisposeのタイミングが遅れるので注意
• 必要な範囲だけメソッド抽出してしまえば気兼ねなく使える
◎
using var file = File.OpenWrite("out.txt");
file.Write(data);
// この後だいぶ長い処理
ダメ!絶対!
static void M(ReadOnlySpan<byte> data)
{
using var file = File.OpenWrite("out.txt");
file.Write(data);
}
パターン ベースusing
• usingステートメントで使える条件が緩和された
• 今まで: IDisposableインターフェイスの実装が必須
• C# 8.0 : ref structに限り、IDisposable実装がなくてもOK
◎
struct Disposable : IDisposable
{
public void Dispose() { }
}
今までは必須
ref struct Disposable : IDisposable
{
public void Dispose() { }
}
ref structにインターフェイス実装
はできない(コンパイル エラー)
ref struct Disposable
{
public void Dispose() { }
}
なので、パターンさえ満たせば
usingステートメントで使えるように修正
静的ローカル関数
• ローカル関数外の変数のキャプチャ
◎
static void M(int a)
{
// 外の変数・引数を使ってる(キャプチャしてる)
int f1(int x) => a * x;
// 使ってない
int f2(int x, int y) => x * y;
}
• キャプチャの有無で性能が変わる
• 大体、無い方が性能がよくなる
• 本当に意図的なキャプチャなのか
• 実は避けれたのにミスしてないか
静的ローカル関数
• ローカル関数外の変数のキャプチャ
• static修飾することで、キャプチャしないことを明示できる
◎
static void M(int a)
{
// 外の変数・引数を使ってる(キャプチャしてる)
static int f1(int x) => a * x;
// 使ってない
static int f2(int x, int y) => x * y;
}
キャプチャしちゃってると
コンパイル エラーに
キャプチャしていないことを宣言
引数のシャドーイング
• ローカル関数外の変数と同名の変数を使えるように
• staticを付けておけば外の変数と混ざることはない
◎
static void M(int a)
{
// 外の変数・引数を使ってる(キャプチャしてる)
int f1(int x) => a * x;
// 使ってない
static int f2(int a, int y) => a * y;
}
外にある変数と同名の別変数
Mの引数のa f2の引数のa
• 外のを隠すのでshadowingと呼ぶ
• staticでなくても使えるけど外の
ものと混ざらないよう要注意
null合体代入
• よくあるキャッシュ処理の類
• 複合代入 ??= で書けるように
public string Data => _data = _data ?? GetData();
private string _data;
private string GetData()
{
// 1度きりしか呼ばないけどものすごく重たい処理
}
public string Data => _data ??= GetData();
private string _data;
private string GetData()
{
// 1度きりしか呼ばないけどものすごく重たい処理
}
〇
int x = 1;
int? y = null;
var z = y ??= x;
16.1でちょっと変更ありそう
• zはintかint?か
• 16.0ではint?
• 16.1ではint
.NET Core 3.0/.NET Standard 2.1推奨
非同期ストリーム: await/yield混在
• 生成側: 非同期イテレーター
△
async Task<Page> GetPageAsync()
{
// ネット越しに取りたいので async
await Task.Delay(1); // 代用
return new Page();
}
IEnumerable<string> GetTitles(Page page)
{
foreach (var s in page.Sections)
yield return s.Title;
}
非同期メソッド(C# 5.0) イテレーター(C# 2.0)
async IAsyncEnumerable<string> GetTitlesAsync()
{
Page page;
do
{
page = await GetPageAsync();
foreach (var title in GetTitles(page))
yield return title;
} while (page.HasNext);
}
• awaitとyieldの混在
• 戻り値はIAsyncEnumerable
非同期ストリーム: await foreach/using
• 消費側: 非同期foreach
• await foreachの語順
• IAsyncEnumerable<T>に対して使える
• (System.Collections.Generic名前空間)
• 同期版同様、パターン ベース
(同じメソッドさえ持っていれば任意の型に使える)
△
.NET Core 3.0/.NET Standard 2.1推奨
await foreach (var title in GetTitlesAsync())
{
Console.WriteLine(title);
}
インデックス範囲構文
• 整数インデックスを持つ型の一定範囲(range)得るための構文
• x[^i] で Length - i 番目の要素参照
• Index構造体(System名前空間)が作られる
• Index i = ^1; みたいな変数定義も可能
• x[i..j] で i(含む)からj(含まない)の範囲を参照
• Range構造体(System名前空間)が作られる
• Range r = 1..^1; みたいな変数定義も可能
△
.NET Core 3.0/.NET Standard 2.1推奨
まとめ
• C# 8.0はプレビューだけどもう使える
• C# 8.0の新機能
• 割と今でも安定 : switch式、再帰パターン、using、??=
• .NET Core 3.0依存 : 非同期ストリーム、Range
• まだまだ変更だらけ : null許容参照型
• VS 16.1辺りで実装予定 : インターフェイスのデフォルト実装
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<LangVersion>preview</LangVersion>
</PropertyGroup>
</Project>
(他にも細々)

C# 8.0 Preview in Visual Studio 2019 (16.0)