Upload
-
View
9.137
Download
0
Embed Size (px)
Citation preview
C# 6.0
C#とともに祝15周年
岩永信之
数年待った新仕様
• async/awaitの情報が出始める
• Visual Studio 2012でC# 5.0正式リリース
• C# 6.0の情報が出始める
• C# 7.0の提案が始まる
• Visual Studio 2015でC# 6.0正式リリース
2010
2011
2012
2013
2014
2015
潜伏期• 裏でRoslynを作ってて、言語的な新機能何もなし
久しぶりに楽しい時期
Roslyn (潜伏の理由)
•新コンパイラー(製品名 .NET Compiler Platform)
•作り直し• C++コンパイラーの保守がもうつらい
• Visual Studio拡張と、コンパイラー用のコード2重開発がつらい
• C#チームが作っているコンパイラーの力を、Visual Studio拡張を作っているサードパーティ企業・個人開発者が活用できないのがつらい
• C#でC#コンパイラーを、VBでVBコンパイラーを
• 単一コンパイラーでコンパイルとVisual Studio拡張を
• コンパイラーの内部データをとれるAPI公開
.NET 2015とRoslyn
language toolruntime library ecosystem
C# 6.0/VB 14 Ryu JIT.NET Native
.NET Fx 4.6.NET Core 5
Visual Studio2015
NuGet
.NET 2015
新しいコンパイラー
csc.exe/vb.exe
コード解析ライブラリ
Microsoft.CodeAnalysis Roslyn
それぞれ独立• C# 6.0で.NET Framework 2.0開発とかも可能• .NET 4.5以上をインストールすればMicrosoft.CodeAnalysis利用可能
• Visual Studio 2012は最初から.NET 4.5
C# 6.0
•テーマ(?): Just-do-it (いいからやれよ)• Roslyn化で手いっぱい→ 大きなものが入らない
• 潜伏期間が長すぎた。これ以上待たせたくない
• 大きなものはC# 7.0行き
• Roslynにしたからこそ→ 細々とした機能を保守できる• コンパイラー自身がC#製になったから本気出す
• 「よくあるパターン」のめんどくささを改善
C# 7.0 (提案開始、ディスカッション中)
•テーマ• データ
• Null• パフォーマンスと信頼性
• コンポーネント化
• 分散コンピューティング
• メタプログラミング
https://github.com/dotnet/roslyn/issues/98
この辺りのテーマはC# 6.0でいくつか簡単なものが入ってる
本日はC# 6.0の方のみ
• C# 6.0の新機能
• Deep Dive• 用途・効能(実測)
• 背景・裏話
自動プロパティの改善 式形式の関数メンバー
using static null条件演算子 文字列挿入
nameof式 インデックス初期化子
catch/finally内でawait 拡張メソッドでコレクション初期化子
例外フィルター
プロパティやメソッドの改善
•プロパティやメソッドがだいぶ書きやすく• 式形式の関数メンバー(expression bodied function members)
• メソッド/演算子(expression bodies on method-like members)
• プロパティ/インデクサー(expression bodies on property-like members)
• 自動プロパティの改善(auto-property enhancements)• 自動プロパティ初期化子(auto-property initializers)
• getter-onlyな自動プロパティ(getter-only auto-properties)
背景:よくあるコード(1)
•割と単純な計算
class Calculator{
public double Square(double x) { return x * x; }}
背景:よくあるコード(2)
•子オブジェクトに丸投げ
class Sample1 : Sample{
Sample _inner;public override string X() { return _inner.X(); }
}
背景:よくあるコード(3)
• readonlyにするためだけにフィールド定義
class Immutable{
private readonly int _x;public int X { get { return _x; } }
public Immutable(int x) { _x = x; }}
背景:よくあるコード(4)
•初期化するためだけにフィールド定義
class Root{
private List<string> _items = new List<string>();public IList<string> Items { get { return _items; } }
}
背景:共通していえること
• { return ; }うざい
•しかも頻出
public double Square(double x) { return x * x; }
public int X { get { return _x; } }
public IList<string> Items { get { return _items; } }
public override string X() { return _inner.X(); }
特にこいつなんて、書きたいもの(_x)に対してノイズ({ get { return ; } })が多すぎ
式形式のメソッド/演算子
• returnステートメント1つだけの場合、=> を使って短縮可能• ラムダ式と同じルール
public override string X() { return _inner.X(); }
public double Square(double x) { return x * x; }
public override string X() => _inner.X();
public double Square(double x) => x * x;
式形式のプロパティ/インデクサー
• getter-onlyな場合だけ、同様に =>を使って短縮可能• { get { return ; } }を消せる
public int X { get { return _x; } }
public int X => _x;
自動プロパティ初期化子
•自動プロパティに対して初期化子が書けるように• 明示的なフィールド定義が不要に
class Root{
private List<string> _items = new List<string>();public IList<string> Items { get { return _items; } }
}
class Root{
public IList<string> Items { get; private set; } = new List<string>();}
getter-onlyな自動プロパティ
•コンストラクター内でだけ初期化できる自動プロパティ• { get; }だけ書く
class Immutable{
private readonly int _x;public int X { get { return _x; } }public Immutable(int x) { _x = x; }
}
class Immutable{
public int X { get; }public Immutable(int x) { X = x; }
}
没案: プライマリコンストラクター
•当初予定では、もう1つ文法が提案されてた• C# 7.0で、より高度な新機能に統合予定
• プライマリコンストラクター(没) → レコード型(7.0)
class Immutable(int x){
public int X { get; } = x;}
class Immutable{
public int X { get; }public Immutable(int x) { X = x; }
}
プライマリコンストラクター(没)未
効能
•どのくらいのインパクトがあるか実測• 計測用のプログラム:
• https://github.com/ufcpp/UfcppSample/tree/master/Scribble/FindSingleStatementBody
• せっかくなのでMicrosoft.CodeAnalysisライブラリを利用
• 今仕事で使っているソリューションを1個、集計してみた
効能:メソッド統計
全体, 16260
0% 10% 20% 30% 40% 50% 60% 70% 80% 90% 100%式1つだけ, 8343 それ以外, 7917
0% 10% 20% 30% 40% 50% 60% 70% 80% 90% 100%全体のうち51.3%が => 化の恩恵を受ける
効能:プロパティ統計
全体, 5385
0% 10% 20% 30% 40% 50% 60% 70% 80% 90% 100%getのみ, 2225 自動実装, 1690 1470
0% 10% 20% 30% 40% 50% 60% 70% 80% 90% 100%式1つ, 2101 自動実装, 1690 1470
0% 10% 20% 30% 40% 50% 60% 70% 80% 90% 100%243 1858 372 1318 1470
0% 10% 20% 30% 40% 50% 60% 70% 80% 90% 100%
フィールドの値を返すだけ private set
全体のうち39%getのみのプロパティの94.4%が => 化の恩恵を受ける
効能:プロパティ統計
全体, 5385
0% 10% 20% 30% 40% 50% 60% 70% 80% 90% 100%getのみ, 2225 自動実装, 1690 1470
0% 10% 20% 30% 40% 50% 60% 70% 80% 90% 100%式1つ, 2101 自動実装, 1690 1470
0% 10% 20% 30% 40% 50% 60% 70% 80% 90% 100%243 1858 372 1318 1470
0% 10% 20% 30% 40% 50% 60% 70% 80% 90% 100%
フィールドの値を返すだけ private set
合計615個(全体の11.4%)※が自動プロパティの改善の恩恵受ける
※ public setなものの多くがJSON化の都合でpublic。実際には書き替えしてない
using static
•静的メソッドを、メソッド名だけで呼べるように• using static構文を使う
using System;
public class Program{
static void Main(){
Console.WriteLine("hello");}
}
using static System.Console;
public class Program{
static void Main(){
WriteLine("hello");}
}
System.Console.WriteLineが呼ばれる
using staticの用途(1)
•クラスにあまり意味がないもの• グローバル関数の代わり
• Mathとかが好例
using static System.Math;
class MathSample{
double F(int x) => x * Log(x);double G(int x) => Sin(x) * Exp(x);
}
数学関数ばっかり使うような文脈で、1個1個 Math.を付けたくない
using staticの用途(2)
•ファクトリメソッド• 諸事情あってコンストラクターを公開せず、メソッド越しに作るもの
• Expressionとかが好例
using System.Linq.Expressions;using static System.Linq.Expressions.Expression;
class ExpressionSample{
static ParameterExpression x = Parameter(typeof(int));static ParameterExpression y = Parameter(typeof(int));
static Expression ex = Lambda(Add(x, y), x, y);}
Expressionクラスの静的メソッドでインスタンス生成
元々はC#スクリプト用
• Roslynの最終目標には「C#スクリプト」も入っている• (手が回っていなくて未実装)
• 自作アプリに組み込んでマクロ実行
• REPL上で1行1行C#ステートメントを実行
•スクリプト実行時限定のつもりで機能を追加した
•あまりにも要望多すぎて、通常のC#にも実装
静的プロパティ/非静的クラス
•静的プロパティ可
•静的クラスである必要ない
using static System.DateTime;using static System.Console;
public class Program{
public static void Main(){
WriteLine(Now);}
}
普通の(static が付かない)クラス
(こっちは静的クラス)
静的プロパティ
拡張メソッド
•拡張メソッドは、静的メソッドとしては呼べない• 拡張メソッドとしては使える
LINQでおなじみのEnumerableクラス
Enumerable中の拡張メソッド
拡張メソッドを静的メソッドとしては呼べない
staticが付く理由
•もし、staticを付けないと• 当初提案では付けなくてもよかった
• 結構極悪なコードを書けてやばかったから修正
using System.Linq;
namespace System{
public static class Linq{
public static string nameof(Action x) => "";}
}
既存の名前空間と同名のクラス
元とは別の定義を紛れ込ませられる
没
null条件演算子(null conditional operator)
• ?.で nullチェック +メンバーアクセス• 左辺が nullだったら戻り値に null伝搬
• ??と組み合わせるのも有効
string name;if (unit == null) name = "空欄";
else name = unit.Master.Name;
var name = unit?.Master.Name ?? "空欄";
var name = unit == null ?"空欄" :
unit.Master.Name;あるいは
インデクサー/多段チェック
• ?[]でインデクサーに対しても nullチェック可能
• 1つの式で多段チェック可能
var m = team.Units?[0]?.Master;
var unit = team.Units != null ?team.Units[0] :null;
var m = unit != null ?unit.Master :null;
デリゲート/イベント呼び出し
• ?()とは書けない• 条件演算子 ? :との区別が構文上難しい
•デリゲートなら、?.Invokeで代用可能
void OnPropertyChanged(string propertyName)=> PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
用途
• null許容な文脈と、非null許容な文脈ははっきり分かれる
•許容する方で活躍するはず
例
一覧画面
A B
(順序・位置などに意味があるような)一覧画面では「空欄」があり得る
空欄をnullで表したり
C
詳細画面
Cweight : 327dimension: 14.6×14×3.5
詳細画面では無効な項目の表示は想定しない
タップで画面遷移
null許容(nullable) 非null (non-nullable)
没案: null非許容
•より重要なのは非nullの方• 現状のC#ではnullを認めないコードを書くのが大変
• C# 7.0で検討中
void ShowDetail(Unit! unit){
...}
(現状の案)!で非nullを明示
※ 既存のC#コードを壊さないように非null対応するのはかなり大変で妥協が必要• 古いコードが混在すると100%の非null保証は無理
• !を付ける必要あり
未
文字列挿入(string interpolation)
•文字列整形がだいぶ楽に• $から文字列を始める
• {}の中に任意の式が書ける
string.Format("({0}, {1})", x, y);string.Format("数値: {0:n}, 通貨: {0:c}", x);
$"({x}, {y})"$"数値: {x:n}, 通貨: {x:c}"
書式設定も使える
複数行
• $@から始める
• ちなみに逆(@$)はダメ(コンパイルエラー)
Console.WriteLine($@"x: {x}y: {y}");
string.Format化
•コンパイル結果
• 要望多かったのに今まで入れなかった理由は単純に「ライブラリでできることはライブラリでやれ」
• まさに「just-do-it」の代表格• Roslynになって保守コストが下がったから入れれた
string.Format("({0}, {1})", x, y);
$"({x}, {y})"
まんま、string.Formatが生成されるだけ
IFormattable
• IFormatProviderを指定したい場合• (カルチャー指定などをしたい場合)
• stringじゃなくてIFormattableで受ける
• .NET 4.6/.NET Core 5を必要とする唯一の機能• FormattableStringは新しく追加された型
• (他は、await除けば.NET 2.0で動く)
IFormattable f = $"{x:c}, {x:n}";var s = f.ToString(null, new CultureInfo("en-us"));
FormattableString型のインスタンスが作られる
カルチャー指定して文字列化
nameof式(nameof expression)
•識別子(変数名、メンバー名、クラス名…)を文字列リテラル化
if (x < 0)throw new ArgumentException("x must be positive");
if (x < 0)throw new ArgumentException(nameof(x) + " must be positive");
リファクタリングの対象にならない
リファクタリングの対象になる• 「リネーム」できる• 「定義に移動」できる• 「参照の検索」できる
nameof式(nameof expression)
•結構複雑な式も書ける
var now = DateTime.Now;var x1 = nameof(now.AddHours);var x2 = nameof(IList<int>.Count);var x3 = nameof(System.Text.RegularExpressions.Regex);
AddHoursCountRegex
補足: CallerMemberName
• PropertyChanged用途だと、こんな方法も
• ただし、これでは不十分場合あり
private int _x;
public int X{
get { return _x; }set { _x = value; OnPropertyChanged(); }
}
void OnPropertyChanged([CallerMemberName] string propertyName = null)=> PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
自動的に"X"の名前が渡る
補足: CallerMemberNameでは不十分
•パフォーマンス考えるとよりよい書き方があって
public int X{
get { return _x; }set { _x = value; PropertyChanged?.Invoke(this, XProperty); }
}private static readonly PropertyChangedEventArgs XProperty
= new PropertyChangedEventArgs(nameof(X));
同じインスタンスを使いまわす方がメモリ効率がいい
この場合、CallerMemberNameでは役に立たない
補足: CallerMemberNameでは不十分
•他のプロパティに依存したプロパティpublic int X{
get { return _x; }set { _x = value; OnPropertyChanged(); OnPropertyChanged(nameof(Sum)); }
}public int Y{
get { return _y; }set { _y = value; OnPropertyChanged(); OnPropertyChanged(nameof(Sum)); }
}
public int Sum => X + Y;
X, Yの一方でも変化したらSumの値も変化する
この場合、CallerMemberNameでは役に立たない
インデックス初期化子
•オブジェクト初期化子内にインデクサーを書ける
•利点• オブジェクト初期化子で、プロパティと混ぜれる
• 式になる• =>で使える、フィールド初期化子で使える、式ツリー化できる
var d = new Dictionary<string, int>{
["X"] = 1,["Y"] = 2,
};
light weight dynamic
•裏にあるストーリーは結構壮大• 場合によっては、辞書的にプロパティアクセスしたい
• ライブラリ内がリフレクションだらけになったり
{"Id": 10,"Position": { "X": 1, "Y": 2 }
}
<TextBox Text="{Binding Id}" /><TextBox Text="{Binding Position.X}" /><TextBox Text="{Binding Position.Y}" />
public int X{
set{
_x = value;OnPropertyChanged(nameof(X));
}}
プロパティ名がキーの辞書
light weight dynamic
•プロパティアクセスと辞書アクセスを近づける発想
var p = new Point();p.X = 1;p.Y = 2;
var d = new Dictionary();d["X"] = 1;d["Y"] = 2;
var p = new Point{
X = 1,Y = 2,
};
var d = new Dictionary{
["X"] = 1,["Y"] = 2,
};
var d = new Dictionary();d.$X = 1;d.$Y = 2;
var d = new Dictionary{
$X = 1,$Y = 2,
};
没
没
一瞬、こういう文法が提案されてた(キモいって言うやつ多すぎるからやめた)
Working with data
• C# 7.0の大きなテーマの1つ
• light weight dynamicはそのはしり
• 7.0にご期待ください• レコード型、タプル型
「データ」が主役
var d = new Dictionary{
["X"] = 1,["Y"] = 2,
};
低コスト、低リスクなこいつだけが6.0に残った
例外フィルター
•例外 catchに条件を付けれるように
•ちなみに• C#的には新機能だけど、.NET的には1.0のころから持ってる機能
• C#がそれに対応しただけ
try{
…}catch (Exception ex) when (ex.InnerException is IOException){
…}
用途: inner exceptionでcatch
static void Main(){
try{
RunAsync().Wait();}catch (InvalidOperationException){
Console.WriteLine("ここを通ってほしい");
}}
static async Task RunAsync(){
await Task.Delay(1);throw new InvalidOperationException("");
}
実際にここに来るのは AggregateException
もちろんcatchできない
用途: inner exceptionでcatch
static void Main(){
try{
RunAsync().Wait();}catch (AggregateException ae)
when (ae.InnerExceptions.Any(e => e is InvalidOperationException)){
Console.WriteLine("ここを通ってほしい");
}}
用途:複数種の例外
•複数種類の例外に対して同じ処理
try{
…}catch (Exception ex)
when (ex is AccessViolationException || ex is FileNotFoundException){
…}
没案: if
•当初提案ではifキーワードだった
• {}のちょっとした位置で意味が全く変わるとかちょっと
try{}catch (Exception ex) if (ex.InnerException is IOException) { … }
try{}catch (Exception ex) { if (ex.InnerException is IOException) … }
没
catch句、finally句でのawait
AsyncResource asyncResource = null;try{
asyncResource = new AsyncResource();}catch (Exception ex){
using (var w = new StreamWriter("log.txt"))await w.WriteAsync(ex.ToString());
throw;}finally{
if (asyncResource != null)await asyncResource.DisposeAsync();
}
catch句内でawait用途:• ログ記録
(ファイル書き込みにしろサーバーに送るにしろ非同期)
finally句内でawait用途:• 非同期Dispose
今までなかったのは
• yield return• awaitの発想の元になっているのがyield return
(awaitのコード生成結果はyield returnに近い)
• yield returnはtry-catch-finally句内に書けない
•生成されるコードが結構複雑• 一度すべての例外をcatch→await→再throw的なコードに展開される
• catch前のスタックトレースを保ったまま再throwする
拡張メソッドでコレクション初期化子
•コレクション初期化子の結果として呼ばれるAddメソッドが拡張メソッドでもOKに
var points = new List<Point>{
new Point(3, 4),};
var points = new List<Point>();points.Add(new Point(3, 4));
var points = new List<Point>{
{ 3, 4 },};
var points = new List<Point>();points.Add(3, 4);
static class PointExtensions{
public static void Add(this IList<Point> list, int x, int y)=> list.Add(new Point(x, y));
}
今までなかったのは
•コンパイル時間を気にしてのことらしい• 拡張メソッドの検索はノーコストではない
• といっても、普通の拡張メソッドとそんなにコスト変わらないはず
• VBは昔からできてた
まとめ
• Just-do-it• Roslyn化に時間をかけすぎたのでコンパクトに
• Roslynになったからこそ保守コストが下がって新機能入れやすく
• ローコスト・ローリターンな機能ばかりだけど、確実に便利に
•いくつかは、7.0の大きなテーマにつながる機能• データ:
• null: null条件演算子
インデックス初期化子拡張メソッドでコレクション初期化子
おまけ
•ここから先、時間が残れば
その他小さな変更
•これまでも、大々的に出てない小さな変更はあった• 特に、破壊的変更
• Visual C# 2008 Breaking Changes
• Visual C# 2010 Breaking Changes
• Visual C# Breaking Changes in Visual Studio 2012
• だいたいは以下の類• 新機能の余波でオーバーロード解決順序変わりました
• バグだったのでなおしました
• Roslynオープンソース化で、こういう小さな変更も見えやすくなった
C# 6.0の破壊的変更(1)
•コンストラクターの再帰循環参照• 昔:実行時にスタックオーバーフロー
• C# 6.0:コンパイルエラー
class C{
public C(int x) : this() { }public C() : this(0) { }
}
C# 6.0の破壊的変更(2)
• generic型インスタンスに対するlock• 昔:コンパイル通るものの、値型の場合lockの意味ない
• C# 6.0: class制約が必須に
public void DoSomething<T>(T data){
lock (data) { } // << Generates CS0185}public void DoSomethingElse(SOptions data){
lock (data) { } // << Works fine}public void DoSomething<T>(T data) where T : class{
lock (data) { } // Works fine}
C# 6.0の破壊的変更(3)
• generic型の静的readonlyフィールドに対する書き込み• 昔:他の特殊化に対して初期化できる
• C# 6.0:自分自身だけを初期化できる
public static class Foo<T>{
static Foo(){
if (typeof(T) == typeof(int)){
Foo<int>.compute = x => (int)x;}
}
public static readonly System.Func<double, T> compute;}
日本人だけが気づく破壊的変更
• Unicodeの変更の影響• 昔:カタカナ中点(中黒)OK
• C# 6.0: Unicode側のミスでした。Unicodeが修正したらC#にも影響出ました
int x・y = 10;Console.WriteLine(x・y);
微妙な新機能(1)
• enumの基底型• 昔: int, short, longなど、キーワードでないとダメ
• C# 6.0: Int32, Int16, Int64など、System名前空間の型でもOK
enum X : System.Int32{
A, B, C,}
微妙な新機能(2)
•オーバーロード解決ルールを改善• 昔: ラムダ式の型推論、1段は行けてたけど2段は無理だった
• C# 6.0: 2段以上もOKusing System;
class FuncOfFuncTypeInference{
static void Main(){
X(() => () => 10);}
private static int X(Func<Func<int>> f) { return f()(); }private static int X(Func<Func<int?>> f) { return f()() ?? 0; }
}
intか int?か古いコンパイラーは判定できない
追悼
•一瞬、C# 6.0に入る予定だったもの• 互換性壊れるので取りやめたり
• 引数なしの構造体コンストラクター
• C# 7.0で改めて取り組むことになったり• プライマリコンストラクター→ レコード型
• 変数宣言式→ パターンマッチング
追悼:構造体の引数なしコンストラクター•構造体にも引数なしのコンストラクターを定義できるようにするはずだった
• .NETのランタイムレベルでバグがあることが発覚して断念• Activator.CreateInstance<T>()が new T()をnullに置き換える
struct MyInt{
public readonly int Value;
public MyInt() { Value = -1; }public MyInt(int value) { Value = value; }
}
没
追悼:プライマリコンストラクター
•型定義の型名の直後にコンストラクターを1個書けた
• 7.0でレコード型に吸収予定
class Immutable(int x){
public int X { get; } = x;}
class Immutable(int x) { } 未
未
追悼:変数宣言式(declaration expressions)
•式の途中で変数宣言できた
• 7.0でパターンマッチングと合わせて作り直す予定
while ((var line = Console.ReadLine()) != null)if (int.TryParse(line, out var x))(var y = Math.Sin(x)) * y;
if (x is string s) { … }else if (x is int n) { … }
未
未