Orange Cube
「UnityのためのC#勉強会」に行ってきました。
弊社同僚 細田とでやったデモで使ったソースコードは GitHub 上に置いてあります。
https://github.com/ufcpp/UnityCsharp20150321
セッション資料の pptx もこの GitHub リポジトリ内に含まれています。
会社内の成果ベースでの発表は久しぶりでした。発表しようしようと思いつつもタイミングを逃し続け、気が付けば結構いろいろ仕上がってしまってて。ちょっと1回のセッションに一気につめこみすぎたかなぁとか思いつつの発表でした。
ピックアップRoslyn 3/14
今週のProposals
昔、「UnreachableAfter属性」で提案されていたもの。エラーチェックなどの際、必ず例外を投げるようなメソッドを作ることがあります。この場合、そのメソッドよりも後ろは絶対に実行されないわけですが、それをコンパイラーがわかるようにしたい(unreachableコード判定用)という要望があって、その解決策としての提案。
それが今回、属性じゃなくて、System.Neverっていう特別な型を作ってはどうかという提案が出ました。Scalaは、Nothing型で同様のことをしていて、そこからの着想だそうです。
Design Notes for Feb 11
2/11 の C# デザイン ミーティング議事録が今頃公開。
2/4 以降、議事録が issue ページ化されてなかったわけですが。
あんまりディスカッションしたいポイントがないからページ立てるモチベーションが低かったんですかね。議事録自体はあるみたいで、2/4のissueページの方で、「2/4以降のやつはないの?」とつつかれて「少しずつ出していく」との返事が返ってきてました。とりあえず、その翌週分、2/11のものがissueページ化。
内容的には、
- Destructible types:
- #161 の提案ベースだとダメそう
- 「決定論的なオブジェクト破棄」というゴール自体はなくしたくない
- IDisposable とか、既存コードとの親和性が高いよりよい方法が望まれる
- Tuples
- かなり乗り気なんだけど、もっと詰めないといけない点が何点か
という感じ。
タプルに関して、自分が興味ひかれたところだけ抜き出すと:
まず、タプル間の変換をどうしようという話。タプルの変換というと、以下のようなパターンがあり得るけども、どれがいいか(どれもダメっていう結論もあり得る)。少なくとも、reordering と renaming は絶対に両立できない。
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
(string name, int age) t = ("Johnny", 29); | |
/* Covariance */ | |
(object name, int age) t1 = t; | |
/* Truncation */ | |
(object name) t2 = t; | |
/* Reordering */ | |
(int age, string name) t3 = t; | |
/* Renaming */ | |
(string n, int a) t4 = t; |
もう1つは、タプルの分解時に、変数名の補完が効くためには、受け手が右辺にないとダメよねという話。代入だと、受けてが左側に来るので、変数名を打ってる時点では右辺の型がわからず、補完が効かないのがめんどくさい(LINQ で、SQL もどきのくせに1行目が from なのも同じ理由。from から初めて、最後が select なら変数の補完が効く)。右辺側に代入するような新しい構文が要るかも(以下はその一例)。
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
Tally(myValues) ~> (var sum, var count); // strawman right side alternative | |
Console.WriteLine($"Sum: {sum}, count: {count}"); |
最後、out 引数を暗黙的にタプル化したいという話。F# だとできるので。例えば以下のような感じ。
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
bool TryGet(out int value){ … } | |
/* current style */ | |
int value; | |
bool b = TryGet(out value); | |
/* New style */ | |
(int value, bool b) = TryGet(); |
「C#スクリプト」パッケージ
工数的な問題で長らくいったん消えていたC#スクリプト関連のAPIが、NuGetパッケージに帰ってきそうです。
Roslyn プロジェクトはNuGetパッケージの毎夜ビルドにmygetを使ってるみたいなんですが、そのmyget(以下のURL)ビルドでは、スクリプトAPIが利用できるようになったとのこと。
RC2 扱い。RC2 のリリースいつだろう。
ピックアップ Roslyn 3/7
構造体の引数なしコンストラクター
今週は大変残念なお知らせが…
1つ目。構造体の引数なしコンストラクターの導入、やっぱり見送り(今のCTPだと実装されているけども、次のCTPで元に戻す)になるみたい。
構造体Tに対して、new T() が default(T) と同じ(0 初期化)になるという前提の最適化がすでにあちこちにあって、.NETのフレームワーク/ランタイムのレベルでもこの前提になっちゃってるところがあるみたい。で、new T() でコンストラクターが呼ばれないバグが、ランタイム レベルであって、今、C#に構造体の引数なしコンストラクターを導入するのは無理くさい。
個人的には、C# 6.0 に入って(入ることになってて)ありがたかった機能の結構上位にあったので残念。というか、一応名前が「RC (リリース候補)」になった矢先にですか…
今週のProposals
なんか今週は、「実現性薄そうだけどとりあえず議論しようか」感が漂っていたり。新しい提案 issue ページが結構な数「C# 7 and VB 15」マイルストーンに登録されていました。
enum のメンバーを、E.A | E.B みたいに書くんじゃなくて、型名を省略して A | B 的に書かせてくれという話。
実は、C# 6.0 の時点ですでに、using static を使って近いことはできるようになっています。
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
using System; | |
using static X; | |
[Flags] | |
enum X | |
{ | |
A = 1, | |
B = 2, | |
C = 4, | |
} | |
class Program | |
{ | |
static void Main(string[] args) | |
{ | |
Console.WriteLine(A | B); // X.A | X. B | |
} | |
} |
その上で、もう1歩踏み込みたい?という話。型の推論は効いてほしいかどうか。例えば、以下のコードをコンパイルできるようにしたいか。
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
using System; | |
using static Color; | |
using static Mood; | |
enum Color { Red, Blue } | |
enum Mood { Cheerful, Blue } | |
class Program | |
{ | |
static void C(Color c) => Console.WriteLine($"color: {c}"); | |
static void M(Mood m) => Console.WriteLine($"mood: {m}"); | |
static void Main(string[] args) | |
{ | |
C(Red); | |
M(Cheerful); | |
C(Blue); // Error CS0229 Ambiguity between 'Color.Blue' and 'Mood.Blue' | |
M(Blue); // Error CS0229 | |
} | |
} |
2つ目。C# の仕様的に、ジェネリックな属性クラスを作らせてほしいという話。
流れ的には、「メリット薄くない?」「実装手間考えるとあんまりやりたくない」的空気。
3つ目。静的メソッドも「拡張」できるようにしてほしいという話。
やりたいけど具体的な文法案はまだ固まっていない状態なので、このページのコメントでいろんな文法案が出てきています。
とはいえ、「インスタンスメソッド以外、プロパティとかも含めた拡張」という、もっと広いプランが C# 7.0 の当初から出ているので、おそらくはそっちに統合されるのではないかと。
this 相手に拡張メソッドを呼びたいとき、this.Method() って書くの面倒だし、インスタンス メソッドとの一貫性に書けるから this の省略を認めてほしいという話。こいつはページのコメント、いまいち盛り上がりに欠けてたり。
「クラス自身に対して拡張メソッド呼ぶの要る?自身に対してだったら普通のインスタンス メソッドを足せよ」という話があって、これができないわけですが。interface を明示的実装して、その interface の拡張メソッドを呼びたいということはまれにあるんですよね。ただ、稀にしかないことのためにコンパイラーの複雑化(拡張メソッドを探すのもそれなりのコスト)を招きたいかといわれると。
クラスとのフィールドとかに var を使わせてという話。
これが今まで認められていない理由は2つあります:
- (ポリシーとして) メンバーの型はドキュメントとして扱われるもので、省略すべきではない
- (実装上の問題として) 循環依存で死ぬ
循環依存は例えば以下のような感じ。
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
class A | |
{ | |
public var N = B.M + 1; | |
} | |
class B | |
{ | |
public var M = A.N – 1; | |
} |
この問題を超えるメリットはないかなぁ…
あと、脱線で、const var を認めてほしい的な話も。const var って、constant (不変)で variable(可変)?variable が数学用語としての variable から来ているにしても、字面やばい。
ほぼ同機能を実装することになっているものの、C# に対してディスカッションが非常に少ない VB ですが。その VB で、AddressOf を省略させてという話。
Unicode Version Change in C# 6.0
こないだ、ufcpp.net 本体に「[雑記] C# ソースコードと Unicode」とか書いてたわけですけども。1か所、C# 6.0で破壊的変更になっちゃうもの(中黒「・」が識別子に使えなくなった)の話も書きました。
まあ、「Unicode の変更だ」といわれるとしょうがなく。Java 7 でも同様の問題出てて、しょうがないよね的な空気になっていたり。
なんですが、それはそれで、ドキュメント化は要るんじゃないかな、というか、今、Roslyn プロジェクト内で破壊的変更のドキュメント化作業もやってたよね。と、ふと思い立って、報告してみることに。そしたらその日のうちにドキュメントできてた。
最初にこの話題で盛り上がってたのはグラニの人ら(Build Insider の記事の下の方にその話題あり)なんですけども。今度会うとき言ってみとこう。
Roslyn 2/28
割と「週刊」化している、 https://github.com/dotnet/roslyn 内の今週の動き。
Milestone: C# 7
Milestone が「C# 7 and VB 15」なものに2項目追加。
プロパティ内限定(get と set の両方で使いたいけども、その他のメンバーからは触られたくない)スコープが欲しいという話。
C# 2.0 とか 3.0 の頃から欲しいとは言われていたものの、全然入らなかった機能。当時は自動プロパティ(3.0 で入った T X { get; set; } だけでプロパティが作れるやつ)だけでも、Anders (.NET のものすごく偉い人)がなかなか Go サインを出さなかったらしいですし、時代もだいぶ変わった感が。ここ数年、Anders はご意見番的な立ち位置で、Mads (C# チームのPM)の統括に代替わりできているみたいで。
仮想メソッドを派生クラスでオーバーライドする際に、戻り値に共変性を認めたいという話。例えば、class Derived : Base {} があるとき、ある規定クラスが virtual Base X() メソッドを持っていたとき、こいつの派生クラスは override Derived X() を定義したい。Java には昔からあって C# でもたまに欲しくなる話ではあります。
実装的には、.NET ランタイム(IL 仕様)の変更は不要そう。派生側に、override Base X() (基底クラスのやつのオーバーライド)と、new virtual Derived X() (別の仮装メソッドを追加)の両方を作って、override 側から new 側を呼び出すようなコードを生成すればいいはず(そういうやり方は、現在の C# では書けないものの、IL 仕様的にはできる)。
決定論的なモジュール ID
現状の Roslyn C#/VB コンパイラーは、コンパイルのたびに生成結果のモジュール(exe や dll)のバイナリが変わってしまうそうです。これは、ポータビリティの観点からあまりよろしくないので、決定論的な生成結果を得るための調査を始めた模様。
一番の原因は、モジュールの ID にタイムスタンプが入っているかららしいです。これを、モジュールのハッシュ値みたいなものに変えたいというのがまず第一。ただし、これが決定論的であるためには、タイムスタンプに実際のコンパイル時刻などは使えなくなります。ちゃんとした時刻が入っていることを期待したプログラムの動作保証がなくなります。
そして、ハッシュ値が決定論的であるために、コンパイラーが生成するモジュール自体が決定論的である必要があって、そのためにいくつか別の課題が上がっています。
- String heap in generated IL should be deterministic #360
- 文字列リテラルの出力順が決定論的でないとダメ
- EditAndContinueTests.AnonymousTypes changes emit order in 64-bit runner #223
- 匿名型の出力順が決定論的でないとダメ
- Produce PDBs with deterministic GUIDs #926
- PDB (Program Database: デバッグ用のシンボル情報とかが入ったファイル)の GUID も決定論的でないとダメ
- デバッグ実行時に PDB を参照するため、デバッグ ビルドではモジュールが PDB ファイルの GUID を参照している
- PDB format and caller file name arguments include full path names in compiler output #949
- ソースコードのパスがフルパスで入っているので、今のままだとビルドしたマシンごとに出力結果が変わる
- 相対パスか何かに変えないといけないし、そのために(相対パスの起点となる)ソースファイル/ディレクトリをコンパイラーに渡すオプションが必要
VS 2015 CTP 6、Microsoft.CodeAnalysis.Analyzers RC 1
昨日、Visual Studio 2015 の CTP 6 が出たみたいですが。
まだRC(リリース候補)じゃなくてCTP(テクニカル プレビュー)なんですねぇ。
一方で、Microsoft.CodeAnalysis.Analyzers (Roslyn の NuGet パッケージ配布)は RC1 になりました。Visual Studio 2015 CTP 6 の Roslyn (要するに、CTP 6 が使っている C# コンパイラー)はこの RC 1 のやつだそうです。
つまり、C# 6.0 は RC 1 になりました。
CTP 6 での C# 6.0 の更新点
バグフィックスとかは置いておいて、C# 6.0 の文法的な話でいうと、2点更新があります。
- Exception Filters の仕様変更
- IFormattable な String Interpolation
Exception Filters の仕様変更
例外フィルターで、if ではなく、when を使うようになりました(新しい文脈キーワード。catch の条件句の直後でだけキーワードになる)。
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
try | |
{ | |
} | |
catch (ArgumentException e) when (e.ParamName == "x") // 前まで if だった | |
{ | |
// パラメーター名が x の時だけはエラー無視 | |
} |
if だとね… 波かっこ {} の有無で意味が変わっちゃうとかいう恐ろしい仕様だったので。これは仕方がない。
IFormattable な String Interpolation
「C# 6.0 正式版までには入る」機能のうちの最後の1つ、FormatProvider 指定できるバージョンの文字列補間が実装されました。
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
private static readonly IEnumerable<string> Cultures = new[] { "en-us", "fr", "zh-hk", "ja-jp" }; | |
public static void Y() | |
{ | |
var x = 10; | |
IFormattable f = $"{x :c}, {x :n}"; | |
foreach (var c in Cultures) | |
{ | |
var s = WithCulture(f, c); | |
Console.WriteLine(s); | |
} | |
} | |
public static void SameAsY() | |
{ | |
var x = 10; | |
IFormattable f = System.Runtime.CompilerServices.FormattableStringFactory.Create("{0:c}, {1:n}", x, x); | |
foreach (var c in Cultures) | |
{ | |
var s = WithCulture(f, c); | |
Console.WriteLine(s); | |
} | |
} | |
private static string WithCulture(IFormattable f, string cultureName) | |
{ | |
return f.ToString(null, System.Globalization.CultureInfo.CreateSpecificCulture(cultureName)); | |
} |
これが、C# 6.0 の新機能の中で唯一、.NET Framework のバージョンアップする(もしくは、同シグネチャのクラスを自前で追加実装してやる)必要のある機能。上記コード例は、ターゲットを .NET Framework 4.6 にするか、FormattableStringFactory/FormattableString クラスを自作しないと動きません。
ちなみに、FormattableString は、当初、構造体にするかもという話も出ていましたが、結局クラスになったようです。あと、$”” の展開結果は、直接 new FormattableString(format, args) するんじゃなくて、ファクトリ メソッド FormattableStringFactory.Create(format, args) を介して作られるようになったみたいです。後からの拡張ができるようにしてあるのかも。
Roslyn 2/21
https://github.com/dotnet/roslyn 内の動きから、今週もいくつかネタを。
バージョン違いでコンパイル
そういや先日のネタの為に、複数バージョンのC#コンパイラーを同時に読んで、結果を色分けして表示するPowerShellスクリプトを書いたわけですが、その後も度々活躍していたり。Roslynのリポジトリ見てると結構、古いバージョンだとどうだったんだっけ?とか気になったり。
ということで、このスクリプトもGistにでも置いておきます。
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
cls | |
. C:\Windows\Microsoft.NET\Framework64\v2.0.50727\csc.exe .\a.cs | Write-Host -ForegroundColor DarkCyan | |
. C:\Windows\Microsoft.NET\Framework64\v3.5\csc.exe .\a.cs | Write-Host -ForegroundColor DarkMagenta | |
. C:\Windows\Microsoft.NET\Framework64\v4.0.30319\csc.exe .\a.cs | Write-Host -ForegroundColor DarkGreen | |
. 'C:\Program Files (x86)\MSBuild\12.0\Bin\csc.exe' .\a.cs | Write-Host -ForegroundColor DarkRed | |
. 'C:\Program Files (x86)\MSBuild\14.0\Bin\csc.exe' .\a.cs | Write-Host -ForegroundColor DarkBlue |
gitter運用始まりました
Miguel(Monoの偉い人)がgitter立ててた。
https://gitter.im/dotnet/roslyn
gitterについて紹介しておくと、GitHubと連携して、リポジトリや組織単位の部屋を立てれるチャットサービス。発言はGitHub互換のmarkdownで書けるし、#123とか書くとGitHubの該当issueへのリンクになるし、:+1:とかでの絵文字も使えるみたい。
Portable PDB
Portable PDB (program database)の仕様が公開されたみたい。
https://github.com/dotnet/roslyn/blob/portable-pdb/docs/specs/PortablePdb-Metadata.md
PDBは、デバッグとかビルド用にプロジェクト内のソースコードの状態が色々記録されてるファイル。例えば、スタックトレースとかブレイクポイント用に実行ファイルのどこがどのソースコードの何行目かとかの情報が入っていたり。あと、インクリメンタルビルドのためとかにも使っているみたいです。
これまでは、一応PDB読み込み用のライブラリは公開されてたりはするものの、詳しいドキュメントはいっさいなかったみたいです。
この手のデータは、このMSが使ってるPDB以外でも、大抵のコンパイラーがそれぞれ独自の形式を持ってたりします。ほんとバラバラ。C#でも、MS製コンパイラーはPDBを使っていますが、MonoのmcsコンパイラーはMDBっていう別形式を持っています。
今回、オープンになった形式(Portable PDB)は、.NETのメタデータの上位互換なフォーマットみたい(物理フォーマットは同じで、メタデータと同じ実行ファイルに混ぜることも可能。要は論理スキーマの追加)。
まあ、デバッグに必要な情報はかなりの部分メタデータと被るので。世の中には「C++でもリフレクションできるよ?PDB読めば」とかいう猛者もいらっしゃるくらいで。ちなみに、「PDBってVC++専用でしょ?」って聞くと、「もちろんコンパイラーごとに別実装する」との返事が返って来たり。
などという深い闇を産んできたわけですが、オープン化で状況改善しそうでほんとになにより。
default(StaticType)
静的クラスがらみのバグフィックスと、それの結果、一部旧コンパイラーから破壊的変更になったという話が1件ドキュメント化。
https://github.com/dotnet/roslyn/blob/master/docs/compilers/CSharp/Static%20Type%20Constraints.md
C#の仕様的に、静的クラス(static修飾を付けたクラス)は絶対にインスタンス化できないわけで、静的クラスの変数はそもそも作れなくしてあります(コンパイルエラー)。
静的クラスAがあったとき、
A x;
はエラーになります。これくらい素直な場合はいいんですが、めんどくさいケースがいくつかあります。まずは、varでの変数型推論。
var x = default(A)
そもそも、default(A)を認めるなよって話ではあるんですが、旧コンパイラーでは default(A) を書けちゃうそうです。Roslyn新コンパイラーでも、互換性のため、default(A)自体は書けるようにした(バグだけど直さない)とのこと。
そして、そのせいで、先ほどのvar x = default(A); が問題に。別途、「型推論の結果、varが静的クラスになるのは認めない」というルールを設定。
次にはまったのは、これをジェネリックなメソッドに渡す場合。void M<T>(T x)がある時の、
M(default(A));
これも別途、推論結果で型パラメーターが静的クラスになったときをエラーにしないといけない(最初、コンパイルエラーになってなくて、バグ報告されてた)。
そして、これとは別に、旧コンパイラーにはもう一個バグがあって、
((dynamic)3).M<StaticType>()
これがコンパイル通るそうです。dynamicが絡むとチェックが甘く。Roslynではこれは直したそうで、破壊的変更になってしまっているとか。まあ、実行時エラーになって他ものがコンパイルエラーになるだけですけども。
Roslyn内のコードをいくつかC#6化
試しにアナライザーのいくつかをC# 6を使って書き換えてみたそうです。
https://github.com/dotnet/roslyn/pull/740
目的は「少しやってみて、皆の印象を聞きたい」みたいな感じみたい。なのでごく一部だけ(Roslynのアナライザーを使えば機械的に全置換とかもできるけど、それはやってない)みたい。
Roslyn コンパイラー仕様
まだ pull-req 通ってないんですが、Roslyn リポジトリ上にこんなものが。
https://github.com/dotnet/roslyn/pull/536
コンパイラー仕様のドキュメント化、始めました。
“コンパイラー仕様”
コンパイラーを作っていると、標準化されている言語仕様では不十分な仕様ってものがどうしても出てきます。C# コンパイラーにもいくつかそういうものがあるんですが、「オープン化したんだからそういう隠れ仕様もドキュメント化しないとダメだよね」という感じで、その手始めに、コンパイラーチームの内部 OneNote で書かれていた仕様を .md 化してリポジトリに追加しようとしているみたい。pull-req が通った暁には、/docs/compilers フォルダー以下にこういう “コンパイラー仕様” が並びます。
具体的にどういうものをドキュメント化しようとしているかというと、
- コマンドライン スイッチとその意味
- 以前のバージョンのコンパイラーからの破壊的変更
- 標準仕様に反するコンパイラーの内部挙動
- 言語仕様に記述されていないコンパイラー機能
- COM 関連や、マイクロソフト特化の動作
- コンパイラーの挙動に影響を与える “well-known” な属性(Obsolete とか Conditional のこと)
- “ruleset” (FxCop 的なものの、どの警告を出してどの警告は抑止するみたいなルール)のファイル構文(syntax)と意味論(semantics)
- C#とVB間の相互運用のための機能
- 名前付きインデクサー(インデックス付きプロパティ)のC#からの利用
- 言語仕様で明確でなく、発散の余地のあるコンパイラー挙動
- 制限次項(例えば識別子長など)
- バージョンごとの変更履歴
など。
そんなのよく気づいたな
今回公開されたコンパイラー仕様のうちの1つ、「Definite Assignment」がネタとして面白かったんで紹介。要するに、「変数には確実に値を与えておけ」仕様に関する話。
C#では、未初期化のローカル変数の参照を認めていません。これ自体は言語仕様にも書かれていることです。問題は、「何をもって未初期化・初期化済みを判定するか」という部分。判定ロジックに関する詳細が言語仕様に足りていないので、「明確でなく、発散の余地のある挙動」になっているみたいです。
例えば、このページで紹介されている例は以下のようなもの。
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
class Program | |
{ | |
static void Main(string[] args) | |
{ | |
int x; | |
if (false && x == 3) // 恒偽定数式の後ろの、ショートサーキット演算に続く式は絶対に到達できない | |
{ | |
x = x + 1; | |
} | |
} | |
} |
C#コンパイラーは、到達不能な場所では「未初期化変数を使った」判定をしないみたいです。つまり、 if (false) { ここ } とか、if (true) {} else { ここ } とかでは、別に未初期化変数を使ってもコンパイルエラーにならない。
で、コンパイラー挙動がぶれているのは、さらに、ショートサーキット演算(&& とか || とか、左辺の時点で結果が確定したら右辺を実装しないもの)と組み合わさった場合。false && (ここ) とか true || (ここ) とかは絶対に通らないわけで、前述の if の例に習うなら、別に「未初期化変数を使った」判定をさぼってもいいはず。
ところが…
C# 3.0~5.0 まではエラーになるんですって、これ。C# 2.0の頃はエラーになってなかった。そして、Roslynでもエラーにならなくした。というか、明確なルールを加えた。とのこと。
言われるまで気づかなかった… まあ、こんなコード書かないし、誰も気づかないから言語仕様に漏れるんですが。
なんなんですかね、これ。パターン マッチングとか宣言式とかを実装する過程でこれに類するコードが生成されちゃって困ったとかがあったんですかねぇ。それとも、誰かC# 2.0の頃にこの類のコードを書いてる人がいて、C# 3.0の時に「破壊的変更」を不具合報告入れたとか?
Roslyn issues 2015/2/16
前回でひと段落したC# 7提案関連の話、一応リンクまとめておきますか。
- C# Design Notes / デザイン プロセスについて
- C# Design Notes / テーマ
- C# Design Notes 1/28版
- C# 7に向けて(1): 変数やフィールドの書き換え
- C# 7に向けて(2): 性能と信頼性
- C# 7に向けて(3): その他
- C# 7に向けて(4): C# 6に漏れた分
- C# 7に向けて(5): Record types
- C# 7に向けて(6): Pattern matching
- C# 7に向けて(7): まだ具体案の見えていないもの
- C# 7に向けて(8): Tuples
- C# Design Notes 2/4版
- C# 7に向けて(9): Method Contracts
そして今後は週1程度で個人的に興味持ったものをピックアップしていこうかなぁという感じなわけですが。
とりあえず、第1回。
Non-nullable references: a few difficulties and possible solutions
https://github.com/dotnet/roslyn/issues/227#issuecomment-73928184
非null許容な参照型に関する issue ページに、Mads (C# チームの偉い人)が結構まとまった内容のコメントを付けてた。
要点は
- フィールドだと他の場所で書き替えられる可能性があるから、非null保証しにくい
- 参照型 T を、非null参照型 T! の変数で受けてから使う、みたいな処理が必要
- パターンマッチングを使えば、x is T! y みたいな書き方はできる
- 規定値どうしよう
- 参照型の規定値は null なんで、既定でない値の代入が必須
- 特に配列とかout 引数の場合に心配
- ライブラリの互換性
- 非null参照型が入ったら、既存のライブラリをぜひともこれに対応させたいって思う人がかなり多いはずだけども、既存の参照型 T を非null参照型 T! に書き替えると破壊的変更になる
- コードを壊さないようにするためには T から T! への暗黙的変換が必要。でも、暗黙的にしてしまうと、せっかくの非null型の魅力半減
という感じ。
Avoid unnecessary boxing with String.Concat
https://github.com/dotnet/roslyn/pull/415
C# で、”int ” + 1 みたいな書き方(文字列 + その他の何か)をすると、string.Concat 呼び出しにコンパイルされます。この例で言うと、string.Concat(“int”, 1)。
で、string.Concat の引数は params object[] です。整数とか、値型に対して使うとボックス化が発生。
で、この無駄なボックス化を避けるために、値型だった場合、先に ToString メソッドを呼んでしまおうという提案がされています。もちろん挙動変更なので慎重に検討。
とりあえず、この挙動変更で問題が出るようなプログラムはなさそうには思います。また、C# の仕様書を読み合わせてみても、問題のある(仕様違反になる)変更ではないはず。
What language proposals would benefit from CLR changes?
https://github.com/dotnet/roslyn/issues/420
C# 6までは、とりあえずコンパイラーの修正だけで実現できる機能ばっかりでした。
C# 7の提案に入ってからは、まずは要望だけ洗いだしている状態で、コンパイラーだけでやるべきか、.NETランタイムに手を入れるべきかも含めて検討中。
で、今週、「.NETランタイムにも手を入れるならこういうこともできるよ。他に何か要望ある?」ページが建ちました。
Improve memory managment in async methods
非同期メソッドやイテレーター中でローカル変数を定義すると、コンパイル結果的にはクラス生成されて、ローカル変数だったものが実はフィールドに格上げされます。これは、awaitやyield returnを超えて同じ変数を使うためには必要な処置です。
問題は、awaitを超えて変数を使わない場合。awaitを超えないということは、別にフィールドにしなくても挙動は変わりません。ところが、現状のC#コンパイラーは、awaitを超えない場合でも変数をフィールドの格上げします。理由は、デバッグ時、非同期メソッド内に break point を仕掛けて止めた場合、awaitより前の変数の状態を見たいときがあるから。つまり、デバッグのためだけに、無駄な処理を行っています(ローカル変数のフィールド化は、メモリ管理上、結構な無駄)。
この挙動変える?break point で止めた時に await より前の変数覗きたい?デバッグ ビルドの時だけフィールドに格上げすべき?みたいな内容の issue ページ。
C# 7に向けて(9): Method Contracts
今 issue ページができてて大きめのものはこれが最後のはず。Method Contracts。メソッドに対する「契約」。
結構長かった… 結局、全9回に。今後は、「週刊ブログ」程度でよくなるはず。きっと。
契約プログラミング
そもそも「contracts」とは何か的な話は、昔書いたスライド貼って済まそう。
(補足: このスライドでは非null制約を例に挙げて説明していますが、非null制約は契約でやるよりも、「非null参照型」を作る方がいいと思います。C# 7向けの提案の中にはこの「非null参照型」も含まれています。)
一応、.NET 4の頃から、ライブラリとツールでのサポートはありました。
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
public int Insert(T item, int index) | |
{ | |
Contract.Requires(index >= 0 && index <= Count); | |
Contract.Ensures(Contract.Result<int>() >= 0 && Contract.Result<int>() < Count); | |
return InsertCore(item, index); | |
} |
また、研究的な位置づけのプロジェクトでは、C# に契約プログラミング機能を足したプログラミング言語(Spec#)もありました。
まあでも、C# が言語機能レベルでサポートしないと流行らないよね、きっと。という状態。
問題
.NET 4で契約がらみのクラス(Contract)が追加されてからも、実際のところ、このContractクラスを使ってくれたライブラリは少ないです。
どういう問題があるかというと例えば、
- Contract. を付けないと行けなくていちいちめんどくさい
- 契約の宣言場所がメソッド本体の中(本来、外に見せない内部実装にあたる場所)にある
- 契約は外から見えるべき
- ポスト プロセスが必要
- Contract.Requires/Ensures を書いている場所では実際には何も起こらず、コンパイル結果のILをさらに書き替えてチェックを行う仕組みになってる
- ドキュメントへの反映(「このメソッドはこういう契約を持っている」みたいなのを doc コメントに反映)もポスト プロセス
- ソースコードの静的解析が結構重い
- Visual Studio の上位エディション(確か、当初、Ultimate 限定?Premium でも行けたかも)が必要
- ポスト プロセス、静的解析ともに
提案: C# 言語機能での契約
文法的には Spec# の頃とあんまり変わらないものになりそう。
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
public int Insert(T item, int index) | |
requires index >= 0 && index <= Count | |
ensures return >= 0 && return < Count | |
{ | |
return InsertCore(item, index); | |
} |
requires (事前条件: 引数が満たすべき条件) や ensures (事後条件: 戻り値が見たすべき条件)の違反は fail-fast (エラー ログを残して即座にプログラム終了)にするようです。契約は、理想を言えばコンパイラーによって違反は全部コンパイル エラーにしてしまう方がいいものです(単に、技術的困難から実行時に残るだけ)。テストによって契約違反は取りきるべき(ちゃんと、呼び出し元がすべて責任をもって引数チェックしてからメソッドを呼ぶ)もの。なので、規定動作は例外すら出さず、強制終了。
一応、requires/ensures の後ろに else throw を付けて、この挙動を変える(例外を投げるだけにする)機能は検討されているようです。
あと、呼び出し元側で責任をもってチェックする必要があるということは、requires/ensures 句内では、メソッドよりもアクセス制限の強いメンバーの参照は禁止されるべきです。internal なメソッドの契約で private フィールドを参照したりはできません。
C# Design Notes 2/4版
新しいC#デザイン ミーティング議事録。
今回の議題は3つ。大きいのは3つ目の Classes with values です。タプルと関連してのレコード再検討。
あっ、あと、おまけで。Roslyn の RC1 を NuGet 公開したそうです。
Internal implementation only
前にC# 7シリーズその(6)で触れた、 ImternalImplementationOnly 属性に関して、もう少し詳細な検討。
問題点に関しても前の issue ページより少し詳細に。そして、今でもできること、コンパイラーに機能追加すればできること、.NET ランタイムにも手を入れるならみたいな話と、それでどのくらい「穴」が改善するかという話がかかれています。
問題はインターフェイスに後からメソッドを足しにくい(一般にもやっては行けないこととされているし、BCL チームのポリシー的には完全にNG)ことなので、こういう属性を足す以外にも、mixins/traits って呼ばれるような言語機能の追加でも問題は回避できるかもしれません。
実際に C# 6 や 7 で、どこまでやるかや、開発ポリシーをどうしていくかなども含めて、BCL チームとの対話も必要そう。
Tuples, resords, deconstruction
その(8)で書いた通り、C#7 に向けて、タプル型も提案されたわけですが。タプル型が入ることによって、レコード型やパターン マッチングについても再検討が必要という話。
特に、タプルとレコードについて、これらの機能は一元化する方がいいかもしれないし、少なくとも統合的にデザインしていく必要があるでしょう。タプルは「レコードの特別な場合」であるべきか、もしかしたらレコードは単なる「型名のあるタプル」であるべきかなどということも考えられます。
レコードの導入動機は、値セマンティクスや immutability を持つクラスを楽に作れるようにするものなので、この辺りを次節で検討します。
Classes with values
今回出たアイディアは以下のようなもの。
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
// Point の実際の値を持つ構造体。 | |
// 現状の C# だと自分で実際にこういう型定義を書く必要があるけども、タプルにできるのでは。 | |
struct PointValue | |
{ | |
public int X; | |
public int Y; | |
} | |
// 値セマンティクスを持つ immutable クラス | |
// レコード型の実装はこうあるべきだろうという内容。 | |
// ここでいう値セマンティクス(value semantics)は、全メンバー比較で Equals、全メンバーのハッシュ値から GetHashCode 計算できるもののこと。 | |
class Point | |
{ | |
// 唯一の構造体メンバーに readonly を付けているので immutable。 | |
// タプルが入るのであれば、public readonly (int X, int Y) Value; でいいはず。 | |
// Point p に対して (var x, var y) = p; で、(var x, var y) = p.Value; の意味に解釈するのもよさそう。 | |
public readonly PointValue Value; | |
// 外から mutable な型で値をもらう。いわゆる「ビルダー パターン」。 | |
// PointValue をタプルにするのなら、スプラッティングが効いて、new Point(x, y) とも書けるはず。 | |
// 簡易構文として、new Point { X = 1, Y = 2 } で、new Point(new PointValue { X = 1, Y = 2 }) の意味に解釈するのもよさそう。 | |
public Point(PointValue value) { Value = value; } | |
// Value に処理を丸投げ | |
public int X => Value.X; | |
public int Y => Value.Y; | |
public override int GetHashCode() => Value.GetHashCode(); | |
public override bool Equals(object obj) => (obj as Point)?.Value.Equals(Value) ?? false; | |
// 値書き換え(immutable なので別のインスタンスを作る)用の "wither" (GetX, SetX みたいなのを getter/setter というように、WithX をこう言う) | |
// p.Value { X = 1 } みたいに、new するとき以外でもオブジェクト初期化子を使えると、wither 書くのが楽になるはず。 | |
// Point WithX(int x) => new Point(Value { X = x }); | |
// あと、var q = p { X = 1 }; みたいなので new Point(Value { X = 1 }) の意味に解釈するのもよさそう。 | |
// これなら、自前の wither 定義要らない。 | |
public Point WithX(int x) { var v = Value; v.X = x; return new Point(v); } | |
public Point WithY(int y) { var v = Value; v.Y = y; return new Point(v); } | |
} |
要点は
- 値セマンティクスを持つクラスは、文字通り Value 1つだけを持つ
- Value 用の構造体を作って、それを受け取るコンストラクターを作ることで、いわゆる「ビルダー パターン」になる
- Value はタプル型を使って定義するとよさそう
- オブジェクト初期化子の拡張で、「wither」も簡単に書けそう
これなら確かにタプルとレコードの親和性がよく、使う側のわかりやすさ的にもコンパイラーの実装負担的にも都合がよさそう。
まあ、これはこれで、元々のレコード型の提案にあった、部分的なメンバーの置き替え(以下のようなもの)の実装が難しくなりそう。
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
class Person(int Id, string Name) | |
{ | |
public string Name { get; set; } // Name だけ mutable | |
} |
レコードもタプルも、まだ提案の初期段階でこれから変わっていくでしょうし今後どうなるかはわかりませんが、これはこれで面白い検討内容でした。