C# 7に向けて(5): Record types
現時点ではC# 7の目玉になるであろう機能として、pattern matching と record types があります。
C# 6の機能提案の段階で出てきてはいたものの、期間的な問題で「7送り」になりました。当時と比べて、record types の書き方が少し変わっています。
長くなりそうなので、record types と pattern matching の説明は2回に分けようかと思います。今日は record types の話。
最小限の例
値の書き替えってどのくらいの頻度でやりますか?
僕の場合だと、自分が書くクラスの半分以上は書き換え不能(先日書いた内容で言うところの immutable)に作っています(参考:C# 7に向けて(1): 変数やフィールドの書き換え)。「めんどくさいから後で」とか言って mutable に作ったまま放置してるものとかも含めれば、7割くらいは immutable にしたいクラスではないかと。
まあ、めんどくさい… 例えば以下の通り。
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// C# 5 以前で、きっちり作るなら | |
// プロパティ1つごとに5か所ずつ同じものを書かないと行けない | |
// しかも、プロパティ/フィールド、コンストラクターで結構場所が離れる | |
public class Point | |
{ | |
public int X { get { return _x; } } | |
private readonly int _x; | |
public int Y { get { return _y; } } | |
private readonly int _y; | |
public Point(int x, int y) | |
{ | |
_x = x; | |
_y = y; | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// C# 5 以前で、多少さぼる | |
// private set な自動実装プロパティを使用 | |
// 自動実装で作られるフィールドは readonly ではなく、private なだけでクラス内からは書き換え可能 | |
public class Point | |
{ | |
public int X { get; private set; } | |
public int Y { get; private set; } | |
public Point(int x, int y) | |
{ | |
X = x; | |
Y = y; | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// C# 5 以前で、さぼる | |
// new Point(1, 2) って書くのをあきらめて、new Point { X = 1, Y = 2 } と書く | |
// set も public になってしまっていて大変残念 | |
public class Point | |
{ | |
public int X { get; set; } | |
public int Y { get; set; } | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// C# 7 の秘めた可能性 | |
// これだけで readonly なフィールドを持った、get-only なプロパティが生成される | |
public class Point(int X = 0, int Y = 0); |
上から、
- Point1: readonly のおかげできっちり immutable を保証しているもの
- Point2: 実装している自分が気を付けている限りには immutable になっているもの
- Point3: 完全にさぼり。immutable じゃない。でも、使ってる側では別に値の書き換えしないことの方が多い
という感じ。自分は Point2 くらいの書き方をすることが一番多いです。面倒でなければ Point1 の書き方をしたいものの。あと、C# 6で導入される get-only 自動プロパティを使えば、Point2 程度の書き方で、Point1 程度のきっちりした immtable 保証ができます。
そして、Point4 が、C# 7 での導入が提案されている新機能、record types です。たったこれだけで、immutable なクラスが作られる。
マジでほしい。今すぐ欲しい。C# 6も正式版はまだだけども… きっと、C# 7 のプレビュー版が出た瞬間に入れて使うと思う(仕様変更でプレビュー版更新のたびに大変だと思うけども、たぶんやっちゃう)。
カスタマイズ
詳しくは提案ページのコードでも見てもらうとして。
一番シンプルな書き方をした時に、一番望む形になっているのは本当にありがたいです。ですが、常に immutable に作りたいわけじゃないですし、カスタマイズはしたいところ。record types では、ちゃんと、コードを足してカスタマイズしていくことができます。
- class Point(int x: X, int y: Y)
- パラメータ名と、生成されるプロパティ名を変えたい場合
- class Point(int X, int Y) { public X { get; set; } }
- X を mutable にしたい場合、明示的にプロパティを書く
- class Point(int X, int Y) { public Length => Sqrt(X * X + Y * Y); }
- パラメーターと無関係な追加のメンバーも足せる
- ちゃんと、自動生成されたプロパティを参照可能
- class Point(int X, int Y) { public Point(int x) : this(x, x) { } }
- コンストラクターを足せる。
- ただし、必ず primary コンストラクター(クラス定義の最初の行の引数リストのこと)を呼ばないとダメ
- class Point(int X, int Y) : Shape;
- 継承も利用可能
- struct Point(int X, int Y);
- 構造体も利用可能
ここではプロパティの自動生成の話ばかり話していいますが、これに加えて、いくつかのメンバーも自動生成されます。
- ToString … “Point(X: 1, Y: 2)” みたいなのを出力
- Equals … メンバーごとの比較で等値判定
- GetHashCode … メンバーごとのハッシュ値計算結果から、自身のハッシュ値を計算
- is 演算子 … pattern matching 用(次回説明予定)
ちなみに、もう1つ、便利記法として、record types の場合はインスタンスを作るときに new を省略できるようにしようっていう話もあります。new Point(1, 2) の代わりに、Point(1, 2) だけでコンストラクター呼び出し。このため、record types は、デリゲートとかメソッドと同列の「invokable」という扱いにしようとのこと。
争点
C# チーム自身が issue として挙げている点や、コメントでついている意見などとして以下のようなものがあります。
- 属性の付け方どうしよう?
- primary コンストラクターの引数1個から、フィールド、プロパティが生成されて、さらにプロパティは get メソッドが作られる
- 合わせると、引数、フィールド、プロパティ、メソッドの4つあるわけで、それぞれに属性付けたい?指定なしだとどれについてほしい?
- record キーワードとか付けないの?
- CodePlex 時代にはいったん class キーワードの前に record 修飾を付ける文法で提案されてたはずだけども
- 既存の構文とずいぶん違うし、結構な量のコードがコンパイラーによって生成されるし、わけなくていい?
- プロパティの自動生成とかはしてほしくなくて、primary コンストラクターだけ使いたい場合もありそうでは?
- (C# 3で導入された)匿名型が浮かない?
- 匿名型は new { X = 1, Y = 2 } みたいな書き方で immutable なクラスが生成される
- 今回導入される構文は new Pont(1, 2)
- Equals, GetHashCode, ToString
- 現状のコード生成結果は(仮)
- 仕様化が必要
- IEquatable は実装した方がいい?
- == はないの?(参照型に対して == はあんまり推奨されないけども… struct のときとかは)
[…] 先日の続き、C# 7の目玉になるであろう機能、pattern matching と record types […]
C# 7に向けて(6): Pattern matching | ++C++; // 未確認飛行 C ブログ
2015年2月7日 at 02:56
[…] C# 7に向けて(5): Record types […]
Roslyn issues 2015/2/16 | ++C++; // 未確認飛行 C ブログ
2015年2月16日 at 02:20
[…] レコード型についてのディスカッションをしたそうです。レコード型自体については2/5のブログを参照。今回は、要件整理のみ。 […]
ピックアップRoslyn 3/22 | ++C++; // 未確認飛行 C ブログ
2015年3月22日 at 19:36