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

Visual Studio 16.1.0 & 16.2 Preview 1

$
0
0

Visual Studio 16.1 のリリースと、16.2 の Preview 1 が来ていますね。

16.1

16.1 の方は、こないだの Preview 3からそんなに変わってなくて、割かし「リリースされました」という感じ。

C# 8.0 的には、

という感じ。

16.2 Preview 1

IDE 的には以下のようなものが増えてるみたいです。

  • プロジェクトの新規作成で、作成したいアプリのタイプで検索できるように
  • テスト エクスプローラーがだいぶ見やすく

あと、Developer PowerShell (開発ツールがらみの環境変数とかパスが通った状態の PowerShell)が追加されたみたいです。 これまでもあった Developer Command Prompt の PowerShell 版。

ちょっと C# コンパイラーに致命的なバグがありそうなので注意安全な stackallocを使うと不正なコードを生成して、プログラムが起動できなくなります。 (正確に言うと、stackalloc を書いたメソッドを呼んだ瞬間、InvalidProgram例外発生。) 修正済みっぽいんですけど、16.2 Preview 1 には反映されていない状態。

C# 8.0 的には、16.1 の方と以下の差があります。

非同期イテレーターの仕様変更

X(ct1).WithCancellation(ct2) みたいなのを書いたときの挙動が変わります。

using System.Collections.Generic;
using System.Runtime.CompilerServices;
using System.Threading;
using System.Threading.Tasks;
 
class Program
{
    static async Task Main()
    {
        var c1 = new CancellationTokenSource();
        var c2 = new CancellationTokenSource();
 
        // キャンセルなし
        await foreach (var x in X()) ;
 
        // AscynEnumerable 生成時に c1 が渡る
        await foreach (var x in X(c1.Token)) ;
 
        // GetAsyncEnumerator 時に c2 が渡る
        await foreach (var x in X().WithCancellation(c2.Token)) ;
 
        // 旧挙動: c2 だけが渡る
        // 新挙動: c1, c2 の両方が渡る。内部で CreateLinkedTokenSource
        await foreach (var x in X(c1.Token).WithCancellation(c2.Token)) ;
    }
 
    // 新挙動: EnumeratorCancellation 属性付きの引数は1個に限る
    static async IAsyncEnumerable<int> X([EnumeratorCancellation]CancellationToken ct = default)
    {
        await Task.Yield();
        yield break;
    }
}

base(T) 削除

base(T) アクセス、いったん取りやめになりました。 (書いた記事どうしよう… 消すか、「今後入る予定です」に変えるか…)

C# コンパイラーだけでできる実装方法だと不満だそうで、 .NET Core ランタイム側も合せて修正変更したいそうです。 結果的に C# 8.0 には間に合わず、ランタイム修正ありなものをマイナー リリースするとは思えないので 9.0 以降での実装になります。

stackalloc in nested expressions

式のど真ん中に stackalloc を書けるようになりました。

using System;
using System.Threading.Tasks;
 
class Program
{
    static int M(Span<int> span) => 0;
 
    static void Main()
    {
        // 引数にも書けたり
        M(stackalloc int[1]);
 
        // 式のどこにでも書ける
        if (stackalloc int[1] == stackalloc int[1]) { }
    }
 
    // フィールド初期化子内にも書けたり
    int x = M(stackalloc int[1]);
 
    static async Task Async()
    {
        // 式中に書くなら、非同期メソッド内でも stackalloc が書ける
        M(stackalloc int[1]);
 
        await Task.Yield();
    }
}

ぶっちゃけ、再帰パターンのついでだそうです。 再帰パターンの導入で参照として返せるものの判定が複雑になったらしく、 ちゃんとした判定に書き換えたらついでに stackalloc を書ける場所も増えたとのこと。


C# で、同じソースコードから常に同じバイナリを生成する

$
0
0

昔、gist にだけ置いてて、そういえばブログに書いてなかったものを思い出したので書いておくことに。

(一応、部分的には言及したことがあるんですけど、ちゃんとした話はしたことがなかったはず。)

決定論的ビルド

3年くらい前まで、C# コードをコンパイルすると、ソースコードを一切書き換えていなくても、生成結果の exe/dll や pdb のバイナリが変化していました(決定性(deteminism)がない)。

原因は以下の2つです。

  • バイナリ中に埋め込まれる GUID にタイムスタンプと乱数から生成される値を使っていた
  • デバッグ用のファイル情報がフルパスで埋め込まれていた

GUID の方はタイムスタンプと乱数なので本当に致命的で、ローカルで再コンパイルしても毎回バイナリが変化していました。

フルパスの方は基本的には pdb (デバッグ用シンボル情報)だけの問題なんですが、 exe/dll でも、CallerFilePath 属性を使ったりすると文字列定数でフルパスが埋め込まれます。 ローカルで再コンパイルする分には変化しないんですが、C:\users\ユーザー名\Documents\ みたいなのが pdb 中に残ってしまってみっともないです。 また、クラウド ビルド環境では常に同じパスでコンパイルされるとは限らないので、やっぱりバイナリの一意性を確保できなくなります。

バイナリが決定論的でないというのは単に不格好という問題ではなく、 CI/CD 環境でキャッシュが効かなくてなってサーバー リソースを食いつぶすことにつながったりします。

これらに対処するための C# コンパイラー(csc)オプションは、2016年頃から提供されるようになりました(参考: Deterministic builds in Roslyn)。

  • /deteministic: GUID の算出にタイムスタンプと乱数を使わなくする
    • ソースコードなどのハッシュ値から計算するので、ソースコードに変化がない限り一意
  • /pathmap: デバッグ情報などで使うファイルのパスを所定のルールで置き換え可能にする
    • 置き換え元=置き換え後 という形式で、単純な置換をするだけ
    • 置き換え元の方に $(MSBuildProjectDirectory) とかを指定して、置き換え先の方を/とかあたりさわりのない固定の文字列にしておけば、どの環境でも同じ結果が得られる

csproj での指定

C# コンパイラー オプションとしては3年前に実装されていても、 dotnet コマンドでのビルド(csproj ファイル中にオプション記述)できるようになったのはもうちょっと後です。 それでも、Visual Studio 2017 (2年前)から、以下のオプションが指定できるようになりました。

  • <Deterministic>: csc の /deteministic に相当
  • <PathMap>: csc の /pathmap に相当

ちなみに、SDK-based なプロジェクトの場合、 <Deterministic> がデフォルトで true になっています。 タイムスタンプ問題は何もしなくても解消済み。

問題は <PathMap> の方で、こちらは明示的に指定しないと今でもフルパスが入ります。

Directory.Build

ちなみに、<PathMap>Directory.Build.props に書いておいても動作します。 プロジェクト1個1個に設定を入れるのも面倒なので、自分はリポジトリのルートに1個、以下のような Directory.Build.props を入れています。

<Project>
 
  <PropertyGroup>
    <LangVersion>latest</LangVersion>
    <RepoRoot>$([System.IO.Path]::GetFullPath('$(MSBuildThisFileDirectory)..\'))</RepoRoot>
    <PathMap>$(RepoRoot)=.</PathMap>
  </PropertyGroup>
 
</Project>

$(MSBuildProjectDirectory) (csproj があるフォルダー)からではなく、 $(MSBuildThisFileDirectory) (props があるフォルダー)を基準にして、 リポジトリ内の相対パスは残すようにしています。

単に $(MSBuildThisFileDirectory) ではなく、その1つ上のフォルダーまで(git clone してきてるリポジトリ名まで)残すようにしています。

PathMap の例

<Deterministic> の方は一応過去に少し触れたことがあるのと、 SDK-based な csproj を使う限りもう今はデフォルトで true なので、 改めて例を挙げるほどでもないかと思います。 ということで、<PathMap> の方。

実行すると、CallerFilePath 属性で取ったファイル名と、例外のスタックトレースが書き換わってる(短くなってる)ことがわかります。

余談

2年くらい前の機能をなんで今更ブログに書くことになったかというと、 今日、ちょうど「ログ出力で CallerFilePath 使うか」という話になったからだったり。 「フルパス入るの嫌か」「あっ、でも、確か PathMap 導入してたはず」みたいな。

で、その PathMap 導入がいつかというと、このとき:

違う人がコンパイルするたびに差分が出て困ったところで、ネットで検索してみたら自分の gist が出てきたというよくあるやつ。

Visual Studio 16.2 Preview 2 & .NET Core 3.0 Preview 6

$
0
0

Visual Studio 16.2 Preview 2.NET Core 3.0 Preview 6 が来てますね。

Visual Studio 16.2 Preview 2 の方は、自分が気になったのだと、switchステートメントをswitchに書き換えてくれるリファクタリング機能が入ったとかあるみたいです。

.NET Core 3.0 Preview 6 は、パフォーマンス カウンターで GC とかスレッド周りの詳細な情報が取れるようになったり、AOT シナリオで「使ってなさそうなコードを消す」系の最適化が増えたり、HTTP/2 サポートが入ったみたいです。

WPFのオープンソース化(リポジトリができた時点ではスカスカだった)も完了したとのこと。

※追記: .NET Core 3.0 Preview 6 をインストールすると、WPF アプリ(の .NET Core 版)が英語ロケールでしか動かなくなるみたいです。ご注意を。

あと、null許容参照型に関連する属性が標準で入ったみたいです。 ただ、これは型として存在しているだけで、C# コンパイラー側が解釈できるようになるのは 16.2 Preview 3 以降随時みたいです。

C# 8.0 in 16.2 Preview 2

C# 的には、Preview 1の頃にあったやばいバグは治りました。 (stackallocを使っただけで不正な IL を生成して実行できなくなる問題。) これで今度こそ、気兼ねなく式中の stackallocを試せます。

あと、ひそかに今回から入ったのが、

です。 以下のようなやつ。一度デフォルト実装を持ったメソッドを、もう1度抽象メソッドに変えて、派生側での実装を必須に変える機能。

前述の通りnull許容参照型に関連する属性は .NET Core 3.0 側には入ったわけですが、 C#コンパイラーが対応し出すのは次の Preview 以降みたいです。 スケジュール感は以下のページに書かれている通り。

6/10 で終わっている作業はたぶん 16.2 Preview 3 で入ります。

ちなみに、以下のような issue もあります。

要するに、16.3 で C# 8.0 の preview が外れ、default が 8.0 になるという予告。 buildで「7月にRC、9月にGA」という話をしていたんで、それがそれぞれ 16.2 (で C# 8.0 も RC)、16.3 (で GA)ということなんだと思います。

Visual Studio 16.2 Preview 3、notnull 制約

$
0
0

Visual Studio 16.1.4Visual Studio 16.2 Preview 3が出たみたいです。

見た感じ、大半が不具合の修正っぽい雰囲気。

C# 的には、16.2.P3 マイルストーンの履歴的に、そろそろ null 許容参照型の作業が本格化していそうで、「C# によるプログラミング入門」の対応作業もそろそろやらなきゃ… と身構えていたんですが。 実際に 16.2 Preview 3 を触ってみると、あんまり入ってなさげ。Preview 4 に繰り越されたみたいです。

唯一、動作確認が取れたのが以下の機能。notnull 制約。

using System;
 
class C<T>
    where T : notnull
{
    public T Value { get; }
 
    public C(T value) => Value = value;
}
 
public class Program
{
    static void Main()
    {
        var c = new C<string>("");
        Console.WriteLine(c.Value.Length);
 
        var c1 = new C<int>(1);
        var c2 = new C<int?>(1); // 警告あり
        var c3 = new C<string?>(""); // 警告あり
        var c4 = new C<string>(null); // 警告あり
    }
}

これも、Preview 3 で対応するつもりがそんなになかったのか、 コンパイルはできるものの Visual Studio 上は未対応(コード補完もハイライトも効かない)な状態です。 (一方で、特にコンパイル エラーになったりはしない。)

notnull 制約

そういえば、この notnull 制約の話はあんまりこのブログで取り上げていなかったはず。 null 許容参照型周りはちょっと目を離すと結構実装が変わっているんで…

見たまんま、「この型引数は null を認めない」の意味です。 intstring は受け付けるけども、int?string? は受け付けない(警告のみですが)という型制約になります。 null 許容値型とnull 許容参照型を統一的に扱いたいがための仕様。

ただ、以下のようなコードは今のところ受け付けません。 notnull とは… (仕組み上しょうがなさそう。これを受け付けるためには .NET ランタイム側での対応が必要そうで結構な手間。)

class C<T>
    where T : notnull
{
    // せっかく notnull にしても、T? とは書けない。
    // [return: MaybeNull] という属性ベースの回避策を取る予定。
    public T? X() => default;
}

元々は、where T : object (object? がついてないんだから非 null 扱い)でいいんじゃないかって言われてたんですが、「object だと参照型っぽくて値型に使えなさそうな印象がある」という理由で新規キーワード追加になりました。

さらに言うと当初予定 では nonnull だったのが、今回の実装だと notnull (non と not の差)になっていたり。

C# ってあんまりこういう、2単語(not null)をつないだキーワードを採用することが少ないので、ちょっと最終的にもこのまま進むのかわからなかったりはします。荒れそう…

Visual Studio 16.2 GA と 16.3 Preview 1

$
0
0

一昨日、Visual Studio 2019 16.2 の Generally Available と 16.3 の Preview 1 が出ました。 あと、.NET Core 3.0 Preview 7 も出ました。

16.2 の機能:

  • テスト エクスプローラーの UI が見やすくなった

16.3 Preview 1 の機能:

  • 起動画面でのプロジェクト検索や、プロジェクト テンプレートの検索がしやすくなった
  • .NET Core 3.0 や C# 8.0 のサポート追加
    • LangVersion8.0preview にしなくても C# 8.0 が有効

.NET Core 3.0 Preview 7

  • Go Live (自己責任で、製品環境で使ってもいい状態)になった
  • インストール サイズ改善
    • インストーラーの状態で3割減、インストール後のディスク サイズで 75%減
    • Alpine の Docker イメージのサイズが 148MB に

.NET Core 3.0/C# 8.0

ようやく、.NET Core 3.0/C# 8.0 が最終形になってきました。 前述の通り、Visual Studio 16.3 ではもう C# 8.0 が default です。 LangVersionの明示は要らなくなりました。

.NET Core 3.0 が Go Live になったので、今後、Generally Available になるまであまり変化はないはずです。

素直に「もう完成してる」「バグ修正を除けばもう変化はない」と言えればすっきりするんですけどね… null 許容参照型がらみがどうもまだリリースされ切っていないようで。

(まあ、null 許容参照型の変化は文法には関係なくて、「C# コンパイラーが属性をどう扱うか」で「警告の有無が変わる」というものです。 一応、文法自体はもうさすがに今後 C# 8.0 の正式リリースまでに変化することはないと思います。)

null 許容参照型

ブログ的に Visual Studio 16.2 Preview 4 (7月19日に出てた)の話はすっ飛ばしちゃいましたが、 16.2 Preview 4 辺りでだいぶ null 許容参照型の実装は進んでいました。 ただ、ちゃんと実装されているようなされていないような…

例えば以下のようなコード。

using System;
using System.Diagnostics.CodeAnalysis;
 
public class Program
{
    static void Main()
    {
        // MaybeNull が付いているので、string だけど null が返ることがある
        var a = MaybeNull<string>();
        Console.WriteLine(a.Length); // 警告
 
        // NotNull が付いているので、string? だけど null は返ってこない
        var b = NotNull<string?>();
        Console.WriteLine(b.Length);
    }
 
    [return: MaybeNull]
    static T MaybeNull<T>() => default; // ただ、ここで警告出ちゃう(出ないのが正しいはず)
 
    [return: NotNull]
    static T NotNull<T>() where T : class? => default;
}

MabyNull/NotNull 属性、使う側は対応しているけど、メソッド定義側が対応していなかったり。 この辺りは Visual Studio 16.3 Preview 1 でも変化なし。

C# によるプログラミング入門に null 許容参照型のページを追加し始めてるんですけど、 まだリリースに含まれていない挙動が多くて書きづらい… (先週こっそり書き始めてこっそりもうページはあるんですけど、背景説明だけで力尽きてる。)

roslyn 上の issue からたどるに、 実装自体はあって、結構 merge 済みのものも多いんですけども。 まだリリースには反映されていないようです。

あと、今からマイルストーンを 3.0 に変えて大丈夫なの?という issueもあったり。 (型引数に対して属性を付けたい、付けれないと null 許容参照型で困るという話。)

ピックアップRoslyn 8/3: Records など

$
0
0

Design Notes が3件ほど。

そのうち2件は7/10, 17のもので、C# 8.0 の最後の詰めっぽい感じ。

残りは、7/22 のもので、C# 9.0 に向けて「今度こそ」という感じで Records の話。

7/10

議題は3点。

  • Empty switch statement
  • [DoesNotReturn]
  • params!

Empty switch statement

元々、switch式を式ステートメント(式1個だけ + ; でステートメントを作るやつ)で使えるようにしたいという話があります。 セットで、戻り値が void な式を switch 式使えるようにしたいという話もあり。

static void M(bool flag)
{
    static void a() { }
    static void b() { }
 
    // switch 式内で void なものを書けるようしたいという話あり。
    // (今 (少なくとも VS 16.3 Preview 1)は認められていない。)
    flag switch
    {
        true => a(),
        false => b(),
    };
}

(ちなみに、これの実装はまだなく、関連 issueを今見るとCompiler.Nextという謎のマイルストーンが付けられてた… C# 8.0 を目指してたけどスケジュール的に無理で次に回ったやつですね、たぶん。)

で、7/10 の議題的には、以下のような「空 switch 式」を認めるかどうか。

// 式ステートメントに出来る前提では、空 switch を禁止する十分な理由が見当たらない。
flag switch { };

意味のあるコードではないですけども、 まあ、わざわざ禁止する十分な理由が見当たらないとのこと。

DoesNotReturn

[DoesNotReturn] 属性は、null 許容参照型に関連する属性です。 以下のメソッドのように、そのメソッドを呼んだ時点でそこから後ろは絶対に呼ばれないということを表すもの。

// 例外を出すんで、このメソッドからは絶対に正常に戻ってこない。
[DoesNotReturn]
static void Throw() => throw new Exception();
 
// 永久ループしてるんで、このメソッドからも戻ってこない。
[DoesNotReturn]
static void InfiniteLoop() { while (true) ; }
 
static void M(int i)
{
    string? s = null;
 
    // 絶対に戻ってこない or 非 null な値の代入ありのどちらか。
    if (i == 1) Throw();
    else if(i == 2) InfiniteLoop();
    else { s = "abc"; }
 
    // ここに来た時点で絶対に s = "abc" を通ってるので、s は非 null。
    // 警告は出さなくていいはず。
    Console.WriteLine(s.Length);
}

null 許容参照型のために導入される属性ですが、 原理的には確実な初期化ルールに対しても使えるはずです。 7/10 の議題はこの点についてで、とりあえずこの属性は null チェックにしか使わないという決断。

汎用な reachability (到達可能かどうか。到達可能なら null チェック、確実な初期化チェックが必要)判定は将来改めて考える。 その際にはおそらく別の仕組みを使うとのこと。

param!

引数の後ろに ! を書くことで、その引数の実行時 null チェックを自動挿入したいという提案があります。

ちなみに、null 許容参照型はコンパイル時の静的なフロー解析で、実行時には何もしません。 静的にチェックしても、null 許容参照型導入前の古いコードや、#nullable disable なコンテキストで書いたコード、unsafe なコードから実行時に null が紛れ込むことがあります。 なので、実行時 null チェックの挿入にも需要が残っています。

機能自体はぜひ採用したいものの、詳細を詰め切れていない(いくつか問題がある)ので C# 8.0 には入れないとのこと。

例えば現状の文法案(引数の後ろに !) は、「式の後ろの !」と期待するものが真逆になるのでまずいです。これに対して、適切な文法を考えている余裕はもう C# 8.0 のスケジュールにはありません。

static void M(string param!)
{
    // (C# 8.0 に入れないことが決まった。)
    // param! と書くと、以下のコードがコンパイラーによって挿入される
    // if (param is null) throw new ArgumentNullException(nameof(param));
 
    // つまり、暗に、本来非 null なところに null が来うることを期待してる。
}
 
static void N(string? nullable)
{
    // (これは C# 8.0 に入る。)
    // null が来てても完全に無視。
    // null forgiven (null の罪に目をつむる) 演算子って言ったりする。
    string notnull = nullable!;
 
    // つまり、暗に、本来 null 許容なところに null が来ないことを期待してる。
}

7/17

こっちの議題は2つ。と言っても片方は triage (細々と3つ、機能を入れるかどうか検討)。

  • Nullability of events
  • Triage
    • Support XML doc comments on local functions
    • Warn on obsoleting overrides of non-obsolete members
    • Proposals for ConfigureAwait with context

Nullability of events

C# のイベント、特に自動イベントは2つの側面を持ちます。

  • 外から見て +=/-= できる (add/remove アクセサー)
  • 中から見てデリゲート呼び出しができる(同名のフィールドとして参照できる)

これに対して null チェックをどうするかという話。 例えば、以下のようなチェックの仕方をしてほしいという要求は十分にあります。

  • 外から見て、+=/-= に null を渡すとかは許容したくない。外から見ると非 null であってほしい
  • 中からの呼び出しに関しては、null が来ることを前提としたコードを書くことが一般的
    • イベント E に対して E?.Invoke(this, args) とか (null 許容な前提だから ?. を書く)

ということで、自動イベントに対するフロー解析で、アクセサーとフィールドを別扱いするべきかどうかというのが議題に。

結論的には、要求があることはわかるものの、イベントだけを特別扱いするのも混乱のもとなので、変なことはしないとのこと。

Triage 3件

  • ローカル関数に XML ドキュメント コメントを付けたい
    • 需要はわかるし、やりたい
    • でも、それを言ったらローカル関数だけじゃなくてローカルなもの全般(変数含む)にもドキュメント コメントの需要あって、合わせて考えたい
  • Obsoleteでないメンバーを、Obsolete 付きでオーバーライドしたときに警告を出すかどうか
    • あると便利
    • でも、やるなら warning waves (既存コードを壊すような警告を足せるように、オプション指定で警告度合いを増やす)を入れる段階でやる
  • ConfigureAwait 問題

7/22 (record V2)

records おさらい

records は、要は純粋なデータを表すような型のこと。 現状の C# で書くと、コンストラクター引数をプロパティでほぼ同じものを何度も繰り返し書かないといけなくてしんどいやつです。

class Records
{
    // public なプロパティでデータをまとめたいというのがこの手の型(レコード)の主目的。
    public int X { get; }
    public int Y { get; }
 
    // 目的外のところで、存外書かないといけないコードが多い。
    public Records(int x, int y) => (X, Y) = (x, y);
    public void Deconstruct(out int x, out int y) => (x, y) = (X, Y);
    public bool Equals(Records other) => (X, Y) == (other.X, other.Y);
    // その他、GetHashCode, == と !=, Equals(object) 等々…
}

うちのブログでも何度も何度も出ている話なのでずっと読んでくれている方ならわかると思いますが、初出は C# 6.0 の頃で、7 でも 8 でも流れて、9.0 で今度こそこれを主役にしたいという雰囲気。

records V1

最初に提案された records (今回、records V1 とか positional records とか呼ばれるようになっています)は概ね、以下のような書き方から、上記のようなクラスを生成する機能です。

data class Records(int X, int Y);

クラスの生成は、前述のような immutable (プロパティが get-only で、コンストラクターでの初期化が必須)なものになる(少なくともデフォルトではそうなる。get/set できるようにしたければ明示が必要にする)予定です。

こういう書き方自体はなくなったわけじゃなくて、これはこれで C# 9.0 のスコープに入っているんですが、問題もあって「V2」という別の書き方が提案されました。

問題は、V1 だとプロパティ初期化子が使えないこと。

class Records
{
    public int X { get; }
    public int Y { get; }
    public Records(int x, int y) => (X, Y) = (x, y);
    // 以下略
}
 
public class Program
{
    static void Main()
    {
        // こうは書ける。
        var r1 = new Records(1, 2);
 
        // こう書きたいけど、これが V1 だと無理。
        var r2 = new Records { X = 1, Y = 2 };
 
        // こんな感じで「部分書き換え」もしたい。
        // X は r2.X を引き継ぎつつ、Y だけ書き換えた新しいインスタンスを作りたい。
        var r3 = r2 with { Y = 3};
    }
}

records V2

そこで今回提案されているのが records V2 (nominal records) で、 以下のように、「initonly」なプロパティを定義できるようにするのはどうかというものです。

class Records
{
    public initonly int X { get; }
    public initonly int Y { get; }
}
 
public class Program
{
    static void Main()
    {
        // こう書けるようにする。
        var r2 = new Records { X = 1, Y = 2 };
        var r3 = r2 with { Y = 3};
    }
}

仕組み的には、以下のように、「readonlyが付いてるんだけどコンストラクター以外から書き換えられる set メソッドを用意」を考えているそうです。

class Records
{
    // public initonly int X { get; } に対して
 
    // コンパイラーは <Backing>_X みたいな C# では書けない名前でフィールドを生成してる。
    // ここでは、説明のために単に _X で書く。
    private int _X;
 
    // get アクセサーに相当するメソッド
    public int get_X() => _X;
 
    // set アクセサーに相当するメソッド
    [initonly]
    public void set_X(int x) => _X = x;
}

[initonly] 属性のところは、単なる(C# コンパイラーだけが使う)属性じゃなくて、 .NET ランタイムが解釈して特別扱いできる属性値(modreq)にしたいそうです。

現状の .NET の仕様では、こういう readonly なものの強制書き換えは unverifiable(安全性を検証できなくなる) だけど、unsafe ではないとのこと。 verifiable にするためにも、[initonly] が付いたメソッドには「コンストラクターの直後以外で呼べない」などの制限を掛けるという方針。

with (部分書き換え)については WithConstructor という特別なメソッドを用意して、それに対してコンストラクター同様の制限を掛ける方式を検討中とのこと。

現状の records 要約

3つに分けて考えるとよさそうです。

  • primary コンストラクター (positional records)
  • initonly プロパティ (nominal records)
  • data class/data struct

1つ目の primary コンストラクターは、以下のような書き方で、 コンストラクター(の引数)とプロパティを同時に定義する書き方。

class Records(int X);
   
// ↓解釈結果
 
class Records
{
    public int X { get; }
    public Records(int X) => this.X = X;
}

2つ目が今日話した initonly プロパティ。 immutable なデータ構造に対してプロパティ初期化子が使えるようにするもの。

class Records
{
    public initonly int X { get; }
}
 
// ↓解釈結果
 
class Records
{
    private int _X;
    public int get_X() => _X;
    [InitOnly]
    public void set_X(int x) => _X = x;
}

3つ目は、data class/data struct と書くことで、プロパティから EqualsGetHashCodeDeconstructなどの関連メソッドを自動生成する機能。 要は、これまでの提案と比べると、primarily コンストラクター が別機能として独立したことになります。 (キーワードは仮。data じゃなくて record キーワードになったりはするかも。)

// data が付く。
data class Records
{
    // 中身自体は既存の C# コード。
    public int X { get; set; }
    public int Y { get; set; }
}
 
// ↓解釈結果
 
class Records : IEquatable<Records>
{
    public int X { get; set; }
    public int Y { get; set; }
    public void Deconstruct(out int x, out int y) => (x, y) = (X, Y);
    public bool Equals(Records other) => (X, Y) == (other.X, other.Y);
    // その他、GetHashCode, == と !=, Equals(object) 等々…
}

primary コンストラクター を独立させたのと、今回 initonly プロパティを足したわけですが、 要するに、これらの混在もできる予定です。

// primary コンストラクターと、
data class Records(int X)
{
    // initonly プロパティと、
    public initonly int Y { get; }

    // 既存の普通のプロパティが混在。
    public int Z { get; set; }
 
    // data が付いてるので Equals とかがコンパイラー生成される。
    // (X, Y, Z から生成。)
}

Visual Studio 16.2.2 & 16.3 Preview 2

$
0
0

Visual Studio 2019 16.2.2 と 16.3 Preview 2 と、あと、 .NET Core 3.0 Preview 8 が出てますね。

16.2.2 は脆弱性の修正だけっぽいですかね、リリース ノートを見るに。 .NET Core 3.0 Preview 8 も「最終リリースに向けて磨いてるとこ」みたいなことを言っているので大きな変更はないはず。

16.3 Preview 2 はこまごまと結構追加が。

16.3 Preview 2

僕的に気になるのはまあ .NET/C# がらみくらいなわけですが、 それも今回はそこそこ差分あり。

IDE 機能

.NET Productivity」に書かれてる通り、

  • 1行内に大量にメソッドチェーンでつないでる奴を複数行にばらす整形
  • 先に new DateTime みたいなインスタンス生成を書いてから IDE 機能でローカル変数を導入

とかがあるみたいです。

あと、これは IDE 機能なのか C# 機能なのかどっちに分類していいのかわからないですけど、 doc コメント内で <em> とか <strong> とかのスタイル変更タグが使えるようになったみたいです。

doc コメントにスタイル反映

詳しくはこの PR を参照: Implement Quick Info styles #35667

target-typed switch 式

C# 8.0 の変更は大部分もうnull 許容参照型がらみだけ… という状況下において、1個だけ他の変更がありました。

switch 式でターゲットからの型推論が効くようになりました。 要するに以下のようなコードが 16.3 Preview 2 からコンパイルできるようになります。

class Program
{
    class Base { }
    class A : Base { }
    class B : Base { }
 
    static Base M(bool b) => b switch
    {
        // 条件ごとに型が違うので、これまでは switch 式の結果の型が確定できなかった。
        // ターゲット(この場合戻り値の Base)からの型推論で型を確定するようになった。
        true => new A(),
        _ => new B(),
    };
}

まあ元々「スケジュール的に厳しいけど C# 8.0 正式リリース時点で入れておかないと後からの変更は破壊的になるので避けたい」って言ってたやつです。 ほんとにぎりぎり間に合わせて来た感じ。

null 許容参照型がらみ

さらっと動作確認。

計画に上がってたもの大体一通り実装されてそうな雰囲気。 今度こそnull 許容参照型のページを埋めるの本気出さなきゃ…

ちなみに、一昨日書いたばかりの文章さっそく書き換えるというタイミングのよさ。

「この挙動で本当にいいの?」みたいなものもあったりはするんですが… 「vNext」マイルストーンが付いていたりするので、C# 8.0 時点ではあきらめていそうです。 例えば以下のようなやつ。

要するに、MaybeNull とかの属性は、外から見ると正しく働くものの、メソッドの中から見ると妥協的という感じ。 とりあえずどっちが重要かと言われると「外から」の方なので、重要なところだけは最低限実装したという。

ただ、DoesNotReturnIf が期待通り動いていないのはなんかおかしい感じが。 関連 Pull Request も通ってるので動作してそうなものなんですけども。 マージしたタイミングの問題ですかね…

ピックアップRoslyn 8/31: トリアージ祭り

$
0
0

.NET Core 3.0/C# 8.0 正式リリースがあるとされている .NET Conf (9月23日から)も近づいてきて、さすがに最近は C# 8.0 がらみの動きはないというか、そろそろその先を見た動きになってきた感じがします。

ということで、こういう時期恒例のトリアージ祭りが発生中。

(無言でマイルストーンを変えただけのやつも、GitHub の Sort 順序「Recently updated」で浮上するんですね。 「今週何やらトリアージしてそう」って感知してたのを、今調べてなおしてて大変助かってる。)

ちなみに、Records は「これまでと変わらず」で 9.0 マイルストーンです。 一応、C# 9.0 はこれが主軸になるはず。6.0 時代から案だけはあったのが延々先延ばされてきましたが、ついに。

優先度微ダウン(8.X → 9.0)

この辺りは優先度ダウンしているといっても、9.0 自体のリリースのタイミングの方自体に関心が。

とりあえずわかっていることとして、

  • build での発表によれば、
    • 来年から .NET Core は毎年11月の定期メジャー リリースになる
    • (今年末の3.1は安定として5以降) Long Term Support (LTS)は偶数番リリースのみ
  • C# のリリースは .NET と足並みをそろえたがってる

というのがありまして。 じゃあ、LTS が付いてない奇数番のとき、C# はどうするんだって話でして。 .NET 5 のタイミング、C# も 9.0 になって LTS なしなのか、このタイミングでは 8.X を出すのか…

自分の把握してる範囲ではどっちになるかわからず。どちらになるかによってだいぶ「マイルストーン 9.0」の意味が違いそう。

8.Xに残っているものの少なさとか、9.0 に移動されたものがどれも小さ目な機能だとか、「C# 年次リリース」も十分あり得るような気もしつつ。でも、昔、「文法が年1メジャー アップデートするのはやりすぎ」みたいなこと言ってたし、「C# にも LTS ないバージョンがあるの?」的なこともちょっと思ったりはしつつ。

優先度アップ(X.0 → 9.0)

優先度アップ(未分類 → 9.0)

これ、参照型に対する可変長スタック確保(stackalloc T[n])が GC にやさしくないから厳しいって言われてた気がするものの… 以下の関連する作業が .NET 5 マイルストーンで動いてるので整合性は取れてるのかな…

優先度微ダウン(9.0 → 10.0)

優先度微アップ(X.0 → 10.0)

優先度ダウン(9.0 → X.0)

優先度アップ?(未分類 → X.0)

優先度大ダウン(8.X → Any Time)

ちなみに後者の Unicode なんとかというやつ、 大昔から既知の問題なんですけども、今の C# コンパイラーはサロゲート ペアな文字を識別子として受け付けません。 サロゲート ペアでも、非常用漢字とかヒエログリフとか、 Unicode 文字カテゴリー的には使えていいはずの文字があるんですが、それが使えないのは「バグ」です。

で、まあ、「Any Time」マイルストーンっていうのは概ね「外部からのコントリビューション待ってます」という意味。 やるとしたら自分だろうなぁとか思っていたり。

昔、corefx に「GetUnicodeCategory(int codePoint)を public にしてほしい」と要望を出したのはこれのためでして。 このオーバーロードがないと C# にサロゲート ペア識別子を入れるのは厳しい(きれいで、かつ、パフォーマンスをあまり落とさない手段がない)。

こいつは .NET Core 2.1 で無事に入ったわけですが、C# コンパイラーは今のところターゲットが .NET Standard 2.0 なので、まだこのオーバーロードを使えません。

というかぶっちゃけ、自分が「サロゲート ペアな文字を識別子に使えなくて困ってる人見たことないよ。ジョークでヒエログリフ使いたいって言う人の方が多いくらい。昔勉強会で『欲しい人?』って聞いてみたことあるけど誰もいなかった。それでも使えるようにする pull request 出そうと思ったことがあるけど、GetUnicodeCategory(int codePoint) がないのがネックだった。」的な報告をしているので、「Any Time」になったの自体たぶん自分のせい。

リジェクト

新規(未分類)

今、「C# スクリプティング」が特別なモードとして存在していて、その中でだけトップ レベル(クラス、名前空間の外)にメンバー宣言を書ける状態です。

で、今までは「C# スクリプティング」の需要自体が低かったから特別モードの保守も平気だった(最悪、ちょっと通常の C# よりも遅れてもいい)ものの、 最近、Try .NETによって需要が急増していてどうしようかという感じ。

(ちなみに、なんか最近この Try .NET、jupyter対応してるんですよ、作業的に。)

なので、トップ レベル メンバーを、特別扱いではなく、通常の C# の仕様にも統合できないかという話になっています。 「Mainメソッドはおまじない」からの脱却も、言語を学び始めたばかりの人にやさしいですし。

まあ、マイルストーン未分類ですが。


Visual Studio 16.3 & 16.4 Preview 1

$
0
0

(日本時間だと)昨晩深夜、.NET Conf 2019がありましたね。

キーノートはなんかgRPC一色だった感じが…

要は、ASP.NET Core 3.0 の目玉の1つが gRPC 対応なんですけども。 それを、

  • proto ファイルから ASP.NET のサーバーを作るデモ
  • 同じ proto ファイルからクライアントコードを生成するデモ
  • WinForms とか WPF が .NET Core で使えるようになった → WinForms のデモでも生成した gRPC クライアントを利用
  • Xamarin にも、hot リロード、hot デプロイ機能が入る(プレビュー) → Xamarin のデモでも同じ gRPC クライアントを利用
  • Blazor のデモでも同じ gRPC
  • 生成した gRPC クライアントは C# 8.0 対応 → await foreach のデモに利用

という感じ。

ちなみに、2018年11月ごろから gRPC のチームと協力してフル managed な実装を頑張ったんですって

ところで、Unity だといつ使えるようになるんですかね…

Visual Studio 16.3 & 16.4 Preview 1

とりあえず、予告通り、.NET Core 3.0 が正式リリースされました。

予告通り。(build で発表した通りに行くか多少心配してた。)

伴って、Visual Studio も 16.3 になり、プレビューチャネルの方でも 16.4 Preview 1 が配信されました。

(今回は ja-jp のリンクを貼れて安心してる。機械翻訳なのにやたら反映が遅かったりするんで… 前よりだいぶ改善してるのかな。)

ちなみに、16.4 ですが、C# 的には単にバグ修正になりそう。 既知のバグは例えば、

みたいなやつです。

あと、ひそかに inheritdoc コメントに対応するみたい。

inheritdoc

こいつは、Sandcastleは昔から持ってる機能だったんですけど、Visual Studio 上のコード補完では出てこなかったやつです。 「派生クラスと同じ」しか doc コメントに書くことがないときに使うもの。 ついに補完候補に。

C# 8.0

まあ、いつも通り、「自分は RC の頃には触りつくしてるのでリリース時点では話すことがない」状態ではあります。

「 C# によるプログラミング入門」の C# 8.0 がらみは9割5分くらいは書けてるんですけども。 今回ちょっとリリースまでに書き損ねてる項目あったり。

残タスク: null 許容参照型こまごまとしたやつ、あと、非同期ストリームは利用例を足したい

とりあえず、正式リリースになったので使い放題! みんなー、もう Visual Studio 16.3 のインストールはしてくれたかな? 容赦なく C# 8.0 の機能を使ったコミット出すよ! とか思いながら 16.3 を触ってみていたんですが… なんか、以下のような感じで、微妙にまだ使えないかも…

  • TargetFramework が .NET Core 3.0、.NET Standard 2.1 のプロジェクトの場合普通に default で C# 8.0 になる
  • TargetFramework が .NET Core 2.1 とか 2.2、 .NET Standard 2.0 のプロジェクトは default が C# 7.3 のまま
    • LangVersion に latest を指定すると C# 8.0 になる

あれー… なんでこんな挙動なのかわからず。 単に、「古い SDK だとコンパイラーが更新されてない」というのも疑ってみてるんですけども。 それなら LangVersion は preview にしないと C# 8.0 にならないはずのような…

とりあえず、C# 7.3 の時も、 .NET Core SDK を更新したら 2.0 とか 1.6 でも C# 7.3 を使えるようになったので、しばらく様子見ですかね。 今の、 .NET Core SDK 2.1 と 2.2 は9月10日リリースのもののままのようなので。

ピックアップRoslyn 9/29: Utf8String など、C# 9.0 がらみ

$
0
0

C# 8.0 もリリースされたわけですし、 (リリース直前に 8.0 に関して修正も聞かないので、今月に入ったくらいから) C# 9.0 に向けた Design Meeting が開かれているみたいです。 .NET Conf 2019 も落ち着いたところで、議事録も公開。

雰囲気的には

  • .NET 5 向け
  • C# 9.0 を想定

みたいな感じなので、たぶん来年のリリースですね。 (.NET 5 は 2020年11月と明言されてる。 C# を .NET のリリースとどう同期させるかは明言されていないものの、 議事録の雰囲気からすると .NET 5 時点で C# 9.0。 .NET Core 3.1 (今年11月)時点で何か更新しそうな気配もなし。)

議題については

という感じです。

Design Notes とは別に、以下のドキュメントにも更新 Pull Request あり。

  • Native-sized integers proposal #2833
    • 内部的には IntPtrUIntPtr をそのまま使って、コンパイラーのレベルでだけ nintnuint という別の型に見せかける実装にするみたい

Utf8String

UTF-8 なバイト列を直接読み書きしたいという要望はかねてからあるというか、 C# で Web なことをするときに一番ネックになっていたのが UTF-8 (Web 上ではもうほぼこのエンコード)から UTF-16 (.NET の string の内部表現)への変換です。 なので、UTF-8 を直接読み書きするための型として Utf8String を追加したいという話は3・4年前くらいから出ていますし、プロトタイプ実装はすでにあります。 この型の正式リリースは .NET 5 にしたいということで、それに対する C# 言語上のサポートも C# 9.0 がターゲットになっています。

この Utf8String に関して、今回の議題は以下のようなもの。

  • リテラルを exe/dll にどう埋め込むか
    • いったんは既存の string リテラルの仕組みをそのまま使って UTF-16 の状態で記録して、ランタイムが読み込み時に UTF-8 変換する方式をとる
    • ただ、どう考えても UTF-8 データを生で埋め込める方が効率的なので、将来的にその方式に変更するときに困らないかだけは要確認
      • 今のところそういう内部的な変更が C# の破壊的変更になりそうなものは思いつかない(ので大丈夫)
  • 列挙をどうするか
    • UTF-8 な byte 列、Unicode コードポイント(あと、もしかしたら書記素クラスターも)をそれぞれ列挙するためのプロパティはある
    • (プロパティを介さず) Utf8String 自体を foreach に渡したいか?渡すなら何が列挙されるべきか?
      • プロパティを介さない列挙は認めない方が無難そう
  • C# 言語上の特殊対応は必要か。以下の2点は特殊対応する利点がありそう
    • Concat の最適化(n 個の文字列の Concat に対して O(n) 処理にする)
    • リテラル
  • (今の string リテラルと同じ) "" リテラルに対して Target-Typed な型判定をすべきか
    • 「UTF-8 リテラル」用の追加構文を用意しなくていいという利点はあるけども、オーバーロードで困る
      • M(string s)(M Utf8String s) があるとき M("literal") の解決ができない
      • 破壊的変更にならないようにするためには、string 優先にせざるを得ない
    • リテラルを Target-Typed にするのはやめた方がよさそう
    • u"" みたいな構文を足すことになる
  • キーワード
    • System.Stringstring という C# キーワードになってるのと同様、Utf8String に対して ustring などのキーワードがあった方がいいか?
    • u だけだと UTF-16 だって Unicode だって頭文字 u だから避けたいけど、他にもっと使いたい名前もない
    • リテラルとかで言語的な特殊対応をするんだから、キーワードを与えて primitive な地位を与えることはおかしくはない
    • まだ迷い気味

ローカル関数への属性適用

null 許容参照型とか非同期ストリームとか、ローカル関数でも使えて、かつ、属性を付けれないと困りそうな機能がすでにいくつかあります。 なので、ローカル関数への属性適用をできるようにすること自体は確定事項。

今回の懸念としては、ローカル関数は C# コンパイラーが何らかの通常のメソッドに変換するわけですが、 その変換結果から属性を取れるという保証をするかどうか。 現在のコンパイラー実装では、どこかしらには絶対属性が残るようになっているものの、どこからどうすればその属性を取れるか言語仕様で保証まではしない(という提案が今回あって、その方向で行くことに決定)とのこと。

Target-Typed 型推論

Target-Typed newは以前にもやる気になっていて、その時にデザインも終えてる。その頃から気がわかってないかだけ改めて確認。 やる気は変わらず、デザインのレビュー待ち状態に。

その他、Target-Typed な機能というと

  • switch (C# 8.0 で実装済み)は最初から Target-Typed な型推論を持ってる
  • switch でやるのなら」ということで ?? 演算子、[?: 演算子] (https://github.com/dotnet/csharplang/issues/2460)でも検討中
  • それとは別(ただし同じ課題に対する解決策)に、「Common Type 推論の改善」がある

Target-Typed 型推論と Common Type 推論の改善はどちらも、 flag ? 1 : null みたいな式の型決定(int? になってほしい。今はコンパイル エラーを起こす)に対する解決策ですが、 優先度を決めないと競合します。 M(int?)M(short?) みたいなのがあるときに、M(flag ? 1 : null) みたいなのがどちらのオーバーロードを呼ぶべきかが変わります。

Target-Typed 型推論の方が「ターゲットさえはっきりしていればいろんな型に対応できる」という利点があるものの、 「ターゲットがはっきりしない」ということも多いので Common Type 推論の方が使える場面は多くなります。

破壊的変更になることを避けるために、C# 8.0 の新機能の switch 式にだけ Target-Typed 型推論が入りました。 一方でこれから Common Type 推論も入れたいわけですが、switch 式とその他で違う挙動にせざるを得ないかもしれません。

Target-Typed 型推論に対する破壊的変更にならないように Common Type 推論を実現できないかは検討はしたい。 もし switch 式に対して破壊的変更が起きそうなら、その影響範囲は調査しておきたい(場合によっては破壊的変更を認めるかも)とのこと。

その他のトリアージ

ピックアップRoslyn 10/4: C# 9.0, パターン追加、switch 式ステートメント、共変戻り値

$
0
0

何件か、C# 9.0 向けに提案されている機能のドラフト仕様が出てきました。

パターン マッチ

C# 8.0 でもずいぶんとパターンが増えましたが、9.0 でも追加が出そうです。

  • 複数のパターンを andor でつないだり、!(x is pattern) と書かなくても x is not pattern と書けるようにしたり
  • andor があるなら優先度を付けるために、パターンを () で囲えるようにしたり
  • x is >= min and <= max みたいに、比較パターンを入れたり

switch 式を式ステートメントに

メソッド呼び出しなど、いくつかの式は、式を単体で M(); みたいに書いてステートメント化できます。(こういうのを「式ステートメントと言います。)

switchでも、以下のような書き方に需要があるので、式ステートメント化をしたいという話は前々からあります。

static void A() => Console.WriteLine("A");
static void B() => Console.WriteLine("B");
static void C() => Console.WriteLine("C");
 
static void M(bool? state)
{
    state switch
    {
        true => A(),
        false => B(),
        null => C(),
    };
}

C# 8.0 には間に合わなかったので、9.0 での提案に。

共変戻り値

これは要するに以下のような奴。

class Base { }
class Derived : Base { }
 
class A
{
    public virtual Base M() => null;
}
 
class B
{
    // 戻り値が Base じゃなくて Derived。
    // 原理的には問題ないはずだけど、今までの .NET ではできなかった。
    public override Derived M() => null;
}

これはずっと「C# 上の構文糖衣ではなく、ランタイムに手を入れた方がいいので難しめ」ということでなかなか手付かずだったやつです。

C# 上の構文糖衣で何とかごまかせないかという検討もしていたんですが、 結局、ランタイム(.NET の型システム自体)の修正込みでやろいうという流れになっています。

インターフェイスのデフォルト実装に続く2例目の「ランタイムを選ぶ新機能」になります。

.NET Core 3.0 をもって .NET Framework からの移植作業は完結

$
0
0

corefx で以下のようなアナウンスが。

buildの時点で .NET を .NET Core ベースに一本化、.NET Framework は 4.8 をもって最後にするという話があったわけですが、 改めてというか、総括的なアナウンスです。

API 数

まず、.NET Framework から .NET Core に移植してきた API 数の総括。 メソッドのオーバーロード1個1個を「1 API」とカウントしてるんだともいますが、以下のような数字が書かれています。

  • .NET Core 1.0 時点では1.8万個
  • .NET Standard 2.0 では .NET Framework、.NET Core、Xamarin の共通部分として3.8万個
  • Windows 限定な機能も Windows Compatibility Pack として提供して、これが2.1万個
    • .NET Standard 2.0 と合わせて6万個
  • .NET Core 3.0 では WPF と WinForms を移植して、これが12万個
    • .NET Framework の API の約半分
  • 依然として6.2万個の API が未移植なものの、8割の API が移植済み

移植しないもの

これも build の時点でアナウンス済みですが、AppDomain、remoting、Web Forms、WCF、Workflow は .NET Core への移植をしません。

この辺りはレガシー扱いで、 モダンなアプリ開発に必要なテクノロジーは .NET Core 3.0 をもって一通りそろったといえる段階に達したと判断されています。 今後はもう、レガシー移植よりも、新しいテクノロジーに開発リソースを割いていきます。

.NET Framework のコード自体はMicrosoft .NET Reference Source として MIT ライセンスで GitHub 上にあるので、 Microsoft が保守を止めてしまった部分も、コミュニティ ベースで保守していくことはできます。 (実際、Core WFCore WCFなど、いくつかのコミュニティ プロジェクトがすでにあります。)

ちなみに、Reference Sourceは本当にソースコードの公開のみで、ビルド基盤をもう保守していないので、 ビルドして動くものが欲しければCore WCFなどのコミュニティ プロジェクトを参照(なければ立ち上げ)してほしいそうです。

移植の要望 issue を close

「このテクノロジーを移植してほしい」という要望 issue があって、 これまで「どれを優先的に移植するか」の判断材料として使ってきました。 しかし、.NET Core 3.0 をもってもう移植作業は打ち切りということで、issue も一通り close したそうです。

ただ、打ち切ったといってもあくまで WCF であるとか Web Forms であるとかのテクノロジー単位での話で、 例えば「移植済みのクラスのこのオーバーロードがないみたいだけども、あった方が便利じゃない?」くらいのものであれば追加される可能性はあります。

「.NET Framework にあるから」という理由だけで .NET Core に追加されることはなくなりますが、同時に、「.NET Framework にある(けどまだ .NET Core にない)から」というだけで絶対にその API が移植されないということもありません。

ピックアップRoslyn 10/25: Records、Static lambdas

$
0
0

Design Meeting でちょっと Records がらみの話があったみたいです。 あと、ついでのように「static ラムダ式」の話。

Records

Records がらみは何か新アイディアが出たわけではなくて、直近、何から手を付けるか的な意思決定っぽいです。 8月にブログに書いた通り、元々 Records と呼ばれていた機能は今はいくつかの小さな機能に分割する流れになっています。

  • Automatic structural equality
    • 「メンバーごとの等値判定」で Equals メソッドを自動生成
    • 今出てる案では data class DataType { ... } みたいに data 修飾子を付けると Equals の自動生成をするようになる
    • キーワードは変更の可能性あり
  • With-er
    • obj with { X = newValue } みたいな書き方で一部のメンバーだけ値を差し替える構文
    • with で使う用の特別なコンストラクター」みたいなのを用意しておく想定
  • Object initializers for readonly
    • get-only なプロパティ Xnew DataType { X = 1 } みたいな初期化子で上書きできるようにする案
    • プロパティに initonly 修飾を付けることでこの初期化を有効化
    • (今はコンストラクター内での初期化が必須なので、コンストラクター引数にする必要あり)
    • 「初期化子を呼ぶタイミングまでは書き換え可能で、その後は読み取り専用」みたいな仕組みを追加予定
  • Primary constructors + deconstruction
    • class DataType(int X, int Y); みたいな書き方からプロパティと、その初期化・分解(コンストラクター、Deconstructメソッド)を自動生成

それぞれの機能が後々衝突しないように考えながら作らないといけないものの、 一応は個別に実装して行けるようには分割してあります。

いずれも、原則として、コンパイラーが自動生成しなくてもやろうと思えばユーザーが手書きできる範疇の機能にとどめたいそうです。

また、今回のミーティングでは、まず割かし仕様が固まってきてる primary constructors と structural equality の2つから実装していきたいという話をしています。

init-only

init-only (前節の Object initializers for readonly のこと)の懸念点についても軽くディスカッションがあったそうです。

基本的には、initonly T X { get; } みたいに書いたとき、実際にはプロパティ Xset も生成して、それを呼べる場所をコンストラクター内と初期化子だけに制限するという仕様。

懸念は以下のようなもの。

  • 基底クラスのコンストラクターでの初期化
    • 今のところ特にそれを禁止する理由は思い当たらない
  • required (初期化が必須)のメンバー
    • null 許容参照型があったりするわけで、未初期化(自動的に null になる)のはできれば避けたい
    • ただ、「初期化子での初期化をしていないとエラー」ってやってしまうと、後からプロパティを追加したときに既存コードを壊す
    • 特に、initonly は C# ソースコードのレベルでの機能になる予定で、コンパイル済みのメタデータには反映されない予定
      • 後から required な initonly プロパティを足しても、以前からその型を使っているコードは未初期化のまま素通しになってしまう

static ラムダ式

C# 8.0 で静的ローカル関数が入ったわけですが、ラムダ式にも同じ制限が考えられます。

static 修飾子を付けることで外の変数・引数をキャプチャできなくする機能で、 パフォーマンスへの配慮です。 キャプチャが発生してしまうと発生してないときよりもちょっとパフォーマンスが落ちるので、 意図せずキャプチャしてしまうことを避けるために、 キャプチャの必要がないならこの修飾子を付けておけという機能。

というか、C# 8.0 の時点でも「ラムダ式でも同様に」という話はあったんですが、 スケジュールの都合でお蔵入りしていました。 それが改めて検討に上がった状態。

検討に上がったというか、最近、すでにプロトタイプ実装があったり。 元々ラムダ式に async 修飾子を付けれるわけで、追加で static を認めるのはそんなに難しくない様子。

ミーティングで出た議題としては、 static を付けたラムダ式は、生成結果的に静的メソッドであるべきかどうかという点。 詳しくは「デリゲートの内部」で書いたことがありますが、 実のところ、デリゲートとして使う場合、静的メソッドよりもインスタンス メソッドの方が高速だったりします。 なので、static を付けたラムダ式であっても、実体としてはインスタンス メソッドに「なってもよい」という余地を残す方が最適化が掛けやすいです。

ということで、仕様としては「static キーワードを付けても必ずしも静的メソッドが生成されるわけではない」ということにしておきたいそうです。

ピックアップRoslyn 10/9: base(T), UTF-8 String, Discard parameters

$
0
0

数日前、いくつかの新機能について、仕様書のドラフト案が上がっていました。

どちらも、これまであった Design Meeting の議事録通りな感じ。

あと、ちょこっと変更が検討されて、結局元さやに納まったものが1件。

base(T)

これは、C# によるプログラミング入門に説明を書いた直後に「やっぱり C# 8.0 ではやめておく」となってしまったやつ。 (しょうがないんで「C# 8.0 から外れました」って書き足してそのまま残してあったり。)

まあ、.NET ランタイムのレベルで対応してもらう予定だそうです。 base(T).M() と書いたとき、T 自体に M の実装がなくても基底クラスをたどって最初に見つかった M を呼んでもらえるという仕様。

UTF-8 String Literals

待望の。

といっても、Utf8String 自体についてはまだいくつか悩ましいポイントがあり…

  • string (今は UTF-16)自体を UTF-8 に切り替えるオプションがやっぱりほしい
    • とはいえ、インデクサー(UTF-16 での i 文字目を s[i] で定数時間アクセスできる)を期待しているコードが壊れる
    • unsafe に fixed (char* p = s) しているコードも壊れる
  • System.String に対して C# キーワードの string があるけど Utf8String に対してはどうするべきか
    • キーワード足さない?
    • ustring
    • utf8
  • クラス名自体まだ悩ましい
    • 今回のドラフト案も冒頭に「corert 側が今のところその名前になってるのでそれに従う」との注釈がまだ必要な段階

というのもあって、C# vNext で検討されているのは本当に「手始め」という感じのものだけ。

  • (今ある) 文字列リテラルをそのまま使う
  • 以下のようなルールだけ C# に追加
    • 文字列リテラルから Utf8String への暗黙の型変換を認める
    • Utf8Stringconst にできて、それに渡せるのは const な文字列のみ
    • + 演算子は Utf8String.Concat として解釈する
  • コンパイル結果としても string (UTF-16) のままプログラムにデータを埋め込む
    • 読み込み時に .NET ランタイム組み込みのヘルパー関数を呼んで UTF-8 に変換する
    • 将来的には直接 UTF-8 なデータをプログラムに埋め込めるように改修する可能性はあり

まあ、Utf8String クラスが標準ライブラリに入ってくれるだけでも随分助かりはするんですが… 既存のコードベースが string だらけなのがだいぶやっぱりネックになりそうな感じ。

Discard parameters

この issue の趣旨自体は、Action<T, T> a = (_. _) => { }; みたいな書き方を認めたいというもの。 2個以上の引数が _ の時、それはdiscard扱いにします。 ラムダ式の中で _ を普通の変数のように触ろうとするとコンパイル エラー。 一方で、1引数の _ は今現在有効は引数名として使えてしまっているので、破壊的変更を避けるために今のまま。

で、ここ数日で、一瞬、「ラムダ式以外、普通のメソッドの引数やローカル関数にも適用してもいいんじゃないか」というのが議題に上がりました。 でも、名前付き引数がある以上、引数の名前は API の一部分であり、メソッドの外から見えてしまう情報になります。 そこを省略するのはあんまりお行儀がよくない。

なので結局、当初予定通り匿名関数(ラムダ式と匿名メソッド式)でだけ、_ のdiscard扱いをしたいという結論に。

ピックアップRoslyn 11/16: Discriminated Union, Enhancing Common Type, Type pattern, Interpolated String Const など

$
0
0

10月末のと、今週の Desing Notes が3件ほど。

なんか結構一気に、C# 9.0向けと思われる議題が上がっています。

Function pointer syntax

C# で関数ポインター的なものというとデリゲートなわけですけども、 こいつはクラスになっていて Managed なものです。 で、.NET の IL 仕様上は、デリゲートの他に生の関数ポインターもあったりします。 基本的にはネイティブ コードとの相互運用のためにあるもので、これまでは C# から直接使う方法はありませんでした。

関数ポインターを C# あらも直接触れるようにしようという話は前々から上がっていて、C# 9.0 向けの機能として作業も始まっていました。 今回は、作業を始めてみたら元々考えていた構文だと問題があったので変えたいという話です。

元々の案だと以下のような構文を考えていたんですが、これだと他の構文と不明瞭で、結構先までソースコードを先読みしないといけないのが負担になりすぎるとのこと。

func*(int, int)

なので、以下のような構文にしたいとのこと。 既存の文法では *< が連続することがないので、これなら構文解析が楽というのが主な理由。

func*<void>

あと、この機能はネイティブ相互運用のためのものなので、関数の呼び出し規約 (引数や戻り値をメモリ上やレジスター中にどう並べて受け渡しするか) の指定の仕方をどうするかも今回の議題に。

Enhancing Common Type Specification

ここでいう Common Type (共通型)っていうのは、条件演算子配列初期化子'switch` 式などで、異なる型が同列に並んでいるときに、結果の型をどうするかという話です。

特に要望として大きいのが整数と null を混在させたときに int? などとして扱ってほしいというもの。

// 以下のいずれも int? になってほしいけど、現状はコンパイル エラーに
var x = b ? 1 : null;
var y = b switch { true => 1, false => null };
var z = new[] { 1, null };

他にも、共通の基底クラスから派生している型 A : BaseB : Base があったとき、これらを並べたら Base として扱ってほしいという話もあります。 で、C# 9.0 では、ちゃんと 1 と null の共通型を int? と認識できるようにするつもりで作業が進んでいます。

この問題に対する解決策としては「Common Type」の他に、Target-Typed 型推論というものもあります。 C# 8.0 で入りたての swtich 式だけはこの Target-Typed 型推論を行っていて、たとえば以下のコードはコンパイル可能です。

// switch 式に限り、以下の書き方はコンパイルできる。
// 変数の型から推論(terget-typed 型推論)
int? targetTyped = b switch { true => 1, false => null };

で、今上がっている議題は以下のような感じ。

  • Common Type 解決を改善するのは決定事項
  • Target-Typed 型推論の方と衝突するけども…
    • Common Type 解決の方を優先したい
    • Common Type 解決に失敗した場合のフォールバックとして、条件演算子や配列初期化子にも Target-Typed 型推論を入れたい
    • すでに Target-Typed 型推論を持っている switch 式は破壊的変更になる
      • switch 式だけ「Target-Typed 型推論を優先して、フォールバック先を Common Type 解決にする」という選択肢もなくはないものの、それはそれで混乱のもと
      • このくらいの破壊的変更は許容したい(上記混乱よりはマシ)

[MaybeNull]T

C# 8.0 だと、以下のようなコードを書くと null 警告が出ます。

#nullable enable
[return: MaybeNull]
T M<T>() => default;

MaybeNull 属性を付けると「たとえ非 null であるとされる型であっても null 許容に上書き」みたいな挙動になるものです。 ただ、現状、この属性は「メソッドの外向け」にしか機能していなくて、 メソッドの内側、この例でいうと => default の部分に対しては効力が及んでいません。

現状これは「仕様」なんですが、頻繁に「バグ報告」を受けています。 好ましい挙動でもないので「積みタスク」にはなっているんですが、C# 9.0 で直す方向で動いています。

ちなみに、構文解析上の問題じゃなくて、フロー解析に新しいステートを足さないとダメな模様。 MaybeNullEvenIfNotNullableですって…

こんな感じなので、「他の属性も全部一斉に対応」ってのは実は難しいらしく、とりあえず MaybeNull だけ対応する(NotNullIfNotNull とかは 9.0 でも相変わらず default が警告を起こす)方向で考えるそうです。

Allow interpolated string constant

const な文字列リテラルだけを使って文字列補間をする場合、 その結果も const 扱いできるようにしようという話。

const string A = "abc";
const string B = $"{A}123"; // 今はエラーに。これを認めたい。

ちなみに、数値の場合は書式によってはカルチャーの影響を受ける(小数点がコンマだったり、3桁区切りがピリオドだったりする言語が結構ある)ので、 {} 内に書けるのは const string だけになりそう。

Type patterns

is 演算子 の場合は x is Type とか書けるのに、 'switch` 式 とかのパターンの場合は x switch { Type => ... } とは書けないのが思った以上にストレスなので「このパターン足すわ」という話。

今だと、宣言パターンを使えば同じようなことはできます。 x switch { Type _ => ... } というように、型名の後ろに discard を付ければいいんですが、これも結構ストレスになるということで、 単に Type だけを書けばいい「型パターン」を追加したいとのこと。

今までこれができなかった理由は、型パターンと定数パターンが競合するからで、優先度を決めないといけません。 例えば以下のように、型名にも X があって、定数にも X があるとき、パターン中の X はどちらになるかという話。

class X { }
class A
{
    const int X = 0;

    int M(object x) => x switch
    {
        X => 0,
        _ => 1,
    };
}

既存コードを壊さないようにするには以下のようにするしかなく、ちょっと残念感はあるものの、これで行くことになりそう。

  • is では型優先
  • switch では定数優先

Name lookup with target type

Target-Typed な推論は、型の決定だけじゃなくて、メンバー名のルックアップにも使えるんじゃないかという話。 要するに、以下のようなコードを認めるようにしようという話です。

enum E { A, B }

class Program
{
    int M(E e) => e switch
    {
        A => 1, // 今だと、E.A と書かないとダメ
        B => 2, // 同じく、E.B
        _ => 3,
    };
}

enum だけじゃなくて、クラスの静的メンバーのルックアップでも同様。 特に、同じく C# 9.0 で Discriminated Union (後述)も考えているので、そのためにもこの機能は役立ちそうとのこと。

Discriminated Union

F# に同名の機能があるやつ。 他の言語で言うと Ether 型とか oneofとかがありますけど、 要するに「いくつかの型のうちのいずれか」みたいなやつです。

「いずれかの型」は、オブジェクト指向言語としては利便性を抜きにすれば単に「派生クラス」でできたりするんですが。

// Base 型は A もしくは B のいずれかである
class Base { }
class A : Base { }
class B : Base { }

問題は2つ

  • 単純に書くのが煩雑
  • 網羅性 (A もしくは B 「だけ」を保証したい)が取れない

煩雑さに関してはそもそもクラス自体が煩雑というのがあり、 その解決のために C# 9.0 で検討されているのがRecordsになります。

ということで、その Records に合わせた文法で、網羅性も考慮に入れた構文として、以下のような案が出ています。 enum class ですって。

enum class Shape
{
    Rectangle(float Width, float Length),
    Circle(float Radius),
}

ちなみに、以下のような感じで解釈されます。 (data class は Records で提案されている構文。)

partial abstract class Shape
{
    public data class Rectangle(float Width, float Length) : Shape,
    public data class Circle(float Radius) : Shape
}

まあ、ほぼ F# の Discriminated Union 総統の機能です。

「構造体な Discriminated Union が欲しい」みたいな話もあって、検討には上がってるんですが、 とりあえず C# 9.0 のスケジュールではクラスだけを考えているみたいです。

ベースが Records なので、普通のクラスのメンバーを追加で差し込んだりもできるみたいです。


ピックアップRoslyn: C# 9.0での、パターン、records、switch、null チェックの改善

$
0
0

この1週間で C# Design Notes が4件立て続けにアップロードされました。

あと、1件、提案ドキュメント追加:

Nov. 18: パターン マッチ

C# 9.0 でいくつか「パターン」の追加が検討されています。

  • パターンの組み合わせ: not, and, or とかで、パターンの組み合わせたり条件を反転させたり
  • 括弧つきパターン: x is (pattern) みたいなやつ。and とかの優先順位の明確化用(x is (pattern1) and (pattern2) とか)
  • 関係パターン: x is < 3 みたいな感じで大小比較

Nov. 18 の Design Meeting ではこの辺りに関する検討があったみたいです。

  • 括弧つきパターンは 1-tuple (1要素のタプル パターン)と区別がつくか
    • 1-tuple の方を x is (Item1: var tupleVal) みたいに名前(この例でいうと Item1)必須にすれば区別はつく
  • 関係パターン
    • 正直、構文として `x is >= 3 and <= 5' みたいなのはきもい
      • 2項演算子を単項的に使う構文になれてなさすぎる
    • でも、x is int 3..5 みたいな「range パターン」と違って「両端を含むかどうか」で悩む必要がない明瞭さがある
    • ユーザー定義演算子をどうするかという問題もあり
      • x is null みたいな「定数パターン」ではユーザー定義演算子は呼ばれない
      • でも、x is < 3x < 3 で挙動が違うのはいいのか
    • x is < 3 とかは暗黙的に x is int i && i < 3 みたいな型チェックが含まれる
      • x is not <3x is >= 3 が同じ意味にならない
  • パターンの組み合わせ
    • 今の提案では、今後、パターン中での not, and,or` がキーワードになる
    • これは既存のコードを壊す可能性がある
    • どこまでこの breaking change を許容できるものか要検討

Dec. 11: Records

key 修飾(records の等価比較)

Dec. 11 は Records がらみの検討があったみたいです。 以前も書いた通り、「Records」と呼ばれる機能は独立した小さな機能に分解して実装が検討されています。 この日は、等価比較 (Equals メソッドや == 演算子)の生成に関して、key 修飾子を追加しようかという話です。

以下のように、key 修飾子を付けることで、そのメンバーの比較によってクラス自体の等価性を判定(する Equals メソッドなどを生成)しようという話。

class C
{
    public key string Item1 { get; }
    public string Item2 { get; }
}

検討事項としては、これ単体では Records 的な機能としては足りていない(コンストラクターを書かなきゃいけないようでは「何度も同じ名前を書かないといけなくてつらい」問題の対処にならない)とか、 クラスの場合に mutable なプロパティで Equals を実装してしまうと Dictionary/HashSet の動作を狂わせてしまうので避けたいとか、 そういう問題にどう取り組もうかという感じ。 構文がこれでいいか(`key' を使うかとか、どうつけるかとか)は置いておいて、議題としては前向きに取り組みたいという様子。

nominal vs. positional

nominal っていうのは new C { Item1 = 1 } みたいなやつで、positional っていうのは new C(1) みたいなやつのこと。 8月頃に原案が出ていたので紹介していますが、 それぞれ init-only、primary constructor という提案が出ています。

今回はこれら再検討というか、それぞれバラバラに提供できるか、混ぜて使って使用感はどうか、 「primary constructor は既存の戦略(普通にコンストラクター呼び出し)の延長線上なのでいいが、init-only は大丈夫か」とかそういう話でした。

Dec. 16: switch

Dec. 16 は switch の改善が議題に。

旧来の C# の switch ステートメントは、C++ の影響を色濃く受けていて、使い勝手的にいまいちだとよく言われています。 それに対して、C# 8.0 では switchを導入したわけですが、 C# 8.0 時点で未解決の問題として、戻り値がない場合をどうするかというものがありました。

単に、switch 式の各枝に void なものを認めようという提案も出ていましたが、「末尾に ; が必須になるのがちょっと嫌」とのこと。

代わりに、2つ提案が出ています。

トリアージ

Dec. 16 ではその他ちょこっとトリアージがあったみたいです。

  • private なフィールドに対する確実な初期化判定
    • 先に warning waves (警告出すかどうかの選択権)が必要。それ以降ならいつでも
  • catch 付きの try ブロック内に yield を書けない問題
    • 今もうこの制限がある意味はあまりない(await 導入時に解決済み)ので緩和したい
    • いつでも
    • やるなら合わせて非同期イテレーターでもやる
  • generic operators
    • ジェネリクスがらみは「型推論に失敗するときでも、型の明示で解決できる」状態にあってほしい
      • M(x) で型解決できない書けないときに、M<T>(x) と書けばいいみたいな
    • 演算子の場合、これに対するいい解決案が思い浮かばないのでリジェクト
  • 引数に対する nameof
    • 引数のスコープは「メソッド内」なので、メソッドに付けてる属性中で参照できない問題がある
      • [return: NotNullIfNotNull(nameof(x)] T M(T x) みたいなのが書けない
    • 認めたい。いつでも

Dec.18: nullable issue

Dec. 18 は null 許容参照型がらみ。

pure null チェック

パターン マッチで、以下のような書き方は暗黙的に null チェックを含みます。

  • x is { }
  • x is object

x is nullx == null と書くとそれ以降は「x は null ではない」判定が働くのに、これら x is { }x is object などでは現状は判定が通っていません。 これらも null 判定に含めたいとのこと。

var?

型推論はしたいけど、null 許容性だけを変えたいということがあります。

var current = myLinkedList.Head; // 連結リストの先頭とかで、not-null 指定あり
while (current is object)
{
    ...
    current = current.Next; // not-null と推論された current に null が渡る可能性あり。警告
}

こういう時のために、var? という書き方を導入したらどうかという案が以前に検討されています。 それを再検討。

Unconstrained type parameter annotation

null 許容参照型で一番困るのが制約なしのジェネリクスです。 LINQ の FirstOrDefault みたいなやつの戻り値をどう扱うかで、そのために T?? という記法を導入したいとのこと。

  • ジェネリック型引数にだけ付けれる
  • 意味としては default を返す/受け付ける
    • 結果、参照型に対しては null があり得て、非 null 値型には null があり得ない

ピックアップRoslyn 1/13: null 許容参照型改善、式ブロック

$
0
0

1件、Design Note 追加。

C# 9.0 に向けた null 許容参照型の改善と、「式ブロック」の話。

null 許容参照型

属性のメソッド内への適用

C# 8.0 だと、以下のような感じのコードの null 警告は ! 演算子 で無視する以外に消す方法がありません。

bool TryGetValue<T>([NotNullWhen(true)]out T t) where T: class
{
    t = null; // 今、警告が出る
    return false;
}
[return: MaybeNull]
T GetFirstOrDefault<T>() where T : class
{
    return null; // 今、警告が出る
}

C# 8.0 ではスケジュール都合で放置(属性をメソッドの中にまで反映させるのは結構大変&これを使う人(= ライブラリ作者側 << 利用側)は少ない)されてたやつです。 機能要望どころか、バグだと思われてバグ報告がたびたび入ります(重複 issue がたくさんある)し、9.0 で再検討。 今回の Design Meeting でも「有益度合いの調査しないとね。有益そうなら実装したい」みたいな雰囲気。

Task-like の変性

Task-like (= 要は非同期メソッドの戻り値)の変性も不便な場面がよくあります。

Task<string> A() => Task.FromResult("");
Task<string?> B() => A(); // async/await が付いていればOKなものの、この書き方だと警告

これも要望が多いわけですが… というか、これに関してはそもそも Task<object> x = new Task<string>(); 的な、(null 許容性関係ない)一般の共変性を認めてほしいという話もありましが、 そっちは「クラスの変性を認めるのは大変」ということでここではあくまで ? の有無(null 許容性違いの変性)だけの話です。

Task-like (Task とか ValueTask とか、非同期メソッドの戻り値に使うもの)だけ特別扱いするのも気持ち悪い話なんですが、 特別扱いというなら今、どうもそもそも、IEnumerable<T> だけ特別扱いしているそうなので今更とのこと。

interface I<in T> { }
class C<T> : I<T> { }
 
static void Main()
{
    // string は string? に代入可能
    string s1 = "";
    string? s2 = s1;
 
    I<string> x1 = new C<string>();
    I<string?> x2 = x1; // 一般にはここで警告。in が付いてても ? 違いの共変性はない
 
    // でも、IEnumerable だけは特別扱いして共変らしい
    IEnumerable<string> e1 = new string[1];
    IEnumerable<string?> e2 = e1;
}

なので、Task-like を特別扱いするの自体はありだろうという雰囲気。 ただ、「オーバーロード解決とかで問題起こさないかとか要検証」という感じ。

キャスト

今、以下のようなコードは警告が出るようになっています。非 null 型にキャストしたければそれより前に null チェックが必須。

static void M(string? nullable)
{
    string nonNull = (string)nullable;
}

これも意図的にそうしてるんですが、「うっとおしいと思う場面と有益だと思う場面、どちらが多いか」ということで再検討。 例えば Roslyn (C# コンパイラー自身のコード)内では (object)x == null という書き方をよくするそうです。 ユーザー定義演算子を無視して確実に参照比較で null チェックしたいというもの。 ここで null 警告が出ちゃうので結構うっとおしいとのこと。 ただ、Roslyn 内では多くても、一般にこの書き方が多いかといわれるとそんなことはなくて少数派。

結論としては今のまま、警告は出す方向でいきたいとのこと。

式ブロック

前々から、式の中に書けるものを増やしたいという話はたびたびあります。 例えば、宣言式 (var x = F() みたいな変数宣言を式中に書けるようにする話)と、 シーケンス式 ((var x = F(); x * x) みたいな ; 区切りで複数の式をつないで、最後の1個の値を返す)提案が出ていたりします。

C# 8.0 で switchが入ったことで、その枝(x switch { c1 => e1, _ => e2 } とかの e1e2 の部分)にちょっと凝ったものを書きたいという欲求も増えたので、「Block-bodied switch expression arms」なんていう提案も出ています。

今回の Design Meeting では、この辺りの似て非なる提案はまとめて検討して、統合、もしくは、少なくとも一貫性のある文法を考えないとまずいだろうという話。

それに伴って、switch 式の枝に限らず式中のどこにでもステートメント(if とか for とか)を書けるようにする構文として「式ブロック」が提案されました。

{ var x = F(); x * x } みたいに、{} 中の最後の1個を式にすることで、ブロック全体を「最後の式の値を返す式」にするというもの。

末尾のたった1個の ; の有無で意味が変わるというのはだいぶ気持ち悪いんですが… 他に適切な記号もなさげ。

f = () => { F(); G(); }; // block body
f = () => { F(); G() };  // expression body

switch 式の枝の方の話だけに留めるか、式ブロックの方までやるべきか、とりあえず C# 9.0 で何かしら実装する前提で調査は始めようという感じみたいです。

ピックアップRoslyn 2/3: Records総まとめ、トップ レベル ステートメント

$
0
0

2件ほど。

どちらも、散発的にアイディアが出てたもののまとめであるとか、現状報告的なものです。

Top-level statements and functions

まず短い方から。

前々から、普通の C# の文法と、スクリプト向けの文法を統合したいみたいな話はって、それの再考というか、 シナリオの整理とどのシナリオを優先するかみたいな話。

ちなみに、まだマイルストーンも決まっていないので、おそらく C# 9.0 よりは先の話になると思います。

要は、以下のような「いつものおまじない」なしでいきなり(トップレベル、あるいは、名前空間直下のレベルに)ステートメントとかメソッドを書きたいという話になります。

class Program
{
    static void Main()
    {
    }
}

主たる目的として3つのシナリオが上がっています。

1つ目は単にプログラムをシンプルに書きたいというもの。 普通に「おまじない」でボイラープレートなコードを減らしたい。 この意味では、トップレベルに書いたコードは Program.Main メソッドの中に自動的に組み込まれてほしい。

2つ目はグローバル関数的なものを定義したいという話。 Math.Sin みたいなものは元々「グローバルでも特に問題はないけど、名前の衝突を避けるために Math クラス配下にまとめらている」みたいなものです。 単に名前分けなら、名前空間直下に関数を書けても別にいいはず。 この意味では、何かラッパークラスを1個作って、その中の静的メソッドに変換して、自動的に using される扱いすればいい。

3つ目はスクリプト用途。 今現在、Microsoft.CodeAnalysis.CSharp.Scriptingで提供されているやつで、微妙に通常の C# 文法と違う文法を受け付けます。 通常の文法と統合したいといいつつ、1つ目のシナリオとは競合します。 スクリプトの場合、実行するたびに別の状態を持たせたく、静的な Main メソッド内に展開されるような方式よりは、クラスを1個作ってそのメンバー扱い(ローカル変数のように書いたものが、実際にはフィールド扱い)する方が好ましかったりします。

今回の決定では、1つ目のシナリオ、要するに「トップレベルのステートメントを Main メソッドに自動的に組み込む」という方向で行きたいとのこと。 トップレベルのメソッドも、Main メソッド内のローカル関数扱いしようという感じみたいです。

結局、「通常モード」と「スクリプト モード」の統合はあきらめていて、 「2つのモードの差をあまり開かないようにしたい」くらいの方針。

Records as a collection of features

ここ数か月くらい散発的には話題に上がっていましたが(10/2511/1612/21)、Records がらみの総まとめ。

こちらは C# 9.0 向け。なのでそろそろ具体性を帯びてきています。

まだ全部の提案に Strawman (藁人形。C# リポジトリ内では「いろいろ叩かれることを前提に、まずは C# チーム内で決めた案を公開」くらいの意味)という言葉が入っているので最終決定からはまだ遠い段階ですが、今までの中では一番まとまっていて、一番具体的な文法が出ています。

タイトルの通り、Records をいくつかの機能の組み合わせに分割したいという話なんですが、 いくつかは不可分みたいな話もしています。

Value-based equality

値による比較(value-based equality)を楽に書きたいという要望が常々あります。

12/21のブログでは「key 修飾子」案が出ていましたが、 今回は value 修飾子になっています。 意味的には12/21の頃と同じ。この修飾子を付けたフィールドの値比較を持って、その型の Equals メソッドや == 演算子を生成したいというものです。

value members

差分としては、EqualityContract というプロパティも生成して、以下のような比較をした方がいいだろうという話が増えています。

例えば以下のような Point クラスがあったとして

public class Point
{
    public value int X { get; set; }
    public value int Y { get; set; }
}

以下のようなコード扱いしたいそうです。

public class Point
{
    public int X { get; set; }
    public int Y { get; set; }

    protected virtual Type EqualityContract => typeof(Point);
    public override bool Equals(object? other) =>
        other is Point that
        && this.EqualityContract == that.EqualityContract
        && this.X == that.X
        && this.Y == that.Y;
    public override int GetHashCode() => ... X ... Y ... ;
}

EqualityContract プロパティを用意しているのは、 対称性の確保のため。 このプロパティがないと、基底クラスのインスタンス b と派生クラスのインスタンス d があるとき、b.Equals(d) は true だけど d.Equals(b) は false みたいなことがあり得ます。

単に GetType() メソッドで型判定しないのは、 以下のような、追加のメンバーを持っていない派生クラスは互いに一致判定できるようにです。

class Base
{
    public value int Id { get; }
}

// 以下の2つの型は特に追加で value 修飾の付いたメンバーを持っていないので、
// Id さえ一致していれば互いに Equals 判定できる。
// EqualityContract はどちらも typeof(Base) を返す。
class Derived1 : Base { }
class Derived2 : Base { }

value types

また、value class Point みたいに型自体に value 修飾を付けることで、全メンバーに value 修飾を付けたのと同じ扱いにするという話も。

上記 Derived1Derived2 を、「Id が同じでも型が違えば Equals は false にしてほしい」(ようするに discriminated union 的な挙動)にしたいときはそれぞれ value class Derived1value class Derived2 と書く(逆に、true にしたいときは value 修飾を付けない)という話もあります。

Removing construction boilerplate

長らく、以下のようなコードの冗長性が嫌だという話がずっと言われ続けています。

// プロパティ、コンストラクター引数、代入の左右の4か所で同じ名前を書くのが冗長
public abstract class Person
{
    public string Name { get; }
    public Person(string name)
    {
        Name = name;
    }
}

これを、最終的には class Person(string Name); くらいまで縮めたいというのが Records の肝なんですが、これも、いくつかの段階に分けて考えようとしているみたいです。

direct constructor parameters

まず、direct constructor parameters という案。 以下のように、コンストラクター引数に対応するプロパティだけを書くという方式。

public abstract class Person
{
    public string Name { get; }
    public Person(Name) // 型名なしで、プロパティ名だけ指定
    {
        // this.Name = Name 的なコードが追加される
 
        // 追加で、値の検証コードとか書くのは自由にできる
        if (Name is null) throw new ArgumentNullException(nameof(Name));
    }
}

primari constructors

次が primari constructors で、以下のように、クラス宣言の行に直接引数を書けるようにするもの。 検証コードの類は「() なしのコンストラクター」みたいな構文が提案されています。

public abstract class Person(string name)
{
    public string Name { get; } = name;
 
    public Person // () なしのコンストラクター構文
    {
        // primari constructor に対する検証コードはここに書く
        if (name is null) throw new ArgumentNullException(nameof(Name));
    }
}

primary constructors は先ほどの direct constructor parameters と相乗効果あり。

public abstract class Person(Name) // primary constructors + direct constructor parameters
{
    public string Name { get; }
 
    public Person
    {
        if (Name is null) throw new ArgumentNullException(nameof(Name));
    }
}

primary constructor member declarations

プロパティと direct constructor parameters の重複も避けたいということで、さらに踏み込んだ文法として primary constructor member declarations があります。 primary constructor の引数の部分に直接メンバー宣言を書いてしまうもの。

public abstract class Person(public string Name { get; });

Improvements for object inititalizers

new Point { X = 1, Y = 2 } みたいな初期化の方法をオブジェクト初期化子と呼びます。 ただ、現状だと mutable なフィールド、もしくは、プロパティにしか使えないので、 immutable が重宝されるこのご時世にはつらいと言われています。 それに対する改善案がいくつか。

Strawman: Init-only properties

オブジェクト初期化子では書き換えられるけど、それ以外の場所では書き換え不能という意味で、set の代わりに init アクセサーを持つプロパティ(init-only properties)を認めようというもの。

public class Point
{
    public int X { get; init; }
    public int Y { get; init; }
}
 
var p = new Point { X = 5, Y = 3 }; // OK
p.Y = 7; // エラー。初期化子以外での Y の書き換えは認めない

validation accessors for auto-properties

「get だけ自動実装して、set 内の検証コードは普通に書きたい」ということがあるので、それを認めようかという話。

public string Name
{
    // get の実装を省略
    get;
 
    // set には検証コードだけ書く
    set { if (value is null) throw new ArgumentNullException(nameof(Name)); }
}

前述の init アクセサーでも同様。

object initializers for direct constructor parameters

前節の direct constructor parameters を持っている場合には、 オブジェクト初期化子の構文(new Point { X = 1, Y = 2 } みたいなの)をコンストラクター呼び出し(new Point(1, 2))に置換しようかという案。

匿名型に対してはこういう類の変換をすることで immutable を実現しているので、匿名型と名前付きの型の不整合をなくそうという話になります。

Non-destructive mutation and data classes

immutable な型のインスタンスに対して、非破壊な書き換え(non-destructive mutation)、すなわち、「コピーを作って一部のメンバーだけ書き換えたインスタンスを作りたい」ということが結構あります。 これに対して、以下のような with 構文を導入したいという話は前々からありました。

var p2 = p1 with { X = 4 };

問題は、この with 構文をどう解釈(どうコード生成)すべきかという点です。

withers through virtual factories

with は、以下のような With メソッドとそれの呼び出しに展開しようという案になっています。 With メソッドの生成トリガーにするために、クラスには data 修飾を求めようという話も。

public data class Point(X, Y)
{
    public int X { get; }
    public int Y { get; }
}
var p2 = p1 with { Y = 2 };

以下のように展開されます。

public class Point(X, Y)
{
    public int X { get; }
    public int Y { get; }
 
    public virtual Point With(int X, int Y) => new Point(X, Y);
}
var p2 = p1.With(p1.X, 2);

この案では、どのプロパティがどのコンストラクター引数と対応しているのかがわかっていないといけないので、data class には前述の primary constructor が必須みたいです。

virtual なファクトリ メソッドを必要とするのは、以下のように、派生型のメンバーのコピーがちゃんと働くようにするためです。

public data class Person(Name)
{
    public string Name { get; }
}
public data class Student(ID) : Person
{
    public int ID { get; }
}

以下のように展開されます。

public abstract class Person(Name)
{
    public string Name { get; }
    public virtual Person With(string Name) => new Person()
}
public class Student(ID) : Person
{
    public int ID { get; }
 
    public sealed override Person With(string Name) => With(Name, this.ID);
    public virtual Student With(string Name, int ID) => new Student(Name, ID);
}

Auto-generated deconstructors

data class では「どのプロパティがどのコンストラクター引数と対応しているのかがわかっていないといけない」、「primary constructor 必須」なので、 だったら分解用の Deconstruct メソッドも(プロパティと引数の結び付け、コンストラクターの逆パターンなので)自動生成できる状況になります。

With メソッドだけ、Deconstruct メソッドだけをそれぞれ別々に生成したいという要件はあまり重い浮かばず、「data 修飾を付ければ WithDeconstruct も生成」でいいだろうというような雰囲気。

Abbreviated data members

with 構文に data 修飾と primary constructor の引数が必須なのであれば、 data class はもう常に前述の「primary constructor member declarations」的な挙動をするという扱いでよさそうです。

要するに、以下のような書き方で、

public data class Point(int X, int Y);

プロパティ public int X { get; }public int Y { get; } を生成したいという話に。

data classes as value classes

value 修飾(値による比較、Equals の生成が目的)と data 修飾(非破壊な書き換え、With/Deconstruct の生成が目的)の2つの案が出たわけですが、割と似て非なる感があります。ただ、必ずしも同じではない。

とはいえ、data と value の2個の修飾子を常に両方書かないといけないというのが快適化というと微妙な感じ。

ただ、「value class は常に data class か」と言われるとおそらく違います。 data class の方が「primary constructor 必須」とかの制約が強くて、 値による比較だけが欲しくて使いにくいという場面は十分想定されます。 with 構文が求めている「インスタンスのコピー」自体を禁止したい場合もあると思います。

逆に、「data class は常に value class か」の方はたぶんその方が都合がよさそうです。

結論

Records がらみを小さな機能の集まりに分けたいという話でいろいろと検討していますが、 結局、いくつかの機能は不可分(data class には primary constructor が必須だったり、With 生成と Deconstruct 生成は常にセットだったり)なところはあります。 それでも、抜き出せる部分はちゃんと抜き出して個別の機能としたいし、特に、値による比較(value class)は個別に切り出すことが有用そうです。

まだ詳細を詰めないといけない部分は残っていますが、今回挙げた案で Records として求めらているものは大筋実現できそうな感じにはなっていると思います。

.NET 5 Preview 1 / VS 16.6 Preview 1 公開記念

$
0
0

.NET 5 Preview 1 や、Visual Studio 16.5 正式版、Visual Studio 16.6 Preview 1 がアナウンスされました。

最近、ブログだと、C# 関連に絞って書いていて、 そうすると今回のアナウンスに関しては別に大して書くことはないんですけども。

今回から、ちょっと動画配信してみることにしました。

C# 8.0 fix

ちなみに、アナウンスや Release Note 上は全く触れられていませんが、C# 8.0 にサイレントに修正が入っているはず。 昔、Gist に上げてあった以下のような修正が入っています。

Roslyn チームの中の人が「16.5 では間に合わなくて 16.6 になるよ」みたいなことをツイートしていた気もするんですけど、16.5 の方でも警告が消えていました。 ちゃんと確認できていなくて自信はないんですが、もしかしたらもうちょっと前から治っていたかもしれません。 16.6 Preview 1 を入れたことで .NET Core SDK が更新されて、それで治ったとかもあるかもしれません(未確認)。

動画配信

さて、ブログとしては以上。

Visual Studio の新しいリファクタリング機能とか、スクショぽとぺたブログを書くのもしんどいんですよね。 その点に関しては昨日、動画配信をやってよかったかなと思っています。 まあ、配信に使ったアプリ(OBS)が、右クリック メニューとか Quick Action の電球アイコンとかリファクタリング結果のプレビューとか、ポップアップする UI を移してくれなくてちょっと困っていますが…

動画配信はまだまだ黙々と環境を整えたりしている真っ最中で、 昨日も少々見切り発車な感じはあります。 とはいえ、こういうのは見切り発車であっても取り合えず始めてしまうことが大事なタイミングがありまして、うちの場合はそれが昨日の「Preview 1」かなと思って急ぎ配信することにしました。 「動画配信も Preview 1 だ」とか言っちゃえる年に1度のタイミングですからね。

動画配信に関しても「High-Level Goals」みたいなものはあるんですが、 その辺りはまた日を改めて話すと思います。

.NET 5 の Target Framework と Optional SDK Workload

$
0
0

まだ提案ドキュメントが出たばかりな段階ですが、 .NET 5 の方向性を決めるドキュメントが2件出ています。

ちなみに、Target Framework Name と言いつつ略称が TFM なのは最後が Moniker (あだ名)だからです。 .NET Core に対して netcoreapp、 .NET Standard に対して netstandard みたいな記号的な名前を振っていたわけですが、これが Moniker です。

標準ライブラリ統合

まず背景おさらい的な話になりますが、 紆余曲折経て、 .NET が単一系統に統合されます。

.NET Framework (Windows 専用)ででしかできないことはほとんどなくなり、こちらは保守モードに入って、 .NET Framework 4.8 が最終バージョンになります。

ランタイムとしては .NET Core 系と Xamarin (Mono)系がまだ残っていますが、 少なくとも標準ライブラリのコードは統合する方向で動いています。

そのため、かつて混とんを極めた Portable Class Library の頃の portable-net40+sl5+win8 (サポートしたいターゲットを全列挙)みたいなものも、 .NET Standard (.NET Core, .NET Framework, Xamarin の最大公約数を取ったもの)みたいなものも必要なくなる予定です。

そこで、名称も「.NET 5」(Core も Framework も Standard も付かず、バージョン番号は 5)となりますし、 TargetFramework 名もシンプルに net5.0 を使う予定です。

(提案されたばかりなので今はまだ実装されていません。 今現在出ている .NET 5 Preview 1 ではまだ netcoreapp5.0 という TFM を使っています。)

TargetFramework 名

ということで、どのプラットフォームでも使える基礎的な部分を指して net5.0 という TargetFramework を使います。 これまででいう netstandard2.1 みたいなものが無印の netX.Y みたいな形式になります。

(.NET Framework の方では . を入れずに net472 とか書いていたので、 .NET 10 が来たら(6年は先の話ですが)ちょっとだけ混乱を生みそうですが、 おそらく net10 だけを 1.0 使いする特殊処理(10にしたければ net10.0 と書かないとダメ)が入りそうです。)

一方で、 .NET Core 3.1 でも WPF や Windows Forms は Windows 限定の機能ですし、 Xamarin を統合するということは Android 限定機能や iOS 限定機能が入ることになります。

これら特定プラットフォーム限定機能向けに、net5.0-android, net5.0-ios, net5.0-windows みたいな TargetFramwork 名を与えるみたいです。 ちゃんと - の前後で分離して解釈するそうで、例えば、net5.0 (.NET 5.0 汎用)なライブラリを、net6.0-android (.NET 6.0 + Android 限定機能)から参照できます。 さすがに。PCL の時のようにはならないように。

ちなみに、- の後ろのプラットフォーム名の方にもバージョン番号を指定できるそうです。 例えば、net5.0-ios14.0 みたいな書き方で「 .NET 5.0 で、 iOS のバージョン 14.0」という意味になります。 プラットフォームの方のバージョンがないときは「その .NET がサポートしているもっとも低いバージョン」扱いになるみたいです。

Optional SDK Workload

.NET SDK も分割したいようです。 今でも、「コンソール アプリしか使わないのに」、「WPF しか使わないのに」、「ASP.NET しか使わないのに」みたいに思うことは多いと思います。 ここに Xamarin.Android や Xamarin.iOS も加わるわけで、そろそろ、必要な分だけを追加インストールする仕組みが欲しい段階です。

ということで、特定プラットフォーム向けの SDK 機能を workload と呼んでいて、 workload は optional (オプション指定しない限りインストールされない)な状態にしたいそうです。

さすがに時代的に、最初からゴールとして「できる限り CI/CD フレンドリーにしたい」というものが含まれています。 「使う分だけインストールすればいい」というサイズ削減の意味でも分けた方が CI/CD 的にやさしいですし、 インストールをできる限り簡単にしたいという目標もあるみたいです。 最終的には、dotnet bundle install [bundle name] とか、dotnet bundle restore [project name] みたいな1コマンドで Optional SDK Workload をインストールできるようにしたいそうです。 (同様の仕組みは workload よりも広い用途に使えるかもしれないので、サブコマンド名はより広い名前、まだ決めかねてるそうですが仮に bundle と呼んでいます。)

ここで、dotnet bundle restore の方は、前節で説明したような TargetFramework を見て、 例えば、net5.0-android なら Android 向けの、net5.0-ios なら iOS 向けの workload (bundle)の復元できるようにしたいそうです。

要は、今現在、ライブラリ参照のために NuGet を使ってやっているのと同程度の手軽さで、SDK workload の参照ができるようにしたいそうです。

.NET 5 次点での目標

.NET 5 次点では、 workload のインストールは Visual Studio (for Windows と for Mac)でだけになるかもとのこと。 dotnet bundle restore みたいなコマンドは一部分だけ。

また、 .NET 5 から追加されることになる Xamarin 系統(要するに Android、iOS、Web Assembly など)の workload が Optional 提供になる予定です。 (まだこの時点では ASP.NET や WPF などの分離作業は手付かず。)

もっと先の目標

最終的には、 dotnet コマンドラインツールのフル機能を提供したいし、 apt-get などの Linux 向けインストール ツールの類を使った workload インストールができるようにもしたいみたいです。

また、 .NET Core 3.1 次点ではモノリシックな SDK 提供になってしまっている ASP.NET や WPF、Windows Forms などの workload も Optional 提供に変更したいそうです。

Viewing all 482 articles
Browse latest View live