Quantcast
Channel: ++C++; // 未確認飛行 C ブログ
Viewing all 482 articles
Browse latest View live

コンパイラーの資源枯渇系エラーとの付き合い方

$
0
0

ちょっとしたきっかけがあって、C# 7のタプル、何要素まで書けるのかというのにチャレンジすることになりまして。 結果だけ書くと、数万要素のタプルを書くとVisual Studioがクラッシュしました。

これは、別にタプルに限った話ではなくて、巨大なソースコードを食わせてコンパイラーを限界まで酷使したら落ちるのは当然なことでして。 起こしているエラーはスタック枯渇に類するもの(Insufficient Stack)のようです。 コンピューターの資源は有限なので、OutOfMemoryやStaskOverflowなどの資源枯渇系のエラーはどうやっても起こりえます。 数万引数のメソッドを書いたり、数万項の足し算をしたり、数万caseswitchを書いたりしても、同様に落とせると思います。

なので、本当に気にするべきなのは以下のような点かなぁと思われます。

  • 仕様上の上限: 要素数などに仕様上の上限を設けるべきかどうか
  • 資源枯渇への対処: 資源枯渇を事前に察して適切にエラー メッセージを出すすべきか、コンパイラー自体をクラッシュさせるべきか
  • クラッシュ時の対処: コンパイラー自体がクラッシュした際に、IDE側は同エラー処理すべきか

まあ、割かし一般的な「異常系に対する対処」に関する話です。

仕様上の上限

仕様で決まっている例

この手の数の上限、仕様として定めているものもあります。例を挙げると以下のようなものがあります。

  • C++ では、constexprという機能で、再帰呼び出しに上限があります
    • 標準仕様上は「512回以上であることを推奨」という感じで推奨設定が決まっています
    • 実際の上限はコンパイラー依存、かつ、コンパイル オプションで変更できます
  • Java では、メソッドの引数の上限が256個までと決まっています
    • バイトコード上の制約なので、どうやってもこの数を超えることはできません

仕様を決めることの善し悪し

仕様的に上限を決めることには善し悪しあります。

仕様化するためには、結構余裕をもって上限を設定せざるを得ません。コンピューターの性能が上がって、ちょっと頑張れるようになったとしても、仕様で決まっている数を超えれなくて不便だったりします。

その一方で、コンパイラーの限界ぎりぎりまで頑張る場合、あるマシンではコンパイルできたのに、他のマシンではできないということがあり得ます。 今回のタプルに関しても、実際、手元のマシンだと1万要素くらいはコンパイルできましたが、TryRoslynなどのオンライン サービスを使うと1000ちょっとでもうコンパイル エラーを起こす用です(要するに、使っているVMインスタンスがしょぼい)。

個人的に、1000っていうラインは結構微妙なところです。 手書きで1000個のタプル要素や引数を書くことはまずないでしょう。 でも、「コード生成とかしててうっかり」だと、数百~1000は「ありえる」範疇。

で、開発機ではコンパイルできる。なのに、CIとかを掛けていて、毎朝ナイトリー ビルドには失敗している、みたいなことがあり得るわけです。

マシン環境に左右されない意味では、明確に上限が決まっている(少なくともC++のconstexprみたいに「推奨」が決まっている)方がありがたいのはありがたいです。

仕様を決められるか

とはいえ、引数の数や、再帰の段数みたいに具体的な数値を指定しやすいものばかりではありません。

タプルの場合、「要素数」で区切ればいいじゃないかと思うかもしれませんが、実際にエラーの原因になっているのはスタックの深さであって、要素数は直接の原因ではありません。 例えばの話、数千項の足し算の中に、数千要素のタプルと、数千引数のメソッド呼び出しが混在していたりすると、項数/要素数/引数の数の合計によって限界が決まるだろうことは大まかに想像がつくかと思います。 なので、単純にタプル要素数だけに制限を書けるのも変な話でしょう。

もっと難しい例でいうと、型推論をかなり頑張ってる言語では、「型推論に時間が掛かりすぎているのであきらめてエラーにします」みたいなエラーを出すものもあったりします。 時間で言われても、どういう場合なら推論できて、どういう場合ならできないのかがまるで分らず…

資源枯渇時の対処

まあ、割かし仕様としては決めかねるものなので、コンパイラーの限界まで頑張ることにしましょう。 そうなると、資源枯渇した瞬間がコンパイルの限界になります。

ところが、OutOfMemoryにしろStackOverflowにしろ、資源枯渇からの復帰はかなり難しいです。 そりゃまあ、復帰にもメモリを使ったりするわけで、OutOfMemory下でできることとか限られています。

なので、コンパイラーをクラッシュさせないためには、かなり防衛的に、余裕を持った上限設定が必要だったりします。 「再帰呼び出しが1000段を超えたのでエラーにしよう」的な。 結果的に、仕様的に上限を決める場合同様、過剰な制限が掛かって利便性に難ありです。

そこまでしても、どうせ返せるコンパイル エラーは「資源不足なのでこれ以上のコンパイル作業はできません」程度の、何の解決にもならないメッセージのみ。 だったら、OutOfMemoryやStackOverflowをそのまま未処理例外にして、コンパイラー自体がクラッシュした方が費用対効果は高いのではないかということになります。

ただ、その場合、コンパイラーを呼び出している側でクラッシュに対するちゃんとした対処が必要になります。

クラッシュ時の対処

で、まあ、今回Visual Studioがクラッシュしているのがちょうどそんな感じの状況なわけです。 無理なソースコードを食わせたからコンパイラーがクラッシュした。 Visual Studio側がコンパイラーのクラッシュに正しく対応していないみたいで、Visual Studioごとクラッシュする。

これ、Visual Studio Codeだと大丈夫みたいです。 Codeの場合は、急にインテリセンスが働かなくなるだけ。 エディターとしてはフリーズすることなく稼働し続ける。 Codeでは、Language Server Protocolってものを介して、エディターの外のプロセスと通信してインテリセンスを動かしているので、言語サーバー側がクラッシュしても、エディターは無事です。

利用者からすると、エディターごと落ちるとかほんとつらいんで、今のVisual Studioの挙動は結構いやなんですけども。 Visual Studioの、C#コンパイラーの未処理例外に対する耐性の低さは結構きついものがあります。 今でこそ、Visual Studio 2017もRC3 (リリース直前)まで来たのでクラッシュも減っていますけど、 もっと開発初期なら簡単にコンパイラーがクラッシュしますし。 いまだに、例えば「null is」って打つだけでコンパイラーをクラッシュさせれるバグが残っていたりするんですけども、 それでも、Visual Studioごとクラッシュします。

まとめ

数万要素のタプルを書くとVisual Studioが落ちます。

そんなソースコードを食わせたらコンパイラーが落ちるのは割と想定の範囲内なことかと思われます。 この手の問題に対して仕様的に上限を定めるのは、過剰な制限になって利便性を損なったり、 適切な上限の掛け方が見つからなかったり、結構な難しさがあります。

めったに起きない状況に対して過剰なコストをかけてもしょうがないでしょう。 限界に挑戦するようなソースコードを与えられたとき、クラッシュするのが現実解であることは多々あります。

問題は、むしろ、Visual Studio側が、C#コンパイラーのクラッシュに対して脆弱すぎる点かなぁと思います。 同じようなひどいソースコードを与えても、Visual Studio Codeだったらクラッシュしなかったりもします(インテリセンスだけが効かなくなる)。 Visual Studioにも、C#コンパイラーのクラッシュにエディターごと巻き込まれないような対処が必要でしょう。


C# 6, C# 7

$
0
0

最近、C#がらみの原稿仕事を依頼されたとき、よくある校正が「C# 6になってますけども、6.0ですよね?」。 回答は「いえ、C# 6が正式です」。

C#のバージョン番号、6以降は .0 を付けないのが正式っぽいんですよね。実は。

  • C# 1.0
  • C# 1.1 (#lineディレクティブと/** doc comment */が追加されただけ。1.1なのか1.2なのかも割かしよくわからず)
  • C# 2.0
  • C# 3.0
  • C# 4.0
  • C# 5.0
  • C# 6
  • C# 7

という感じ。

実質的に小数点以下使ってない(1.1とか存在感ない)し、要らないですよね。という感じ。

これに気づいたの、結構後になってからなんで、うちのC#によるプログラミング入門にも「C# 6.0」って表記が結構残ってたんですが、今しがた整理。ブログ以外からは6.0が消えたはず。

実はよくわからない

ただ、C# 7の次がC# 7.1とかになる可能性は0ではないです。「TypeScriptとか0.1刻みの細かいリリースしてるよね」みたいな話題もあるし、C#も1年1回リリースくらいのペースに速めようなんて話が出てますし。パターン マッチングの構文を、7で追加するものと、その先に回すものを分けたとかいうのもありますし。

.NET Frameworkも、「.NET 4」ってブランド名にしたら、その次からが「.NET 4.5」、「.NET 4.5.1」、「.NET 4.6」、…とかだったなんて話もあります。

あと、C#チームは現体制(GitHub上で公開MadsがPM)になってから、この手のアナウンスが手抜きなんですよね。気が付いたら「あれ?そうだったの?」みたいなことが割とちらほら。

単に、「6.0と6に何の差もないだろう」的な発想なのかも。例えば以下のページなんて、C# 1, C# 2, C# 3, ...表記。

ってことで、自分も全部C# 1, C# 2, C# 3, ...表記に変えたい気持ちありつつ。まあ、一応過去への遡及はしないことにします… 6以降だけ、.0を取る方針で。

おまけ: VB

Visual Basicのバージョンもなかなかよくわからないみたいですね。

C#は、言語のバージョンとIDEのバージョンを分けて表記してますけど、「Visual Basic」っていうIDE製品があった頃の名残で、言語とIDEの区別をしないみたい。その結果何が起こるかというと…

まあ、以下のページを見てください。

書かれてるのは以下の通り。

  • Visual Basic / Visual Studio .NET 2002 (内部バージョン7)
  • Visual Basic / Visual Studio .NET 2003 (7.1)
  • Visual Basic / Visual Studio .NET 2005 (8)
  • Visual Basic / Visual Studio .NET 2008 (9)
  • Visual Basic / Visual Studio .NET 2010 (10)
  • Visual Basic / Visual Studio .NET 2012 (11)
  • Visual Basic / Visual Studio .NET 2013 (12)
    • このとき、コンパイラーは全く更新してません
  • Visual Basic / Visual Studio .NET 2015 (14)
    • 13を嫌って、内部バージョン1つ飛ばして14

で、VBのバージョンとしては、Visual Studioの内部バージョンで(2002みたいな年号表記じゃなくて、7みたいなの)表します。 問題は、2013。VBのコンパイラーは一切変更してないのに、こいつのことをVB 12って言うみたいです。 しかもその後、13っていう忌み数を避けて内部バージョンを14にしたもんだから、VBもVB 14に。

dot.net にC#オンライン エディター

$
0
0

.NET Core、リリースされましたね。

まあ、その辺りの話は他の人に任せるとして。

自分が気になったのはこちら。

Scott Hanselmanのブログにこんな画像が

Scott Hanselmanの.NET Coreリリースに関するブログ記事に気になる画像がありまして。画像にリンクが貼ってあって、リンク先は

こちら。

ちょっと前に、「よくこのドメイン取れたな」、「マイクロソフトってURLにこだわってくれなくていつもダサいのに、これはほんとにうれしい」と話題になってたやつですね。 結局はhttps://www.microsoft.com/netに転送されたりはするんですが、まあ、http://dot.netがある、このURLでリンク貼れるってのが大事です。

で、このページをちょっと下にスクロールすると、こんなものが。

Starting coding

ウェブページ内でC#コード書いて試せる! いつからありましたっけ?

まあ、中身的には「Monaco」っぽいです。 Monacoを使って、チュートリアル コードをサイト内に埋め込んだり、「Run」ボタンを押して実行結果を出力したり。

これも、まあ、ずっとほしいほしい言い続けてたやつなわけですが。 Goなんかは公式サイト開いた瞬間「Try Go」なわけで。 同じものがC#にもほしいって言ってたら、ちゃんとできてた。 しいて言うなら、こんなちょっとスクロールしないと見えない位置に置くのはやめてほしいなぁという感じはあります。 このページはたびたび見たことあるけど、大体「DOWNLOADS」か「DOCUMENTATION」リンクに直行でスクロールしませんし。

しかも、チュートリアル ページのサンプル コードも、実行できるものは1個1個、このMonacoベースのオンライン エディターを立ち上げて実行できるみたいです。

チュートリアル ページのサンプル コード

これは大変よさげ。

ピックアップRoslyn 2/3: csharplang リポジトリ

$
0
0

ようやく、言語設計に関するリポジトリを、Roslynから分離する流れに。

ここまではOK。

Roslynリポジトリはコンパイラー実装に関するリポジトリなわけで、issueはバグ報告とかで埋まります。 今現在、3000件以上のissueがあって、そのうちかなりの割合がバグ報告なので、まあ、そりゃそんな場所で言語設計に関する話はできないですよね、というのは仕方がない話。 ユーザーからもリポジトリを分けてくれっていう要望は出ていますし、何よりC#チームがかなりGitHub issueの物量に参っているようです。 ということで、新たにcsharplangリポジトリが出来てみんな幸せに…?!

メーリングリスト…

問題はここから。

曰く、

Design Process

C# is designed by the C# Language Design Team (LDT).

  1. To submit, support, and discuss ideas please subscribe to the language design mailing list.

えっ、メーリングリスト!?

ということで、もちろん炎上中。

反応はおおむね、

  • リポジトリを分けるのには賛成
  • メーリングリストはやめてくれ
  • GitHub issueがつらい気持ちもわからなくはないけど、メーリングリスト、てめぇはダメだ

な感じです。

あまりの炎上っぷりにさすがにC#チームの中の人も再三の説明は繰り返していて、

  • まず最初に、分離先にメーリングリストを使うという判断に関して、チーム内だけで決めたことは謝りたい
  • C#チームの運用上GitHub issueはつらいものがある
    • 「何百もコメントが付くし、後から編集できるから後から事実を追うのもつらいし」などなど
  • もちろんその代案がメーリングリストというのは、チーム内でも揉めた
  • けど、どの案にも善し悪しあって、とりあえず当面、メーリングリストでの運用を容赦してはもらえないだろうか

という感じのようです。

私的な意見として

まあ、僕個人の意見としては、やっぱり「いろいろと気持ちはわかる。でも、メーリングリスト、てめぇはダメだ」ですねぇ…

もう、メールって言うメディアがダメ。 Windows付属のメールアプリとか、もうとてもじゃないけど使うのつらいじゃないですか。 メール ベースで何かやられたら、もうそんな場所に参加するだけで苦痛。

これはもう、「別のもっと良いアプリに移行すべき」とかそういうレベルの話ではなくて。 もう付属アプリが良くなる当てがない(そこに投資してもMicrosoftもユーザーも大して幸せにならない)という現実があるわけで。 Eメール自体に将来性がない。

そして、そういう将来性のないメディアでディスカッションしているプログラミング言語を使いたいかという問題になるんですが、 僕としては嫌。 使いたくない。

去年の夏ごろにこのブログでも取り上げましたけど、その頃、 Mads (C#言語設計のPM)が「What’s New in C# 7.0」っていうブログ記事のコメント欄で、

C# needs to be among the greatest programming languages today, or it won’t be among them tomorrow.

C#は今この時「最高のプログラミング言語」の1つでなければならない。 さもなくば、明日にはそうではなくなってしまう。

って言ってるわけですよ。 それが、もう、メールなんてメディア使ってる時点でだいぶ「今この時最高」からはずれる気分。

そして、それを伝えたいけど…

今のGitHub issue運用がきついって言うのはかなりわかるし、かといってその代案としても決め手に欠けているってのもわかるので、あんまり非難はしたくないんですけども。 なので、今の苦労をねぎらいつつも、「メールだけは勘弁してくれ、C#っていう言語のブランディングに関わる」って話をしたいんですが…

これを、まあ、つたない英語力で英訳すると、

Mailing lists are f***'in

の1行に縮まりかねないわけでして… バグ報告とかの類と比べて難易度高い…

おまけ

そんな感じのことをぼやいてたら、英ペの勇さんがRTしてたので、きっと翻訳してくれるんだと思います。

英ペの勇さん

ピックアップ Roslyn 2/10

$
0
0

Visual Studio 2017 のリリース日、決まったみたいですね。

リリース記念勉強会を開く(リリースされてなかったら「リリース直前勉強会」にする)つもりで3/11(土)に会場を押さえてあるんですが、割かしいいタイミングだったみたいで。 そろそろ1か月前ですし、告知・募集ページを近々作る予定です。

で、Visual Studioがリリースできる段階に来てるということは、C#チーム的にはもう C# 7 向け作業を終えて、その先の作業に入っているような状態。

proposals

先日の、ディスカッションの場がメーリングリストになりそうで炎上って話は、 とりあえず「事前にコミュニティに相談しなかったのは悪かったと思っているので改めて投票の場を作った」って感じで進んでいます。 確か、投票の締め切りが今日。

それはそれとして、csharplangリポジトリ内に、すでに取り組むことが決まっている範囲で、 提案ドキュメントがアップロードされ始めました。

とりあえずRoslyn側から持ってきただけという感じで新しい話は特にないんですが。 早々にここに並んだってことで、今ここに並んでいるものは割と実現性の高いものなんじゃないかと思います。

deterministic ビルドオプション

そういえば、こんな話が。

My personal favorite feature of the new dotnet SDK / MSBuild format: deterministic builds on by default.

(C# チームの中の人の発言)

これの詳細:

去年の春くらいから、C# コンパイラーには /deterministic っていうビルドオプションが実装されています。 これは要するに、同じ入力を与えたら必ず同じ出力になるというもの。決定論的ビルド。 (これまでだと、そこら中にタイムスタンプが入ったり、partial class内のメンバーの順序決定に仕様がなかったりで、 同じ内容のコードをビルドしても毎度exeやdllのバイナリに変化が出ていました。) 決定論的になったことで、ビルド結果のキャッシュが聞きやすくなって、テスト実行とかが大幅に高速になったとか。

で、最近の dotnet SDK を使ってビルドすると、デフォルト動作が /deterministic モードになるみたいです。 ちょっと触ってみている感じだと、たぶん、Visual Studio 2017だと、 .NET Standard 向けライブラリ、もしくは、 .NET Core 向けアプリでだけこのモードになるんじゃないかと思われます。

どおりで… Visual Studio 2017 RCに先月のアップデートをかけて以来、ILSpyで.NET Core/.NET Standardなアセンブリの中身が見れなくなってると思ったら…

ILSpy上で、.NET Core/.NET Standardなアセンブリを開いたとき

ILSpyが/deterministicでビルドされたpdbファイルに対応していないみたいです。 ちなみに、読めないのはpdbファイルだけ。 pbdファイルを消してからexe/dllを開きなおせば読めました。

ピックアップRoslyn 2/13

$
0
0

2/10のブログの補足。

csharplangリポジトリ内にいくつか提案ドキュメントが上がり始めたというものの中で、2点ほど取り上げて紹介。この2個だけ、ちょっと以前からの進展があったものです。

読み取り専用参照

最近、C#でも構造体を使ったパフォーマンス改善をいろいろやろうとしているわけですが。 参照戻り値とかはそのための機能ですし、 タプルは内部的にmutable(書き換え可能)な構造体になっています(パフォーマンス的にはそれが一番いい)。

ただ、大き目の構造体の受け渡しは、値渡し(コピーが発生)の負担が大きいです。 なので、例えば以下のように、参照引数を使ったりします。

static void AddTo(ref Matrix4x4 x, ref Matrix4x4 y)
{
    x.M11 += x.M12;
    // 後略
    // 4×4行列なので15行ほど同じようなの
}

ここで問題は、このコード、xyのどちらが書き換わるのかわからないところ。 xの方は書き換える前提で参照渡しをしていますが、 yの方は書き換えるつもりがない。 でも、値渡しするとコピー負荷が高いんで、やむなく参照渡しにしている。 という状態。

こういうのは、意図を明示できるべきだし、もし意図に反してyを書き換えようとしたらコンパイル エラーになるべきです。 そこで、「読み取り専用参照」が提案されていました。 C++ならよくやるやつです。const T&。 C#に対するこれまでの提案ではreadonly refなんかが上がっていたんですが、 今回inキーワードを使うのはどうだろうという話になりました。

static void AddTo(ref Matrix4x4 x, in Matrix4x4 y)
{
    x.M11 += x.M12;
    // 後略
}

前々からreadonly refだと長すぎて嫌だしという話はでていまして。 要するに、

  • 参照渡しの特殊形としてout引数があるんなら、その逆のin引数があってもいいじゃない
  • inなら、foreachやジェネリックの反変性で使ってて今もキーワードだし(破壊的変更になりにくい)

ということで、in参照引数にしてはどうかということになっているみたいです。

IntPtrに対する演算子

去年の年末に「小ネタ」で書きましたが、System.IntPtrSystem.UIntPtrはプリミティブ型です。 要するに、専用のILを持っていて、高速な計算ができる型です。 というか、IntPtrUIntPtrは、ILの内部的にはnative int, native unsigned intという名前の型になっています。 CPUのバスサイズ(32ビットCPUであれば32ビット、64ビットCPUであれば64ビット)の、実行環境依存の整数型。

最近だと、C#もいろんな場所で動かすようになってきました。 Xamarinや.NET Coreのおかげでクロスプラットフォームになっています。 その結果、このnative intをC#上でも使う機会が増えています。 (まあ、クロスプラットフォームを意識したnative相互運用をしたりといった限られた用途ですけども。 以前よりは必要性が高まっているのは確かです。)

ところが、C#上では、IntPtrUIntPtrに対する演算子が一切使えない。 IL的には持っている専用命令を一切活用できない。 #ifでプラットフォームごとにintもしくはlongにしてから計算とかが必要でした。 ということで、IntPtrUIntPtrに対して、一通りの演算子を使えるようにしたという話が出ているようです。

ローカル関数

$
0
0

ちょっと間が空いたんで「C# 7思い出話」タグでなんか書いてたことを忘れかかっていたりはしますが。

C# 7のページにローカル関数の話を足しました

これで、現状で仕様が結構安定してるやつは全部なんですよねぇ。type switch辺りはそろそろ書いてもよさそうなくらい作業進んでは来てますが。タプル型とかはもうちょっと先かなぁ(自分の執筆ペースを考えるとたぶんちょうどいいくらいに安定しそうな気も)。

ローカル関数

これは本文にも書いちゃってるんですけど、要望として多いのは、ローカル関数がほしいというよりも、匿名関数の改良だったんですよね。

などなど。

そこに来て、ローカル関数ほしい?って話を出したところ、この手の匿名関数に対する問題が、ローカル関数なら結構解決するんじゃないか的な話にどんどんなっていって。数か月後には(正式に機能追加の提案に至る](https://github.com/dotnet/roslyn/issues/2930)。

まあ、元々、C# Scripting/Interactive由来っぽい感じはあったんですが。 スクリプト環境だと、常にメソッド内部にいるかのような文法になる(トップレベルにステートメントを書ける)んで、 そこにメソッドを書くのは、実際のところローカル関数を作るのと大差ない感じになります。 たぶん、今まで全然入れてくれなかった機能が急に出てきたのはこれのせいじゃないかなぁとか思います。

ピックアップRoslyn 2/23: Shapes and Extensions

$
0
0

久しぶりに面白そうな話が。

2つほど原案があって、組み合わせて結構よさげな機能案ができたので詳細を詰めていきたいという感じの話。 元になっているのは以下の2つ。

  • Extension everything
    • メソッドだけじゃなくて、プロパティとかインデクサーとかあらゆるものを「拡張」定義したい
    • 静的メソッドも(インスタンスじゃなくて型に対して)「拡張」したい
    • インターフェイスの後刺しとかもしたい
  • Type Classes
    • Haskellの型クラス的なもの、 .NET ランタイムに手を入れなくてもちょっとした「値型ジェネリック」を使ったトリックで実現できそうという話
    • MS Researchの人と、インターンで来た人の成果っぽい
    • 型クラスってのはどういうのかというと:
      • 静的メソッドやコンストラクターを含めて、その型が持つべきメンバーを規定するもの
      • 「型、インスタンスどちらにも有効なインターフェイス」みたいな感じ

Shapes (型クラス)

今のところ型クラスの定義を Shapes って呼びたいみたい。 要するに、どういうメンバーを持っているべきかという、型の「形状」(shape)を規定するもの。

例として挙げてるのが、零元と加算演算子を持つ型。 数学で「」(group)って呼んでるやつです。 (正確にはこれだとモノイドなんですけど)

public shape SGroup<T>
{
    static T Zero { get; }
    static T operator +(T t1, T t2);
}

概ねインターフェイスと似たような書き方なわけですが、以下の点が異なります。

  • interfaceキーワードの代わりにshapeキーワードを書く
  • 静的メンバーを持てる

静的メンバーが持てるので、ジェネリックの残念な点の1つとしてよく上がる「演算子を使えない」問題が解消します。

わざわざShapes(インターフェイス的なもの)をかますのは、いろんな型、いろんな演算子がこの条件を満たすからです。 例えば:

  1. 整数の加法群: 整数に対して、零元 = 0、演算子 = +
  2. 整数の乗法群: 整数に対して、零元 = 1、演算子 = ×
  3. 文字列に対して、零元 = 空文字、演算子 = 文字列結合

これらに対して、共通ロジックで「総和」を取ったりできるわけです。

public static T AddAll<T>(T[] ts) where T : SGroup<T> // shape 制約
{
    var result = T.Zero;                   // 静的プロパティから零元を取得
    foreach (var t in ts) { result += t; } // + 演算子の呼び出し
    return result;
}

例えば、

  • 1, 2, 3, 4 に対して上記1の意味で総和(普通に足し算) → 10
  • 1, 2, 3, 4 に対して上記2の意味で総和(全部を掛け算) → 24
  • "ab", "c", "de" に対して上記3の意味で総和(Concat) → "abcde"

とかになります。

Extensions

で、拡張(extensions)。 拡張メソッドだけじゃなくて、プロパティだろうがインデクサーだろうが、静的メソッドだろうがコンストラクターだろうが、なんでも拡張できるようにしたいという話が元々ありまして。 この「拡張」で、Shapesを実装できるようにするみたいです。

例えば上記1の意味のShape (整数の加法群)であれば、以下のように書けます(構文は「仮」なもの。特に of の辺りが今後どうなるか怪しい)。

public extension IntGroup of int : SGroup<int>
{
    public static int Zero => 0;
    public static int operator +(int t1, int t2) => t1 + t2;
}

2のやつ(乗法群)であれば以下の通り。

public extension IntMulGroup of int : SGroup<int>
{
    public static int Zero => 1;
    public static int operator +(int t1, int t2) => t1 * t2;
}

呼び出し側は以下のように書けます。

// 全部を足し算。sum == 10
var sum = AddAll<IntGroup>(new[] { 1, 2, 3, 4 });

// 全部を掛け算。prod == 24
var prod = AddAll<IntMulGroup>(new[] { 1, 2, 3, 4 });

展開

構文的には割かしよさそうです。 で、気になるのはどう実現するか。 メタデータ的に(リフレクションや旧バージョンのコンパイラーから見たの時の挙動が)どうなるのかとか、 動的リンクできるのか(コンパイル時にベタ展開しちゃうと、変更時に利用側の再コンパイルが必要)とか、 パフォーマンス的に大丈夫かとか。

とりあえず、予定されている展開結果をお見せしましょう。 これまで挙げてきたコードは、以下のように展開されます。 (比較のために元のコードを並べつつ)

Shapes

元:

public shape SGroup<T>
{
    static T Zero { get; }
    static T operator +(T t1, T t2);
}

展開結果:

// shape はべたにインターフェイス化
// 静的なものもインスタンス メンバーに変更
public interface SGroup<T>
{
    T Zero { get; }
    T op_Addition(T t1, T t2); // 演算子は所定の命名ルールでメソッド化
}

Extensions

元:

public extension IntGroup of int : SGroup<int>
{
    public static int Zero => 0;
    public static int operator +(int t1, int t2) => t1 + t2;
}

public extension IntMulGroup of int : SGroup<int>
{
    public static int Zero => 1;
    public static int operator +(int t1, int t2) => t1 * t2;
}

展開結果:

// extension による shape 実装は、構造体でのインターフェイス実装に
public struct IntGroup : SGroup<int>
{
    public int Zero => 0;
    public int op_Addition(int t1, int t2) => t1 + t2;
}

public struct IntMulGroup : SGroup<int>
{
    public int Zero => 1;
    public int op_Addition(int t1, int t2) => t1 * t2;
}

Shapes に対する共通ロジック

元:

public static T AddAll<T>(T[] ts) where T : SGroup<T> // shape 制約
{
    var result = T.Zero;                   // 静的プロパティから零元を取得
    foreach (var t in ts) { result += t; } // + 演算子の呼び出し
    return result;
}

展開結果:

// 型引数を1個追加。そこに Shape を渡す想定
public static T AddAll<T, TShape>(T[] ts)
    where TShape : SGroup<T> // shape 
{
    var tShape = default(TShape); // 静的メソッド呼び出しだったものは、構造体の既定値に対するメソッド呼び出しに展開
    var result = tShape.Zero;
    foreach (var t in ts) { result = tShape.op_Addition(result, t); }
    return result;
}

メソッド呼び出し

元:

// 全部を足し算。sum == 10
var sum = AddAll<IntGroup>(new[] { 1, 2, 3, 4 });

// 全部を掛け算。prod == 24
var prod = AddAll<IntMulGroup>(new[] { 1, 2, 3, 4 });

展開結果:

// 型引数を1個追加
var sum = AddAll<int, IntGroup>(new[] { 1, 2, 3, 4 });
var prod = AddAll<int, IntMulGroup>(new[] { 1, 2, 3, 4 });

展開結果から言えること

メタデータ

展開してみればそこまで変なコードではないので、リフレクションや、古いコンパイラーから使う場合でもそんなにおかしなコードにはならないと思います。

動的リンク

結局はただのインターフェイスなので、ちゃんと動的リンクできます。 要するに、

  • ライブラリの実装側を修正する
  • 利用側は再コンパイル不要
  • JIT 実行するとちゃんと修正結果が反映される

という状態になります。

パフォーマンス

構造体+ジェネリックに展開されるのがポイントでして。 ちょうど、ついこないだ以下のような文章を書いたんですが、これと同じ状態になっています。

値型に対するジェネリックは、JIT コンパイル時に静的に展開されます。 元々インターフェイスの仮想メソッド呼び出しだったものも、通常のメソッド呼び出しに展開されます。 仮想呼び出しが消えることで、インライン展開すらされます。

もしインライン展開までされれば、きわめて高パフォーマンスに実行できます。 ここまで例示してきたようなSGroup程度、 すなわち、0/1を返したり+/*を計算するだけのものなら、 リリース(最適化オプション付き)ビルドすれば確実にインライン展開されます。

結果的に、以下のようなコードとほとんど変わらない状態に最適化されます。

public static int AddAll_IntGroup(int[] ts)
{
    var result = 0;
    foreach (var t in ts) { result += t; }
    return result;
}

public static int AddAll_IntMulGroup(int[] ts)
{
    var result = 1;
    foreach (var t in ts) { result *= t; }
    return result;
}

まとめ

なんか急にきれいにまとまった感じ。

これまで「なんでも拡張」(Extension everything)をどう実現しようか迷っていたところに、 低コストで型クラス(Type Classes)を実現できそうという案が降ってきたことで、 合わせるといい感じに。

実装的には値型ジェネリックを使ったちょっとしたトリックになっています。 値型ジェネリックに対して .NET の JIT が結構いい最適化を掛けてくれることによって、

  • リフレクションや古いコンパイラーから見てもそこまで苦しくなく
  • 動的リンク可能で
  • きわめて低コスト(静的メソッドとほとんど変わらない高パフォーマンス)

が実現できそうです。

このブログでは触れませんでしたが、元 issueでは、インターフェイスの後刺しとかについても書かれています。


ピックアップRoslyn 2/28: Milestones 7.X

$
0
0

なんか、csharplang にマイルストーンが切られてた

ちなみに、リリース時期を邪推されないように、期日は2070年とかのむちゃくちゃな日付になっています。 7.0が2070年、7.1が2071年、…みたいな。

7.0

3/7のリリースを考えるととっくに実装終わってるはずのもの。

Roslynリポジトリ側からの移植と、あと、ドキュメントがまだないのでそれが残タスクっぽいです。(最近、ほんとにドキュメントが後…)

7.1

C#的には初の「小数点リリース」計画なわけですが、これは、Visual Studio とか .NET ランタイムのリリース スケジュールとは合わせないって意味で使いたさそうな雰囲気。 ライブラリ、ランタイムやIDE支援が要らない機能を主に入れていく感じ。

7.1は、みるからに7.0の積み残し。時間的な都合で入れなかっただけで、いつでも実装可能なやつ。

private protectedなんて、7.0どころか6.0の積み残しだった李。これの話とか見事なもんで、「機能的には大したことがなくていつでも実装可能。あとはキーワードを決めるだけ。キーワードが決まらなくて延期」って状態だったのに、この期に及んで「それでも私は別キーワードがいい」ってコメントを付けてる人がいたり。 「もう多数決では決まらないからC#チームがprivate protectedに決めた。このキーワードでやるか、さもなくばまったくやらないからの二択」と説明されてなお、あれやこれやコメントを付けてる人がいたり…

7.2

なんか、パフォーマンス改善系の世代っぽい。 もしかしたら、この辺りでSystem.Memoryとか、Utf8Stringとかのライブラリを実用段階に持っていきたいのかも。 それと関連しそうなものがちらほら並んでいます。

7.X

その他、7.0の延長、ライブラリやIDEの機能追加が要らない感じのもの。

フル機能版のパターン マッチングはここに入ったっぽいんですよねぇ。 確かにまあ、型パターンが入るだけでそこそこ満足度高いですし。 再帰パターンの類は実装大変な割には用途が狭く。

8.0

メジャーリリースなので、でかい機能。時間も掛かりそうかなぁ…

レコード型とか、 null許容参照型とか、 非同期シーケンスとかはここに。

X

ほんとに未定。 「何かいいアイディアがあれば」とか、 「.NET ランタイム自体に機能追加が必要で、そっち次第」とか、そんな感じのもの。 逆にいうと、ふといいアイディアが降りてきて急に実装されたり、 .NETチームとかVisual Studio IDEチームとかのスケジュール次第では早くも遅くもなりそうな。

コード生成拡張(replace/original)とかはほんと、IDE側の対応次第って感じのものなんですよね… これ、ほんと一刻も早くほしかったりするんですけど。

ピックアップRoslyn 8/10

$
0
0

7月のデザインノートが2件ほど。

これ関連の作業がひと段落したところでまとめて清書して表に出したって感じですかねぇ。 この辺りの話、かなりの割合がもう実装されててマージされてたりします。

先週、dots.をお借りしてこんなイベントやってたわけですが

最新のmasterブランチの取ってきてビルドして実行してみると、大体この仕様通りになってる感じ。

さて、どんな感じの仕様かというと…

タプル型のメンバー名は省略・名前付きの混在可能

こんなコードでOKですって。

var t = (1, y: 2); // infers (int, int y)
(int x, int) t = (1, 2);

ちなみに、名前を省略したところは、ValueTupleがたの本来のメンバーである x.Item1 とかの名前で参照できます。

ITuple

タプル型みたいな「単に複数のデータを寄せ集めただけ」な型に対して、インデックスでメンバー参照したくなることがあります。 ValueTuple型はそのために、以下のようなインターフェイスを実装すべきじゃないかという話に。

interface ITuple
{
    int Size;
    object this[int i] { get; }
}

タプル型の分解に使いたいそうで。

ValueTuple型はこれを実装すべきだとは思うものの、名前にはまだ議論の余地あり。 インターフェイス名もIDeconstructableとかがいいかもしれないし。 要素数のプロパティもLengthとかCountとかもあり得るし。

var型がある場合。

C#のvarは、文脈キーワード(特定の文脈でだけキーワード扱いされる)です。varって名前のクラスがあると、クラス名として認識される。

で、タプル型の分解構文で以下のような書き方を認めることになるわけですが、

var (x, y) = e;

ここで、varクラスがあった場合どうなるべきか。

class var {}
var (x, y) = e;

ちなみに、世の中には、わざわざこういうvarクラスを用意しておくことで、型推論のvarを使わせない(コンパイル エラーにさせる)トリッキーな運用をしている人もいるそうで。C#チーム的には「(その良し悪しは置いといて)そういう運用も認めるべきでしょう」という感じ。 そういう背景もあって、タプル型の分解におけるvarでも、varクラスがあったらコンパイル エラーにするみたい。

var メソッド

じゃあ、メソッドの場合はどうか。分解代入の構文、メソッド呼び出しに似ているので、以下のような書き方ができてしまいます。

ref int var(int x, int y);
var(x, y) = e; // deconstruction or call?

参照戻り値なメソッドへの代入(参照先への代入)か、分解代入か、どちらにするべきか。

常に分解代入の方を選ぶそうです。メソッドの方を呼びたい場合は @var って書けばできます。

partialクラスでのインターフェイス

partialクラスの場合、複数の宣言で、同じインターフェイスを継承できたりします。 ここで、じゃあ、メンバー名違いの同じ型のインターフェイスを継承してしまった場合はどうするべきか。

partial class C : IEnumerable<(string name, int age)> { ... }
partial class C : IEnumerable<(string fullname, int)> { ... } 

タプル型は、内部的には全部ValueTuple構造体に変換されます。 名前は属性に残るだけ。 で、じゃあ、上記の名前違いのインターフェイスは別の型なのか同じ型なのかよくわからず。 紛らわしいのでコンパイル エラーにすべきでしょう。 逆に、メンバー名も含めて全一致している場合だけは、複数のpartial宣言に書いても大丈夫。

もう少し面倒なケースは、多重継承(インターフェイスであればC#でも多重継承が可能)。 以下の場合はどうすべきか。

interface I1 : IEnumerable<(int a, int b)> {}
interface I2 : IEnumerable<(int c, int d)> {}
interface I3 : I1, I2 {} // what comes out when you enumerate?
class C : I1 { public IEnumerator<(int e, int f)> GetEnumerator() {} } // what comes out when you enumerate?

現状、これもコンパイル エラーにする案で進めてるみたい。 できてそこまで大きなメリットもなさそうなので、複雑化させない方向に倒すという感じ。 もし、将来的にこれを認めたくなるような重要な利用シナリオが見つかったりした場合、それはその時に考える。

タプル リテラルの分解

null (全ての参照型に代入可能)とか、1 (intshort, byte 辺りのどれか不明瞭)とか、リテラルの場合、型があいまいなものがあります。 その分解はちゃんと働くべきか。

(string x, byte y, var z) = (null, 1, 2);

できるべきだろうとのこと。

各要素ごとに並べて書いた時と同じ挙動になるべき。上記コードであれば、まあ、↓みたいなのと同じ解釈をすべき。

string x = null;
byte y = 1;
var z) = 2;

ただし、これが逐次実行されるわけじゃなくて、一斉に代入が起きる。つまり、swapに使っても差し支えないようなにはなってる。

(x, y) = (y, x); // swap!

タプル型の中のvar

「タプル型の変数宣言」と「分解代入」は非常に似た構文になるわけですが。

(int x, int y) = GetTuple(); // 分解
(int x, int y) t = GetTuple(): // タプル型の変数宣言

じゃあ、以下の構文(これも似て非なるもの)の場合はどうなるべきか。

(var x, var y) = GetTuple(); // これは分解代入時の型推論
(var x, var y) t = GetTuple(): // varなタプル型。これは認めるべき?

で、結論的には、この後者は認めないとのこと。

分解代入の戻り値の型は void?

C#では、代入は式です。どこにでも書けます…

var x = 1;
var y = (x = 2) * x;

まあ、ろくでもないんですが。副作用を伴う式とか割かし害悪。C言語を参考にしすぎたところですね。とはいえ、今更変更できません。

例えばの話、forステートメントの中には式を書くことになっているので、以下のようなコードを書きたければ、タプルの分解代入も式でないといけないそうです。

for (... ;; (current, next) = (next, next.Next)) { ... }

とはいえ、実のところ、「戻り値がvoidの式」という扱いにすれば、forステートメントの中で使えつつ、さっきのろくでもないy = (x = 2) * x みたいなコードをなくせたりします。

ということで、voidであるべき?

まあ、これも、既存の代入式との一貫性がなくなるので、voidではなく、タプル型を返すべきだと思ってるみたいです。 C# 7では実装しなさそうだけど、後々は、分解代入の結果を、再度タプル構築して戻り値に返すべきだと思っているとのこと。

参考までに: Swift

ちなみに、Swiftはほんとに、代入は戻り値がvoidの式みたいです。 y = (x = 2) * x なんていうクソコードは認めません。

その割にインクリメント・デクリメントがあった y = ++x * x とか書けたわけですが。 そりゃ、forステートメントもインクリメントもなくしたくもなります(Swift 3で破壊的変更してまでなくす予定)。

分解を変換として、変換を分解として

分解代入と型変換はある程度似た構文です。分解は、タプル型への変換的な雰囲気があります。似てるのあれば、いっそある程度統一性を持たせるべき?

まあ、そうしない方がよさそう。分解(コンパイル結果的にはDeconstructメソッドの呼び出し)は型変換的に扱われるべきじゃない。

匿名型

匿名型({ X = 1, Y = "a" } みたいなやつ)はDeconstructメソッドやITupleインターフェイス実装を持つべき?

そうでもなさそう。実装しても、今のところ有用な利用シナリオが思い当たらないとのこと。 欲しくなる場面もなくはないけど、そういう場面では大体タプル型を使えば解決しそう。

分解代入時のワイルドカード

ワイルドカードってのは、要するに、要らない部分を読み飛ばす機能。

(var x, var y, *) = (1, 2, 3);

こういうコードで、3を読み飛ばすために使いもしないダミー変数を用意する必要はありません。

C#的に、こういう機能を入れるべきだろうとは思ってるみたい。 ただし、たぶん、C# 8になる(7には入らない。パターン マッチングと同時期に入る予定)。

あと、ワイルドカードのために使う記号はたぶん *。 関数型言語の類だと _ を使うことが多いんですが、C#では _ が有効な識別子になっちゃうので。 既存コードの意味を変えてまではこの記号は使わないみたい(コード解析をきっちりやれば不可能ではないけど、そうまでするかという話)。

double型に対するswitch

パターン マッチングが入った暁には、double型の変数もswitchに使えるわけですが。 ここで問題になるのは、double型の等値判定。

NaN(Not a Number)の扱いどうするの?とか、実は==EqualsでNaNとの比較結果が違ったりするけどどうする?とか。

==Equalsの違いというと、intの1とdoubleの1.0が等値判定とかも。前者はtrueになるけど、後者はfalse。

Equalsの側を使いそう。

ピックアップRoslyn 3/18: インターフェイスのデフォルト実装

$
0
0

C#でもインターフェイスのデフォルト実装(まんま、Javaのデフォルト メソッドと同じ機能)の実装始めたって。

結構びっくり…

驚いてるって言っても、この機能が革新的だとかそういう驚きではなくてですね。 この機能、.NET ランタイムに手を入れないと実現できない機能なので、「今から実装始めれるんだ?!」という驚きになります。

デフォルト実装

機能的にはそんなに驚くこともなく。 というか、本当にJavaのデフォルト メソッドそのままなので。 まあ、今からC#にデフォルト実装の機能を入れても、そこまで大きなインパクトはないと思います。

メリットは、

  • インターフェイスに後からメンバーを追加しても、利用側コードを壊さずに済むようになる
  • IEnumerable.GetEnumeratorみたいな、1.0時代の互換性のためだけに現存しているものの実装が楽になる

というくらい。後者の方は要するに、以下のような話。

namespace System.Collections.Generic
{
    public interface IEnumerable<out T> : IEnumerable
    {
        IEnumerator<T> GetEnumeartor();

        // こんな風に、デフォルト実装を与えておいてもらう
        IEnumerator IEnumerable.GetEnumeartor() => GetEnumerator();
    }
}

namespace ConsoleApp1
{
    using System.Collections.Generic;

    class MyEnumerable : IEnumerable<int>
    {
        IEnumerator<int> GetEnumerator()
        {
            yield return 1;
            yield return 2;
            yield return 3;
        }

        // IEnumerator IEnumerable.GetEnumeartor の方は書かなくてもいい
    }
}

あったらあったで便利なんですが、ランタイム側の修正が必要となるとなかなか。

.NET ランタイムに手を入れる

C# の機能って、C# 3~7までの間は、コンパイラーが頑張って実現しているような機能がほとんどで、 .NET ランタイム的にいうと .NET Framework 2.0上でも動くものばかりでした。

(参考: C#の言語バージョンと.NET Frameworkバージョン async/awaitTaskクラスに依存していたり、ライブラリを必要とする機能はちらほらありましたが、 ランタイムに関しては.NET Framework 2.0以降何も変化していません。 )

とはいえ、さすがに、ランタイムに手を入れないとなぁという感じはしてきています。 いくつか、「ランタイムによってコンパイラーの進化が妨げられている」みたいなリストもできていたりします。

見るからに「バグだから直して」って感じのもありますが、 他にも例えば、以下のようなものが挙がっています。

  • 構造体に対して引数なしのコンストラクターを認めたい(Activator.CreateInstanceのバグで今はそれができない)
  • 属性にジェネリックなものを認めたい
  • インターフェイスにデフォルト実装を認めたい

需要があるんだからランタイムの修正してくれよって話ではあるんですが、 ここ数年の.NETって、クロスプラットフォーム化とかで忙しくて、新機能を追加する余裕なんて全然なかったんですよね。 そりゃまあ、デフォルト実装を認めるよりも先にXamarin (iOS/Android)とか.NET Core (Linux/Mac)とかの安定化の方が先だろと。

ってことで、個人的な予想としては、この手のランタイム修正が必要な機能の実装は.NET Core 2.0とかよりさらに後だろうとか思ってたんですが。 デフォルト実装も、当初予定ではC# 8.0 milestoneに並べられてたはずですし。 なので、冒頭の「今?!」という驚きにつながるわけです。

今作業が始まったってことは、.NET Core 2.0(一瞬、「今年の春には」とか言う話も出ていましたけど。今年中には出るのかなぁ…出るはずよなぁ…きっと?)とかでひょっとしてランタイムの修正もするのかな?とか期待に胸を膨らませたり。

C# 7.0リリース(もう2週間くらい経過したけども)

$
0
0

気が付けば、Visual Studio 2017がリリースされてから2週間くらい経ってしまっているわけですが…

マイクロソフト公式のRelease CelebrationでLT登壇したり、 自分主催のリリース記念勉強会やったりとかで、 すっかり力尽きていました。

そんな感じでしたが、2点ほどそれの事後的な話。

動画

Visual Studio 2017 リリース記念勉強会、 当日にストリーミング配信とかはやらなかったんですが、 一応動画に撮っていたりはしまして。

許可が取れたものは、YouTube にアップロードしていってたりします。 C#ユーザー会でチャネルを作ったので、こちらで公開中です。

リリース版対応

C# によるプログラミング入門内から、C# 7.0がらみは「正式版ではこうなります」、「予定」、「仮」的な文面全部なくしたはず。 ついに正式リリースですよ。

ちなみに、RCからRTMの間で、C# 7.0の実装にも微妙に変更もありました。

1つ目。タプルに対する拡張メソッドの呼び出しで、オーバーロード解決時にメンバーごとの型変換を考慮してくれるようになりました。 1月25日のブログの最後の方にちょっと書いたやつです。 「『今やる』か『今後もうできないか』の2択なので、もう時間も限られているけど頑張って今やってみる」っていう状態になっていた機能が、無事に入りました。

2つ目。while の条件式中で宣言した変数のスコープ変更(while内のみになった)。 去年の12月にちょっと書いてたやつ。 これ、直前のRC版でもまだwhileの外にスコープ漏れてて、リリース後に確認してみたらちゃんとwhile内のみに変更掛かっていることを確認。 うちの記事にも反映させておきました。

そういや、最近、すっかりC#のバージョン番号の書式が「C# 7.0」(.0が付く)なんですよね。 一時期は、C# 6, C# 7だったんですけども。 しれっと、いろいろとやっぱり .0 が付くように変わっていたり。 うちのサイト内の表記、どうしようかな…

.NET Framework 4.7

$
0
0

.NET Framework 4.7がリリースされたみたいですね。

更新内容

.NET Framework 自体よりも、ドキュメントとかのシステム更新の方が目立つかも。

自分が直接関係しそうなのは、ValueTuple構造体の追加くらいかなぁ。

ValueTuple

C# 7.0のタプルを使うには、ValueTuple構造体が必要なわけですが。

.NET Framework 4.7は、標準でValueTuple構造体を含む初のバージョンになりました。 (.NET Framework 4.7以外ではSystem.ValueTupleパッケージの参照が必要。) 後述の通り、今日のリリースだとWindows 10にしか.NET Framework 4.7が来ていないので、あまりまだ選べる選択肢ではなさそうな気もします。 まあそれに、PCLとかNetStandardなライブラリ、.NET CoreでもまだSystem.ValueTupleパッケージが必要です。

Windows 10 Creators Update

というか、単体インストーラーはなくて、Windows 10のCreators Updateとともにアナウンス。

Visual Studio 2017も同時に更新されましたけども、.NET Framework 4.7をターゲットにするためには、現状だと、

  • Windows 自体にCreators Updateを掛ける
  • Visual Studio 2017を更新する
  • 更新の際に、[変更] → [コンポーネント] → .NET Framework 4.7 SDK、.NET Framework 4.7 Targeting Pack にチェックを入れる

ってやらないとダメみたい。

Visual Studio 2017に.NET Framework 4.7 SDKを追加

リリース順

リリース順、これまでだと、

  1. .NET Framework の更新/単体インストーラー配布
  2. Visual Studio/C# のリリース = Windows のリリース

みたいな順が多かったんですけどね。今回、

  1. Visual Studio/C# のリリース
  2. Windows のリリース/.NET Framework の更新
  3. .NET Framework 単体インストーラー配布(まだない)

っぽい。 「C# 7.0がリリースされたのに、ValueTupleを標準で含んだバージョンの.NET Frameworkはまだ出ないんだ」とか、 これまでのリリース順からすると不思議な感じでしたけども、Windowsのリリース スケジュールのせいでしたか。 (てっきり、.NET Core辺りのリリース遅れが犯人かと思ってました。)

.NETランタイム更新

.NETランタイムの配布方法を、以下のようにしていきたいのかなぁとか思ったり。

  • スマホ: AOT(Ahead of Time: 事前コンパイル)でアプリ単一バイナリにコンパイル
  • サーバー: .NET CoreでNuGetベースの更新
  • デスクトップ: Windowsの自動アップデートに同梱

C# にもそろそろ、ランタイムへの機能追加が必要な構文を追加したいみたいな動き出てきてますしね。

古いランタイムが残りにくい仕組みにしたがっていそう。

ピックアップRoslyn 8/16 Close祭り

$
0
0

実作業が一段落つくと始まるissue整理。

すごい数一気にコメントついたりCloseされたり。以下、そのまとめ。

やらない

1行目: どういうものか

2行目: やらない理由

Proposal: Fixed-size buffers enhancements #126

構造体の内部に固定長配列を持てるようにしたい。

CLRのレベルで対応してもらいたい。C#コンパイラーでのコード生成はメリットの割に複雑すぎる。

infoof / propertyof / methodof (and reflection objects in attributes?) #128

infoof演算子(info-of、typeofのノリでMemberInfoを取得する)が欲しい。

リフレクションを言語に組み込みたくはない。nameofでもかなりのシナリオをカバーできる。

[Proposal] try-catch expressions #132

try-catch を式にしたい。

このページで提案されている構文だと問題がある。再提案があれば、取り組む価値があるかはまだ検討の必要があるかもしれないが、現状そこまでには思っていない。

Language support for the decorator design pattern #124

デコレーター(PythonのデコレーターとかKotlinのclass delgationと同種の機能)が欲しい。

いくらかのメリットがあるのはわかるし、過去に似た機能の提案も見てきた。でも、コンパイラーが暗黙的にコード生成するような機能には心配事が多々ある。 今なら、ソース ジェネレーター (たぶんC# 8)で解決できるかもしれない。

Strongly-typed type aliases #58

同じ型なんだけど、互いに暗黙的型変換はできないエイリアスを作りたい。例えば、EmailAddress型(中身はstring)とstringを区別したい。

ソース ジェネレーター (たぶんC# 8)を使って構造体をコード生成して、同様のことを実現してほしい。ソース ジェネレーターで苦痛は減るはず。

Auto properties with setter blocks #123

プロパティで、getのみ自動実装で、setは手動実装できるようにしてほしい。

良いディスカッションはあった。ディスカッションの中で出てきた副次的な案は別のissueページに移った。 でも、このページで提案されている元々の提案はやらない。バッキング フィールドを参照するような機能は得られるものに対して労力が過剰。

Implement Units of Measure or a broader class of similar contracts #144

F# のUnits of MeasureがC#にもほしい。

非常にクールな機能だと思うものの、同種のものをC#に追加するのはメリットの割にコストが大きい。

訳注: たぶん、上記のStrolngly-typed typle aliasesで十分な利用シナリオも多いと思う。

[Proposal] Better handling of multiple comparisons with the same variable (a > min && a < max) #136

min < x < max みたいな、数学でよく見る多項比較できるようにしてほしい。

C# ではすでに a < b < c(a < b) < c の意味で有効な式になっているし、難しい。 e in (0 ... n) みたいな文法も、e < n なのか e <= n なのか混乱の元。

Allow type parameters to be declared co/contra variant on classes. #171

クラスの型引数にも共変性・反変性を認めてほしい。

CLRレベルでの対応が必須。

Proposal: Virtual arguments in methods #176

引数の実行時の型でメソッドのオーバーロードを呼び分けれるようにしてほしい(いわゆる多重ディスパッチ)。

ちゃんとやるのであればCLRレベルでの対応が必須。 型スイッチ(C# 7で入る)があれば書くのが多少楽になるけども、多重ディスパッチは型スイッチと比べてかなり複雑。

Feature Request: Lighter syntax for doc comments #85

docコメントがXMLなの重たい。JavaDoc/JSDocみたいな文法ほしい。

現状に不服があるのはわかる。でも、新旧文法の混在・競合みたいなのは嫌だし、直近での検討はしない。

重複削除・もうやってる

1行目: どういうものか

2行目: 何と重複してるか

Allow local variables to be static and/or readonly (deeper scoping) #49

ローカル変数にstatic(スコープが関数内に限られた静的フィールドを作る)とかreadonly(書き換え不能)とか付けれるようにしてほしい。

以下の2つに分割できる。

Allow C# to use anonymous iterators. #24

匿名関数でイテレーターを作らせてほしい。

C# 7で入るローカル関数で同じことができる。

Please provide the C# Specification in PDF instead of DOCX #31

C#の仕様書をPDF提供してほしい。

長期作業として、マイクロソフトC#仕様書とECMA標準規格書の統合作業をしている。ECMAの方のはPDFで提供されてる。

訳注: 現体制になってから全然仕様書が表に出てこないんだけども… Long termってどのくらいLongなんだろう…

Lambdas omitting parameters #20

ラムダ式の引数のうち、要らないものは無視できる構文が欲しい。someFunction((_, foo) => fooだけ使う); みたいなの。

パターン マッチング(たぶんC# 8)と一緒にやる。

Extension properties for classes #112

メソッドだけじゃなくて、プロパティも拡張で作れるようにしてほしい。

以下の提案の一部分になる。

UnreachableAfterAttribute and unreachable code detecting #59

常に例外を出すことが分かっていて、呼び出しよりも後ろのコードには絶対に到達しないことが保証できるようなメソッドを作りたい。そのためにUnreachableAfterみたいな属性を作ってほしい。

neverってのを検討中。

引き続き検討

1行目: どういうものか

2行目: どう取り組んでいるか

[Proposal] Object initializers for factory methods #133

new T { X = ... } みたいなのを、new演算子ではなく、ファクトリー メソッドを使った場合でも使えるようにしてほしい。T.Create() { X = ... } みたいな。

with (たぶんC# 8になる)と合わせて考えたい。

Do not require type specification for constructors when the type is known #35

var (右辺値から変数の型を推論)の逆で、T x = new();みたいに、左辺値からnewの型推論をしたい。

分解(deconstruction)の対となる構文として魅力的になりそう。

訳注: 分解の方がvar (x, y) = tuple;みたいに型推論が効くんだから、構築(new)の方にも型推論が効いても良さげ。

IAsyncDisposable, using statements, and async/await #114

非同期Disposeがしたい。usingステートメントで、IAsyncDisposable的な非同期版のインターフェイスを使えるようにしてほしい。

Lucianにタスク割り当て。

訳注: たぶん、非同期シーケンスと一緒に検討。Lucian はTask-likeとか含め、非同期処理がらみの新仕様に取り組んでる人。

Extend LINQ syntax #100

クエリ式の後にToList()とかSingle()とかの、シーケンス自体に対する操作を呼ぶのが、今の構文だとだるい。

バックログに入れておく。from x in items where x.foo, select x.bar do Single() みたいな構文がいいかも。

[Proposal] (Minor) Method groups for constructors #140

コンストラクターをメソッド グループ(デリゲートにメソッドを渡すために、Action a = Methodとか書くやつ)として認めてほしい。

よくある要求だし、よいアイディア。

ピックアップRoslyn 4/16

$
0
0

結構久々のSprint進捗報告。

Visual Studio 2017リリース前後の作業が「バグ修正してた」しかなかったので、割かしほんとに久々。 C# 7.1/7.2作業が始まっています。 いくつか紹介。 (ここに並んでいるのが7.1/7.2に入る全てでもないし、優先度の変更もあり得るものです。あくまで、最近作業したもので、現状の予定としてこのバージョンで出したいというもの。)

C# 7.1

default 式

左辺から型推論が効くなら、default(T)(T)の部分を省略可能にするという話。

割と前から実装を初めていて、masterにmerge済み。

async main

やっと、Main メソッドを非同期にできるようになるみたい。実装的には、

  • Mainの戻り値にTaskTask<int>を許す
  • その場合、async修飾子を付けるの必須
  • コード生成で、Main().GetAwaiter().GetResult()を呼ぶだけの繋ぎ用メソッドを生成する

という感じみたい。

Tuple names

タプルでも、匿名型みたいに要素名を自動で付けたいっていう話。 ↓みたいに、変数x, yがあるとき、(x, y)と書けば、タプル要素名を自動的にxにしてほしいという。

tuple names

C# 7.2

一部ビルドできるようにして、CoreFX Labsで使ってもらい始めたみたい。

確かに、時々、Visual Studio 2017付属の普通のC# 7.0コンパイラーではビルドできないコードがあるんですよねぇ、corefxlab。

Span Safety

System.Memoryパッケージ中に入ってるSpan<T>構造体がらみで、ちょっとコンパイラーによる制限を掛けたいっていう話。

Span<T>型は、内部的にスタック上の参照を握っちゃったりするんで、refと同種のコード フロー解析をしないとまずいということみたい。今は妥協的な実装をしているものの、将来的にはByReference<T>とかいう、ほんとに「フィールドとして参照を持てる型」を作って使いたいみたいで、そうなると、Span<T>に対するフロー解析をちゃんとやらないといよいよまずい。

要するに、

  • ref Tと同種の扱いをしないといけない型をref-likeタイプと呼ぶ
  • 条件としては、
    • 参照(ref T)や、一定範囲のメモリ((ref T data, int length))を握っている構造体
    • あるいは、それを入れ子で握っている構造体
  • 掛かる制限としては、ref Tと同じく、

という型。

補足: TypedReference

これ、実のところ、現在のC#でもTypedReferenceっていう「ref Tと同じ扱いしないとまずい型」があったりします。 まあ、隠し(ドキュメント化されていない)機能を使わないと扱えない型なので問題にはなっていませんが。

現状のTypedReferenceは特殊な制約が掛かっていて、やっぱり普通の型のフィールドにできなくなっています。 C# 7.0で参照戻り値が書けるようになったのに対して、TypedReferenceはいまだに戻り値にも使えません。 これも、参照戻り値と同ラインまでは制限を緩めて大丈夫なはずで、ref-like型の一例です。

ref readonly

2月に紹介したやつ。C++でいうconst&。 構造体のコピーを避けるための参照渡しであって、書き換えは認めたくないっていうやつ。 一部、プロトタイプ実装を始めたみたいです。

その先

ちょうど最近Build Insiderの記事書きましたけども、インターフェイスのデフォルト メソッドの実装作業、もう始まっていたりします。 まあ、それなりに時間が掛かる作業なので、今実装が始まっているって言っても、実際のリリースはだいぶ先。

同様に、非null参照がらみもちらほら作業を進めてるみたいです。 (書かれてる作業内容的には、前々から作業してたやつがしばらく塩漬けで、それを久々にmasterブランチと同期したっぽい?)


C# でビットフィールド

$
0
0

csharplangに、

みたいなのが投稿されていまして。

「それ、ライブラリとアナライザー、ちょっとしたソースコード生成でできるよ。」という話。

BitFields ライブラリ

ということで実装してみたのがこちら。

昔、ビットフィールド的なものを手作業実装してた時に、「これはコード生成でやりたい…」とか思ってて、 できる宛まではついてたんですが。 なんだかんだ言ってアナライザーを書くのは結構めんどくさいんで、放置してすでに数年。

まあ、いい機会だから久々に重い腰を上げてアナライザー書いてみるかと思って作ったのが上記のBitFieldsです。

できること

Nビット整数

Nビットまでの整数を受け付けるBitN(Nは1~64)型と、それに対するアナライザーがあります。

BitN型とアナライザー

ちゃんと、ビット数を超える値(例えばBit1に2)を代入しようとするとコンパイル エラーになります。

ビットフィールドコード生成

例えば、RGB555とか、半端なビット数で色情報を扱う形式があります。 32bitカラーが当たり前な今時だと珍しいですけど、昔はこういう形式も割と使われていました。

で、それを、こう書く。

struct Rgb555
{
    enum BitFields
    {
        B = 5,
        G = 5,
        R = 5,
    }
}

コード生成都合で、「構造体の中にBitFieldsという名前のenumを定義、値としてビット数を与える」みたいな規約ベースの型情報を書きます。 このenumはあくまでメタデータ(型情報)であって、実行時には一切使いません。

で、以下のように、クイック アクション(電球アイコン)が出るので、生成メニュー(Generate bit-fields)を選択。

ビットフィールド生成

以下のようなコードが生成されます。

using BitFields;

partial struct Rgb555
{
    public ushort Value;

    private const int BShift = 0;
    private const ushort BMask = unchecked((ushort)((1U << 5) - (1U << 0)));
    public Bit5 B
    {
        get => (Bit5)((Value & BMask) >> BShift);
        set => Value = unchecked((ushort)((Value & ~BMask) | ((((ushort)value) << BShift) & BMask)));
    }
    private const int GShift = 5;
    private const ushort GMask = unchecked((ushort)((1U << 10) - (1U << 5)));
    public Bit5 G
    {
        get => (Bit5)((Value & GMask) >> GShift);
        set => Value = unchecked((ushort)((Value & ~GMask) | ((((ushort)value) << GShift) & GMask)));
    }
    private const int RShift = 10;
    private const ushort RMask = unchecked((ushort)((1U << 15) - (1U << 10)));
    public Bit5 R
    {
        get => (Bit5)((Value & RMask) >> RShift);
        set => Value = unchecked((ushort)((Value & ~RMask) | ((((ushort)value) << RShift) & RMask)));
    }
}

現状だと1度は手作業で「クイック アクションの選択」が必要なので、使い勝手はいまいちなんですが。 そのうち、正式にコード生成機能がC#に入るはずで、その暁ににはもう少し利便性がよくなります。

ライブラリだけでできることはライブラリで

まあ、一般論として、ライブラリだけでできるならライブラリ提供でいいわけで。 ライブラリを作ってみた結果として、本当にライブラリだけだと不便ということになれば、そこで初めて言語文法の提案をすればいい。

実際、ライブラリありきで出ている提案もあったりします。

特に、今回やったビットフィールド生成みたいなものは、ちょっと「コンパイラーの仕事」にするにはいまいちかなぁと思います。

  • シフトやマスク演算のコードが見えている方が理解がしやすい
  • エンディアンの問題とかあって、汎用化するには怖い
    • 個人でライブラリを作る限りには「ビッグ エンディアン?知らない子ですね。現存するエンディアンじゃないですよ?」とか敵を作りそうな発言もできるものの、標準に取り込む際にそれを言えるかというと無理
  • 人によって求めるものが違う
    • immutable版が欲しい
    • Valueフィールドがpublicなのは嫌
    • コンストラクター、デコンストラクターもほしい
    • BitN、コンパイル時チェックだけじゃなくて実行時チェックもほしい(あるいは逆に、そんな実行時コストは絶対避けたい)

しかも今のC#だと、アナライザーを書けば「Bit1なら0と1だけ代入で来てほしい。2以上はビット数オーバーでコンパイル時エラー」みたいなこともできます。 ライブラリだけでできちゃうことも増えているので、コンパイラーのバージョンアップとか待ってないでライブラリ作っちゃえば良かったりします。

int 型のジェネリック型引数

$
0
0

昨日のビットフィールド用コード生成の副産物の話。

M~Nビット目

「int型変数のM~Nビットの範囲を取り出す処理をしたい」みたいなときに、「M~Nビット目を表す型」を作りたいときがあったりします。 (まあ、ビットフィールドの利用頻度自体がそんなに高くないので、この型の需要もそこまで大きくはないですが。)

ってことで、書きたいのはこんなコード:

// C# では書けない書き方。C++だとこんな感じのことができたりする。
// [M, N) (N は含まない)の範囲のビットを読み書きする構造体。
struct Bit<T, M, N>
{
    // C# で書けないことその1: 参照フィールド
    ref T _r;
    public Bit(ref T r) => _r = ref r;

    // C# で書けないことその2: ジェネリック引数が型じゃなくて int
    private const int Shift = M;
    private const T Mask = (1 << N) - (1 << M);

    // C# で書けないことその3: ジェネリックな型に対して静的な処理(& とかの演算子)
    public T Value
    {
        get => (r >> Shift) & Mask;
        set => r = (r & (~Mask)) | ((value << Shift) & Mask);
    }
}

素直にできないことが3つほどあります。

  1. 参照フィールド
  2. int 型なジェネリック引数
  3. ジェネリックな型に対して静的な処理(特に、& などの演算子)

1つ目の参照フィールドに関しては、unsafeでよければポインターを使って近いことはできます。 (ポインターなので、使う際にfixed必須だったり、参照先が残っている保証がなかったり、いろいろ問題あり。) 今、ByReference<T>とかいう、ランタイムで特殊処理して「参照フィールドっぽいもの」を実現しようとしている動きもあります。

まあ、今日の話は残りの2つ、特に、2つ目のint型ジェネリック引数です。

ジェネリクスに静的な処理を

素直な手段では、C#で静的な処理に対してジェネリクスを使う手段がなかったりします。 だいたいインターフェイス越しにメソッドを呼ぶことになっちゃって、仮想呼び出しになる。 仮想呼び出しなので、インライン展開とかも効かず、あんまり実行性能はよろしくない。

ですが、まあ、ちょっと煩雑なコードを書いていいなら、ジェネリック、かつ、高性能なコードを書く手段は一応あります。

参考:

こういうトリックを使って、「M~Nビット目を表す型」を作ってみたのが以下のコード:

int 型なジェネリック引数の需要

まあ、上記のBits<T, TOp, LowBit, HighBit>だと、やってることが多すぎてちょっと細かく見て行くのしんどいかなぁとか思って、「int 型なジェネリック引数」もどきを実現する部分だけを抜き出したのがこちら:

この手の要件に対してよく例として使われるガロアの有限体を実装しています。

またちょっと、数学わからない人には申し訳ない感じの例ですが… 要するに、Nが素数のとき、N個の値から成る「四則演算全部まっとうにできる型」が作れるって話です。 (整数の部分集合なのに、整数÷整数で、同じ範囲の整数値に収まる。任意の値 a に対して、a × b = 1 となる b がただ1つ定まります。)

2値からなるF2と、3値からなるF3は当然「別の型」なわけで、 プログラムを書く場合にも、型引数にして2と3で「別の具象型」を作れる仕組みが欲しくなります。

GaloisField<N>

ということで、それをC#で、GaloisField<N>って書き方で書けるようにしたのが今日のポイント。

必要な個所だけ抜き出すと、以下のような感じ。

public interface IConstant<T>
{
    T Value { get; }
}

public struct GaloisField<N>
    where N : struct, IConstant<int>
{
    // ガロアの有限体を作るにあたって必要なのは、拡張ユークリッド互除法っていうアルゴリズム
    // https://github.com/ufcpp/UfcppSample/blob/master/Demo/2017/IntTemplateParameter/IntTemplateParameter/GaloisField_N.cs#L43

    // で、その中で、「i を N で割った余り」を使う
    // (C# の % だと、i が負の時に結果が負になるので、その場合には N を足して正にする。)
    static int Mod(int i)
    {
        // 定数「N」の代わりに、default(N).Value って書く
        i %= default(N).Value;
        if (i < 0) i += default(N).Value;
        return i;
    }
}

ただし、定数代わりに、以下のような構造体を作っておく必要あり。

public static class ConstantInt
{
    public struct _0 : IConstant<int> { public int Value => 0; }
    public struct _1 : IConstant<int> { public int Value => 1; }
    public struct _2 : IConstant<int> { public int Value => 2; }
    //...
}

いちいち必要な分だけ構造体を定義しておかないといけないので、定数の置き換えとしては不完全ですけども、 実行性能的には結構いい結果になるはずです。 (ちゃんと静的に展開されて、インライン化もされる状態になります。)

タプル

$
0
0

思い出したかのように「C# 7思い出話」。

タプルがらみも先週末で一通り書き終わったはずかな。

タプル

これ自体はおおむね当初予定通りですかね。 そんなに裏話的な面白話もなく。 文法的に変化が大きいんで実装は大変そうでしたが。

しいていうと、他の提案を差し置いてタプルの優先度が高かった的な話ですかね。 タプルは、レコード型とか、 C# 7のさらに先で入る予定の機能の基礎に使う可能性があって。 依存順的に先にないと行けないという、割かしシンプルな話。

タプルは、(int x, int y)みたいなメンバー名を持った型から、ValueTupleという完全に名前のないデータ構造を生成する機能なわけで、 型に緩い世界と厳しい世界の橋渡しとして期待されます。ロジックを書く上では厳しい型付けを持っている方が楽ですが、View (WPFやUWPのData Bindingみたいな世界)や、Wire Data (通信層でのデータのシリアライズ・デシリアライズ)とかでは緩い型の方が助かったりします。その間をつなぐ基礎に使えそうって意味で、タプルには期待感があります。

そして、レコード型をタプルをベースに作る、あるいは、レコード型からタプルへの変換を用意すれば、型名がある世界とない世界もつなげます。

レコード型をタプルをベースに作るっていうのは、例えば以下のようなコードを書いたとして(これがレコード型って呼ばれる機能)、

class Point(int X, int Y);

以下のようなコードに展開するという案。

class Point
{
    (int x, int y) Value;
    public int X => Value.x;
    public int Y => Value.y;
}

レコード型からタプルへの変換を用意というのだと、以下のような感じ。

class Point
{
    public (int x, int y) Value => (X, Y);

    public int X { get; }
    public int Y { get; }
}

どちらにしろ、依存関係があるなら、依存されている側の方が先に落ち着いていないといけない。

レコード型は、その前身となる primary constructorっていう機能提案から考えるとC# 6よりも前(一瞬、C# 6に入れる想定で話が進んでた)、2014年半ば頃からあります。それが、「もっと洗練してレコード型にしてから取り入れたい。そのためにはC# 6では無理」となり、「タプルやパターン マッチングと併せて考えたい。そのためにはC# 7よりも後」となり今に至ると。

分解

こちらは逆に、前倒し。 前倒しというか、パターン マッチングがらみの仕様のうち、一部分だけが先行してC# 7に入るわけですが、その一部。

パターン マッチングは結構大きな提案です。All or Nothing、「全部入れるか全部入れない」かとかで入れようとすると、たぶん3年くらい先になります。 こないだ短期リリース サイクル化の話とか書きましたが、後への影響残さないなら、先に少しだけリリースする方が助かります。

元々は、以下のような構文で話が進んでいたんで、それからするとずいぶん、分解専用の構文になりました。多少、そこは、今後、パターン マッチングの全体が入るときに衝突しないのかな?とか思ったりはします(たぶん大丈夫という判断が付いたからC# 7で入ることになったはずですが)。

let パターン = 分解したいもの;

新しい csproj 形式

$
0
0

Visual Studio 2017で、csproj 形式が新しくなりました。

背景としては、

  • 一時期、脱msbuildをしようとしてた -脱msbuildのついでに、csprojを辞めて、project.json 形式にプロジェクト設定全部入れようとしてた時期があった
  • 結局、msbuildに戻ったけども、既存のcsprojをもっとシンプルにしたいという要件だけが残った

というものです。過渡期に関しては昔書いたブログ参照:

最近、やっと新形式のcsprojの扱いに慣れてきたのでブログに書き残しておきます。

サンプル: https://github.com/ufcpp/UfcppSample/tree/master/Demo/2017/NewCsproj

新形式

これまで、Visual StudioでC#プロジェクトを新規作成すると、以下のような感じのcsprojファイルが作られていました。 (この例は、ConsoleAppNet35という名前で、 .NET Framework 3.5 向けのコンソールアプリを作ったものです。)

一方で、Visual Studio 2017で、「.NET Standard向けライブラリ」もしくは「.NET Core向けアプリ」を作ると以下のようなcsprojが作られます。

.NET Standard 1.4向けライブラリの例:

<Project Sdk="Microsoft.NET.Sdk">

  <PropertyGroup>
    <TargetFramework>netstandard1.4</TargetFramework>
  </PropertyGroup>

</Project>

.NET Core 1.1向けコンソール アプリの例:

<Project Sdk="Microsoft.NET.Sdk">

  <PropertyGroup>
    <OutputType>Exe</OutputType>
    <TargetFramework>netcoreapp1.1</TargetFramework>
  </PropertyGroup>

</Project>

要するに、「どうせ初期設定のまま大して変更しない設定が多いんだから、全部消す。設定した値だけを入れる」という変更。

ProjectタグにSdk="Microsoft.NET.Sdk"という属性を入れることで、新形式とみなされ、この「要るものだけ残して全部消す」動作が有効になります。

.NET Standard/Core 以外にも使える

Visual Studio 2017でも、「.NET Framework向けライブラリ」もしくは「.NET Framework向けアプリ」のプロジェクト テンプレートは古いままです。 旧型式のcsprojファイルを生成します。

でも、.NET Framework向けライブラリ/アプリでも、手作業で良ければ新csproj形式を使えます。 必要な作業は、

  • .NET Standard向けライブラリか、.NET Core向けライブラリを選んでプロジェクトを新規作成する
  • 作られたcsprojファイルを開いて、TargetFrameworkタグの中身を書き換える

これだけです。 .NET Framework 2.0とか3.5とか、古いフレームワークもちゃんとターゲットにできます。 また、旧型式のcsprojから、新形式のcsprojも「プロジェクト参照」できます。

この仕組みで作ったサンプル プロジェクトが以下の通り。

複数ターゲット

新形式のcsprojでは、ターゲット フレームワークを複数指定できます。

  • TargetFrameworkタグを消して、TargetFrameworks(複数形)に置き換えます。
  • ターゲットを ; で区切って複数並べます。

例えば、以下のような感じ

<Project Sdk="Microsoft.NET.Sdk">

  <PropertyGroup>
    <TargetFrameworks>net35;netstandard1.0</TargetFrameworks>
  </PropertyGroup>

</Project>

この状態で、自動的にNET35NETSTANDARD1_0みたいなシンボルが#defineされた状態になるので、 C#コードも以下のような条件コンパイルができます。

    public class Class1
    {
#if NET35
        public static string Name => ".NET Framework 3.5";
#elif NETSTANDARD1_0
        public static string Name => ".NET Standard 1.0";
#endif
    }

新形式でだけ有効になる Visual Studio 2017 新機能

msbuild/Visual Studioは、ビルドがらみのいくつかの新機能のオン/オフ切り替えを、ファイル形式を見て分岐しています。

機能としては実装されているものの、下手に既存のプロジェクトに新機能を適用すると互換性を壊しかねないので、新しいプロジェクトにだけ適用したいという意図です。

確かに既存プロジェクトの挙動が変わると迷惑なので仕方がないんですが、 待望の新機能を使うためにはプロジェクトの移行作業が必要になるので面倒ではあります…

csprojでも、新形式でだけ働きだす機能がいくつかあります。

ワイルドカード

ソースコードのバージョン管理をしていて、 C#ファイルを追加するたびにcsprojが衝突してうっとおしいという経験をした方は多いと思います。 ソースコード1つ1つに対して、以下のようなタグが書き出されるせいです。

  <ItemGroup>
    <Compile Include="Class1.cs" />
    <Compile Include="Properties\AssemblyInfo.cs" />
  </ItemGroup>

これに対して、Visual Studio 2017では、ワイルドカード指定ができるようになっています。 以下のように、**/*.csでソースコード指定すれば、フォルダー下のすべてのC#ファイルがコンパイルの対象になります。

  <ItemGroup>
    <Compile Include="**/*.cs" />
  </ItemGroup>

これは、実のところ、Visual Studio 2017を使えば旧形式csprojでも使えはするんですが…

  • もちろん手書きで csproj ファイルを書き換えないと、Visual Studioはワイルドカードを書き込んでくれない
  • それどころか、その後、Visual Studio上でC#ファイルの追加をしてしまうと、ファイル名指定のCompileタグが追加される
  • **/*.cs指定のタグがある状況下で、上記操作でファイル名指定のタグが出来てしまうと「ファイルが2重に読み込まれています」警告が出まくる

という状態になります。

一方で、新形式csprojでは、何も書かなくてもデフォルトで<Compile Include="**/*.cs" />が入っているという扱いを受けます。

推移的パッケージ参照

NuGetパッケージの「依存先の依存先」を遷移的に(transitive)解決してくれます。

これは、Visual Studio 2017での初出の機能ではなく、2015の頃からのものですが、 csproj形式の新旧によってNuGetパッケージ参照の仕方が変わります。

昔、「.csproj + project.json」で書きましたが、「NuGet 3」移行の機能です。

NuGetパッケージの依存管理を packages.config でやっていると古い挙動になります。 一方で、Visual Studio 2015, 2017では、以下の方法で依存管理出来て、この場合は新しい挙動になります

  • (Visual Studio 2015の頃) project.json を使う
  • (Visual Studio 2017) PackageReference を使う

PackageReference は、新形式csprojを使うのであれば無条件にこの形式が使われます。 旧csprojの場合でも、以下のオプションを設定することでこちらを使うことができます。

enter image description here

挙動の違いは以下のようなものです。

  • 古い挙動: 依存先が芋づる式に全部 packages.config に書き出される
  • 新しい挙動: 直接参照したパッケージだけが書き出される

推移的プロジェクト参照

Visual Studio 2017では、プロジェクト参照でも「依存先の依存先」を遷移的に(transitive)解決してくれます。

これは、新形式csprojを使っている場合にだけ有効です。 注意点として、依存階層全部が新形式csprojでないと、この仕組みが正しく動作しません。 新形式csprojからであっても、依存先が旧型式だった場合、「依存先の依存先」を全部律儀にプロジェクト参照していく必要があります。

決定論的ビルド/Portable PDB

新形式csprojを使うと、どうも、アセンブリ(exeやdll)やPDB(デバッグ情報ファイル)の形式がちょっと変化するみたいです。

C#コンパイラーが割かし最近実装した2つの機能があります。

  • 決定論的ビルド
    • ドキュメント: Deterministic Inputs
    • コンパイラーに /deterministic オプションを付けると有効になる
    • 入力が同じなら、バイナリレベルで完全に同じ出力が常に得られるという機能
    • ビルド結果のキャッシュが効きやすくなって、テスト実行とかが大幅に速くなる
  • Portable PDB
    • ドキュメント: Portable PDB v1.0: Format Specification
    • /debug:portableオプションを付けるとこの形式でPDBを出力する
    • 仕様がオープンになっていなくて実質的にWindows専用だったPDB形式を、クロスプラットフォーム向けに一新した

で、新形式csprojを使うと、デフォルトでこれらのオプションが使われるようになります。

出力されるexe, dll, pdbのファイル形式が変わるので、当然、古いアプリを使っている場合に問題を起こす場合があります。 例えば、ILSpyで逆コンパイルできなくなったりします (まあ、dnSpyなら読めるんで、移行してしまえばいいんですが)。

これで困った場合は、以下のようにDebugTypeタグ設定を入れて、旧PDBを出力するようにすれば解決します。

<Project Sdk="Microsoft.NET.Sdk">

  <PropertyGroup>
    <TargetFramework>net35</TargetFramework>
    <DebugType Condition="'$(Configuration)'=='DEBUG'">full</DebugType>
  </PropertyGroup>

</Project>

共有プロジェクト的なこと

共有プロジェクト(shproj)は、要するに、1つのファイルを複数のプロジェクトから参照して使う仕組みです。

shprojを新形式に変換(前節で説明したような新機能への対応)するのは結局できませんでした。

が、まあ、かつて共有プロジェクトでやっていたようなことは、新形式csprojなら別にSharedプロジェクトなしでできます。 要するに、共有したいC#ファイルが入っているフォルダーをワイルドカードで指定してやるだけ。

以下のようなCompileタグを書けば、「1段上のフォルダーの、別プロジェクトのフォルダー以下のすべてのC#ファイルをコンパイルの対象にする」ということができます。

<Project Sdk="Microsoft.NET.Sdk">

  <PropertyGroup>
    <TargetFramework>netstandard1.4</TargetFramework>
  </PropertyGroup>

  <ItemGroup>
    <Compile Include="..\ClassLibraryNet35\**\*.cs" Exclude="..\ClassLibraryNet35\obj\**\*.cs" />
  </ItemGroup>

</Project>

WPF/UWP

まあ、「.NET Standard向け」もしくは「.NET Core向け」だけテンプレートだけが新形式csprojになっていることからお察しの通り、 OutputTypeLibraryExeでないとちょっと苦労します。

WPFやWindows FormsのアプリはWinExe、UWPはAppContainerExeなんですが、この辺りのプロジェクトはデバッグ実行できなくなります。

ちなみに、WPFでビルドするところまではできることを確認済み。 以下のような設定が、WPFアプリをビルドするのに必要な最低限のものです。 (ビルドはできるんですが、デバッグ実行しようとすると「Unable to run your project」というエラーが出ます。)

<Project Sdk="Microsoft.NET.Sdk">

  <PropertyGroup>
    <OutputType>WinExe</OutputType>
    <TargetFramework>net47</TargetFramework>
    <LanguageTargets>$(MSBuildExtensionsPath)\$(VisualStudioVersion)\Bin\Microsoft.CSharp.targets</LanguageTargets>
  </PropertyGroup>

  <ItemGroup>
    <ApplicationDefinition Include="App.xaml" SubType="Designer" Generator="MSBuild:Compile" />
    <Page Include="**\*.xaml" Exclude="App.xaml" SubType="Designer" Generator="MSBuild:Compile" />
    <Compile Update="**\*.xaml.cs" SubType="Designer" DependentUpon="%(Filename)" />
  </ItemGroup>

  <ItemGroup>
    <Reference Include="System.Xaml" />
    <Reference Include="PresentationCore" />
    <Reference Include="PresentationFramework" />
    <Reference Include="WindowsBase" />
  </ItemGroup>

</Project>

実行できないのでアプリのプロジェクトに使うには苦しいんですが、例えば、「WPF向けコントロールやリソース ディクショナリ(XAML)を含むライブラリ」を作りたい場合であれば、新形式csprojを十分実用できます。

Visual Studio 2017 Update 3 (15.3) Preview

$
0
0

Build 2017で、Visual Studio 2017 Update 3の Preview 版が公開されました。 (ちなみに、Preview のつかない安定板 Visual Studio とは side by side の別フォルダーへのインストールになります。)

当初、.NET Core 2.0/.NET Standard 2.0 Preview がらみの更新かと思ったら、案外C#がらみも更新かかっていたことに今更気づいたり。 「The future of C#」とか「The future of Visual Studio」とかを見ていると、「C# 7.1」の文字が。

確かに、今見てみたら「C# 7.1」を使えるオプションがありました。

C# 7.1

現時点で実装されている C# 7.1 機能

資料を見てると、以下の2つがリストに並んではいるんですが。

試してみてる感じ、default expressions の方しか動いてなさげ。

default 式 (default expressions)

default(T)と書かなくても、左辺から型推論できる限りにはdefaultだけで既定値を与えられるという機能。 ちゃんと型推論なので、以下のように、マウス オーバーで型が出たりします。

default 式

名前が長い型のデフォルト引数とか作る場合にありがたそう。


void X(
    (int x, int y) t = default,
    KeyValuePair<string, int?> p = default,
    CancellationToken ct = default)
{
    ...
}

async Mainメソッド

Mainメソッドの戻り値が、TaskTask<int>でもよくなったという話。 もちろん、await を使うための仕様。

なんですが、なんか現状の Update 3 Preview (15.3 26510.0-Preview)ではダメでした。 普通に「Mainのシグネチャが不正」って言ってコンパイル エラーになりました。

何か他に条件いるのか、次の更新で来るのか…

その他、新しい IDE 機能

なんか、いくつか便利そうなQuick Actionが追加されてました。

引数生成

メソッドの引数を書くとき、型名まで打てば、それと同名の引数を作ってくれるCode-Fixが入ったみたいです。

確かに、型名と引数名が一致すること多いですもんね。

型名通りの引数名を自動的に挿入できる機能

今、仕事で書いてるライブラリ、大体CancellationTokenな引数の名前がctだわ… こういう略語はあんまりよくないと思いつつ、cancellationTokenって文字列が長すぎて心折れてて。 この機能を紹介するデモでまさにCancellationTokenを使っていて、みんな考えることは同じなのかとか思いました。

git 衝突解決

git の衝突解決するためのQuick Actionが入ってました。

確かに、git guiとかで衝突の解決しようとすると、「どっちがremoteだっけ… この操作であってるかな…」とか常に間違い、 結局 Visual Studio 上で手作業解決してたりしますし。

例えば、こんな衝突したとして。

<<<<<<< HEAD
class A { }
=======
class B { }
>>>>>>> branch

なるほど、こういうQuick Action出れば間違えないわ。

git 衝突解決

コンストラクター引数からのプロパティ生成

コンストラクターだけ書けば、対応するプロパティと、プロパティへの代入を生成してくれます。

コンストラクター引数からのプロパティ生成

RecordConstructorGeneratorもやっと退役かなぁ。

Viewing all 482 articles
Browse latest View live