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

ピックアップRoslyn 5/20: 5月の Design Notes

$
0
0

5月の Language Design Notes が2件ほど追加されました。

さらっと抜粋。

switch 式

先週書いた通り、現状のプレビュー版では、以下のような文法で「式」としてswitchを書けます。

var s = x switch
{
    1 => "one",
    2 => "two",
    3 => "three",
    _ => throw new IndexOutOfRangeException()
};

今のところ、=> で実装されているんですが、これに関して:

  • :->~> なんかも考えはした
  • まだ決めかねてる。今の実装はとりあえず => になってるけども
  • => はこれはこれで、ラムダ式と混ざったりデメリットもありそう

など。

ranges

これも先週書いた通り。1..^1みたいな書き方で「1Length - 1 の直前まで」(= 最初と最後、1要素ずつ削ったもの)を表す。

アプローチとしては大筋はよさそう。完全に認められたわけでもないけども、「害悪」とまでは思われてない。^って文字を使うのはC#にとってはちょっと馴れないけども、しょせん「馴れてない」程度の話。

nullable reference types

参照型に対して T なら null が来ない、T? なら null があり得る、だけだと不十分で、 いくつか、属性を使ったアノテーションを実装し始めたみたい。

string.IsNullOrEmptyみたいなメソッドでは、「戻り値が false だったらそれ以降、引数は null ではない」とかいう挙動なわけですが、それ用にNotNullWhenFalse属性を導入。

static bool IsNullOrEmpty([NotNullWhenFalse] string? s) { }

また、以下のように EnsuresNotNull 属性で、「このメソッドを呼んだら、引数は null ではないことを確認済み」

static void AssertNotNull<T>([EnsuresNotNull] T? t) where T : class { }

AssertNotNull だと「null だったらそこで例外」みたいな挙動だけども、別に例外でなくても、 「メソッド内部で null でない値に上書き」とかもあり得る。

static void EnsureNotNull([EnsuresNotNull] ref string? s) { if (s is null) s = ""; }

== 以外での null チェックもできるように、「Equals の類のメソッドです。null 解析に使ってください」を表す NullableEquals 属性も。

class Object
{
    [NullableEquals] public static bool ReferenceEquals(object? x, object? y) { }
    [NullableEquals] public static bool Equals(object? x, object? y) { }
    [NullableEquals] public virtual bool Equals(object? other) { }
    [NullableEquals] public static bool operator==(object? x, object? y) { }
}

既存のコードでこの属性が付いてない場合に備えて、 「外からアノテーションを足す」みたいな機能も欲しい。 (属性だと、そのメソッドを書いた人にしか付けれない。)

インターフェイスのデフォルト メソッド

インターフェイスの中に実装を置くことに対して、やっぱりいくらかの人が反対してる。 多くの人は、インターフェイスを自分で書いて自分で使ってる。 この状況だと、「インターフェイスにメンバーを追加したら破壊的変更」と言うのが問題になりにくい。

でも、public な API を作っている人にとってはデフォルト メソッド(= インターフェイスに後からメソッドを追加しても破壊的変更にならなくできる)は非常に重要。 また、Swift や Java との相互運用(主に Xamarin 用)には必要。

なので重要な機能だと考える。

discriminated unions

switch の網羅性

以下のような感じで、「Animal の派生クラスは Dog と Cat しか認めない」みたいな状態を作ったとして

abstract class Animal
{
    private Animal() { }
    sealed class Dog : Animal { }
    sealed class Cat : Animal { }
}

switch の網羅性(考えうるケースを網羅してたら _default を警告なしで省略できるようにしたい)はどう考えるべきか。 以下のコードだとダメ。

int M(Animal a)
{
    return a switch
    {
        Cat c => 1,
        Dog d => 2
    }
}
int M(Box<Animal> b)
{
    return b switch
    {
        Box (Cat c) => 1,
        Box (Dog d) => 2
    }
}

実際には以下のように書かないと網羅的じゃない。

int M(Animal a)
{
    return a switch
    {
        Cat c => 1,
        Dog d => 2,
        null => 3
    }
}
int M(Box<Animal> b)
{
    return b switch
    {
        Box (Cat c) => 1,
        Box (Dog d) => 2,
        Box (null) => 3
        null => 3
    }
}

struct unions

上記のようなクラスを使った discriminated unions の実装(F# の discriminated unions はこんな感じのクラスに展開されてる)の他に、構造体を使った実装もありえる。 int(short, short) みたいな、小さい型のどちらかだけを使いたいみたいな場合はある(特にパフォーマンスを求める場面で)。 ただ、これを実現するには .NET ランタイムのレベルでの対応が必要。


ピックアップRoslyn 5/24

$
0
0

珍しくまめに議事録の投稿が。

今回はこの21日の Design Meeting 議事録1件のみの追加。

さらっと内容紹介:

target typed new

要はこんなやつ。

// 今まで
M(new A(1));

// 提案: 左辺から推測できる場合、型名を省略可能
M(new (1));

プロトタイプ実装のプルリクが出ていて、いつでも通せる状態ではあるんですが、 ちょっと懸念事項が出てきたとのこと。

今回懸念しているのは以下のような状況。

class A
{
    public A(int value) { }
}

class B { }

class Program
{
    static void M(A x) { }
    static void M(B x) { }

    static void Main()
    {
        // 今は、1引数コンストラクターを持っているのが A だけなので、A で推論すべき?
        // その場合、後から B に1引数コンストラクターを足してしまった場合どうあるべき?
        M(new (1));
    }
}

厳しくいくなら、「target typed new が使えるのは初期化子でのみ」みたいな制限にしてしまうという案もあり。 なんせ、「フィールドに対して var を使えないのがしんどい。Dictionary<SomeLongNamedType, AnotherLongNamedType> みたいな長い型を2度書きたくない」というのが最大の要件なので、「初期化子のみ」であってもこの最大要件だけは満たせる。

とはいえ、おそらく、一度はそれで実装したとしても結局後から制限を緩めたくなるだろうと思われる。 なので、まじめにこの「後からコンストラクターを足す」問題に向き合わないといけない。

似たものでいうと、「out varを使った場合、オーバーロード解決しない」(引数の数が同じ複数のオーバーロードがある場合、varではなく、具体的な型が必須)ってなっているので、今後 target typed new を足す際にも同様の制限を掛けるのがいいかもしれない。

return/break/continue 式

condition ? value : throw new Exception() みたいに、throwが式にできたんだから、returnbreakcontinueも式の中で使いたいという話。

こちらも、プロトタイプ実装のプルリクが既に(それもコミュニティ貢献で)出ていて、 やろうと思えばいつでも通せる状態。

が、懸念になっているのが return はどこまでさかのぼって return されるべきか。 (throwの場合はどの道「catchがあるところまで上に伝搬」なので問題にならないが、 return は1段階だけ戻るものなので。)

例えば、「ブロック式」みたいなのも将来入れる可能性はあって、 以下のような書き方ができるようになったとして、この return はブロックを抜ける(x に 1 もしくは 2 が入る)ために使われることになる。

var x = { if (condition) return 1; else return 2; };

今、「return式」を入れたとして、こういう将来考えうる文法との整合性がちゃんと保てるかという心配が強く、「現時点では保留」としておいた方がよさそう。

非同期 stream

  • 生成: awaityield を混在させれるようにしたい
  • 消費: foreach await (...) で列挙できるようにしたい

非同期 foreach はパターン ベースであるべきか

通常の foreach は、所定のパターンさえ満たせば使えます(GetEnumerator, MoveNext, Current などを持っていれば、インターフェイスなどは求めない)。

非同期 foreach も同様にすべき。する予定。

拡張メソッドで非同期 foreach

現状、通常の foreach では、GetEnumerator はインスタンス メソッドでないとダメ。 (初期設計がそうだったのと、そこまで拡張メソッドにしたいという要望が強くなかったため。)

非同期 foreach では拡張メソッドも認めたいし、 既存の通常 foreach でも拡張メソッドを認められるよう検討したい。

ラムダ式中で await と yield の混在

現状の C# では、ラムダ式中で yield を使えない。 (「メソッド中に1つでもyieldがあればイテレーター扱い」っていう仕様と相性が良くない。入れ子のせいで誤判定が怖い。) ただし、「優先度低」で捨て置かれてるけども、害悪だとまでは思ってない。

非同期版(awaityieldの混在)もしばらく考えないでおきたい (やるなら、同期版(ラムダ式中でのyield利用)と同時に考えたい)。

イテレーター化する条件

今、

  • 非同期メソッドは、メソッドに async 修飾子が必須
  • イテレーターは、特に修飾子は必要なく、メソッド中に yield があるかどうかで判定

と、ちょっと一貫性が欠けた状態にはなっている。 (イテレーターのこの仕様は今となってはあまり良くないかもしれないけども、 破壊的変更や、「同じことを別の文法でもできる」状態にしてまでやることではない。)

awaityieldを混在させるにあたってはどうするべきか。 現状、今の延長で行く予定。 すなわち、メソッドにasync修飾子が付いていて、かつ、メソッド中にyieldが1つでもあれば非同期 stream 扱い。

非同期 stream の戻り値はパターン ベースであるべきか

非同期メソッドは、当初は戻り値の型が Task/Task<T> 限定だった。 C# 7.0 移行は、所定のパターンを満たしていれば任意の型を使えるようになった。

一方で、イテレーターはいまだに IEnumerator/IEnumerable/IEnumerator<T>/IEnumerable<T> 限定。

非同期 stream では最初から所定のパターンを満たした任意の型を使えるようにしたい。 一緒に、イテレーターの戻り値もパターン ベースにできるように検討したい。

dynamic に対して foeach await

dynamic x に対して foreach (var i in x) は使えるけども、非同期版の場合にも同様に dynamic を認めるべきか。

やらないつもり。

.NET Core 2.1 正式リリース

$
0
0

.NET Core 2.1 が正式リリースされたみたいですね。

内部的にかなりパフォーマンス改善してるとか、 .NET Global Toolsが使えるとか、 SourceLinkに対応したらしいとかいろいろありますが。 C# 的に直接的に関わってくるのはSpan<T>構造体のリリースでしょうか。

C# 7.2で、Span<T>がらみの言語機能がいろいろ入っているんですが、 肝心のSpan<T>自体がプレビュー状態でした。 (.NET の基本ライブラリ自体がC#で書かれてる部分が多いので、 先に言語機能が入ってくれないとSpan<T>周りの最適化がやりにくいので。) それが今日、正式リリースとなりました。

Span<T>構造体は以下の環境で使えます。

  • .NET Core 2.1 では標準で使える
  • System.Memory パッケージ を参照すれば、古い .NET ランタイム上でも使える
    • .NET Standard 1.1、.NET Framework 4.5とかにも対応
  • .NET Core 2.1 上で実行すると特にパフォーマンスがいい(fast Span)

ピックアップRoslyn 6/30: Working with Data

$
0
0

C# 6.0 くらいの頃から脈々とずっとテーマに挙がっている「データ」関連の機能で、2つほど提案が挙がっています。

新しいものが出たというよりは、プライマリ コンストラクターとかレコード型とか言われていたものを、コンパクトに分割した感じのものです。

長らく先延ばしになっていた機能ですが、C# 8.0 でいよいよ実装しようといことで、 詳細を詰めた結果2つに分かれたという感じだと思われます。

data クラス/構造体

1つ目は、data クラス/構造体 と言われるもので、

  • class/struct の前に data 修飾子を付ける
  • public なフィールド、自動プロパティから、以下のものを自動生成
    • GetHashCode
    • Equals
    • ==, != 演算子
    • ToString

というようなもの。

印象としては、匿名型の延長で、ちゃんとしたクラス・構造体に昇格させたいとい時に使うものな感じです。

using System;

// class に data 修飾子を付ける
data class Point
{
    public int X { get; }
    public int Y { get; }
}

class Program
{
    static void Main()
    {
        // 匿名型
        var p1 = new { X = 1, Y = 2 };

        // data クラス
        var p2 = new Point { X = 1, Y = 2 };

        // 比較とかが自動的に作られる
        Console.WriteLine(p2 == new Point { X = 1, Y = 2 });
    }
}

対象となるフィールド/プロパティ

基本的には、比較やハッシュ値計算に使われるのは public なフィールドと自動プロパティだけです。 private なものや、自動実装でないものは除外されます。 (「データから計算で得られる値を、1回だけ計算してキャッシュしておきたい」みたいなとき、そのキャッシュを比較・ハッシュ値計算に使うことはあまりないので。)

ただ、自動実装でないプロパティでも、DataMember属性を付ければ、比較・ハッシュ値計算の対象にできます。

immutable データに対してオブジェクト初期化子

また、data クラス/構造体では、immutable なデータ(get-only なプロパティ)に対してもオブジェクト初期化子が使えます。 (これまでの C# だと、匿名型で特別扱いで認められてた。通常のクラスだと、オブジェクト初期化子が使えるのは書き換え可能なフィールド/プロパティだけ。)

例えば上記の例では、X, Y の2つのプロパティは get-only ですが、new Point { X = 1, Y = 2 } という書き方が許されます。 これを認めるために、get-only プロパティを、実際には以下のようにコード生成する予定だそうです。

class Point
{
    // <> から始まる名前は、通常の C# コードでは書けない。
    // 通常は使えない名前を使うことで、C# コードからは読み書きさせない。
    // (コンパイラー生成のコードからだけ読み書きする。)
    private int <>X;
    public int X => <>X;

    private int <>Y;
    public int Y => <>Y;

    // 以下、Equals や GetHashCode なども生成
}

class Program
{
    static void Main()
    {
        // コンパイラーはオブジェクト初期化子を以下のように展開
        var p2 = new Point();
        p2.<>X = 1;
        p2.<>Y = 2;
    }
}

名前付きタプル

一方で、タプルの延長で、ちゃんとしたクラス・構造体に昇格させるみたいな構文も追加。名前付きタプルと呼ぶそうです。

以下のように、クラス名に続けてタプルみたいなものを書くことで、タプルに名前が付きます。

using System;

// 型名の後ろにタプル的なものを書く
class Point(int X, int Y);

class Program
{
    static void Main()
    {
        // タプル
        var p1 = (X: 1, Y: 2);

        // 名前付きタプル
        var p2 = new Point(1, 2);
        Console.WriteLine(p2.X);
        Console.WriteLine(p2.Y);
    }
}

見ての通り、コンストラクターとプロパティが生成されます。 また、タプルと同様、比較、ハッシュ値計算や、Deconstruct メソッドなども生成されるそうです。

この例だと class Point(int X, int Y); だけ書きましたが、クラスの中身も持てるそうです。 (昔あったレコード型の提案に結構近い。)

また、class Point(int, int); と言うように、メンバー名は省略できます。 この場合、タプルと同様、Item1, Item2 というような番号付きのメンバーが生成されます。

ピックアップRoslyn 7/11: using patterns and declarations

$
0
0

C# 8.0での追加目標で、using ステートメント絡みの機能が2つほど。

1つは、パターン ベース(IDiposableインターフェイスの実装不要)でusingが使えるようになるというもの。 もう1つは、変数宣言・変数のスコープに紐づいたusing

パターン ベースで using

C# の言語機能のいくつかは、単にメソッド呼び出しに変換するだけのシンタックスシュガーが多いです。 foreachawait、クエリ式など、いろんなものが「この名前のメソッドさえ実装していればどんな型でも使える」という類の構文になっています。 ですが、微妙にものによって挙動が違ったり。

ということで、インターフェイスの実装が必須で使い勝手が悪かったusingステートメントですが、C# 8.0で、これを「Dispose()と言うメソッドさえ持っていれば何でも使える」というものに変えるようです。

というのも、ref構造体がインターフェイスを実装できないものの、ref構造体でusingを使いたい場面が非常に多い状況になっているので。 パターン ベースなusingの需要がかなり上がっているみたいです。

ちなみに、これはusingに限った話ではなく、上記のような挙動の差をなくしたいという話でもあります。 C# 8.0では他にも「非同期 foreach」みたいな話もあって、これと関連して、上記の「foreachが拡張メソッドのGetEnumeratorを受け付けないのは変じゃない?」みたいなことも言われています。こちらもセット。

using 変数宣言

これまで、usingを使うときには以下のような書き方でした。

using (var d = someDisposable)
{
    // このスコープ内を抜けたら Dispose
}

で、C# 8.0 では、以下のような書き方を認めようという話です。

{
    // 変数宣言と同時に、その変数を using
    using var d = someDisposable;

    // 変数のスコープを抜けたら Dispose
}

C# の言語機能としては「using修飾付きの変数宣言」みたいになるようです。

この機能を追加する主な動機は、以下のような「usingの入れ子」の解消です。

// 同寿命のリソースを何個も使うとき、こんな感じになる
using (var a = someDisposable)
using (var b = anotherDisposable)
using (var c = oneMoreDisposable)
{
    // ここを抜けたら Dispose
}

// それをこう変えたい
using var a = someDisposable;
using var b = anotherDisposable;
using var c = oneMoreDisposable;

// メソッドを抜けたら Dispose

これも、fixedステートメントにも同じことが言えそう(同寿命で重ねることが結構ある)ということで、 同じような「fixed変数宣言」も考えているそうです(こちらはたぶんC# 8.0よりも将来の話)。

ピックアップRoslyn 7/11: Design Notes

$
0
0

6月くらいからの C# Design Notes 追加。

いくつかは、提案文書の方が先に出てたので、先にブログを書いてあるものの原案みたいなもの。

その他に関して。

nullable reference types

  • May 30
    • 「アノテーションを何もつけていないと非 null 扱い」(unannotated reference types to be non-nullable; "URTANN")に関して
    • URTANN 動作の opt-out ができないと、C# 7.X 以前のコードが警告だらけになる
      • 現状の Roslyn にそのまま適用すると2000個くらいの警告が出るらしい
    • デフォルト挙動は URTANN(true) にしたい
    • 細かい粒度(特定のファイルだけ、特定のクラスだけ、特定のメソッドだけ)で URTANN の opt-in/out 切り替えができるようにしたい
    • 切り替えはたぶん属性でやる。NonNullTypesAttribute(bool)
  • Jun 4
    • ラムダ式にキャプチャした変数の null フロー解析、ちょっと特殊になりそう

式ツリー

式ツリーで使える文法を増やしたいという話は前々からあるものの、やっと検討が始まったっぽい。

前半は需要のあるシナリオについて。Big data に対するクエリを式ツリーで送りたいとか、機械学習ライブラリ方面で自動微分とか GPU 上でのコード実行したいとか。

後半は実現方法に関して。

  • 既存の、Entity Frameworkとかはノードを追加されても使えない。どのノードが使えるかはライブラリごとに異なる。どう制限するか
  • reduction (await をその展開結果である AsyncStateMachine の行動に変換したりとか、そういうノード変形)はやるかどうか
    • あまりしない方がよさそう
  • C# の最新機能全部に、式ツリーが常に100%追従しようとは思っていない

とか言う感じ。あと、一応、プロトタイプ実装あり。

Target-typed new

  • Jun 25
    • オーバーロード解決には寄与させないつもり
    • S? s = new (); みたいに書くとき、new S() の意味にする(new Nullable<S>()、つまり、null の意味にはしない)
    • new () は型を持たない。var x = new (); みたいなのは型が確定しなくてエラー
    • new () とか new {} とかは認めるけど、new 単体は認めないつもり
    • dynamic型がターゲットのnew ()も認めない

オーバーロードに関しては、例えば以下のような話。

struct S1 { public int x; }
struct S2 { }

void M(S1 s1) { }
void M(S2 s2) { }

void X()
{
    M(new() { x = 43 });
    //↑ x を持ってるのは S1 だけだから、S1 と推論できる
    // でも、それをやっちゃうと、S2 に後から x を追加することで破壊的変更が起きちゃう
    // なので、こういうオーバーロード解決はやらない
}

null 条件演算

?. とか ?? 系統の機能をいくつか追加。

  • x ??= y で、x = x ?? y;
  • await? t で、if (t != null) await t;
  • ポインターに対してもp?[a]p?->ap ?? q

ピックアップRoslyn 7/14: Roles, extension interfaces, and static interface members

$
0
0

ここ数日、C# 8.0 (すぐ次のバージョン)を目標にした内容が多かったものの、今日のはもうちょっと先の話。

タイトルに exploration って入っている通り、まだ「吟味・調査」的な段階のものです。 (特に、Roles の呼び名とかは結構不評。あくまで今現在そう呼んでるだけ。)

昔、ShapesとかConceptとか言う案もあったんですが、 この辺りと狙いは同じ。

その狙いを、extension everythingの延長として、以下の3つの要素の組み合わせで実現しようという話になります。

  • Roles: 既存の型に対して、第三者がメンバーを追加するためのラッパー的なものを作る仕組み
  • Extensions: 拡張メソッドの延長で、プロパティとか演算子とか、何でも「拡張」できるようにするもの。これに、Roles を組み合わせて、拡張でインターフェイスも実装できるようにしたい
  • Static interface members: インターフェイスに、静的メソッドも含めるようにしたいというもの。単に(実装のある)静的メソッドを持つという話ではなく、静的な「抽象定義」(実装するクラス・構造体ごとに別の定義を持てる)を実現したい

Roles と Extensions は似ているものの、Roles は「あるインスタンスをキャストして使う」みたいな感じで、Extensionsは現状の拡張メソッドと同様「一定のスコープ内で、特定の型のインスタンス全部を拡張する」みたいな感じ。

このアイディアは、.NET ランタイム自体の改修が必要になります。 (現状の .NET の型システムの上に、C# コンパイラーによる構文糖衣で作ろうとするとちょっと問題がありそう。)

✓DO、X DO NOT の誤訳事案

$
0
0

だいぶ炎上してる例のあれ

対応ミスってるとはいえさすがにかわいそうなレベルでいいがかり付けられてる感じもするのでちょっと補足を。

元々の問題

マイクロソフトの機械翻訳がよくやらかすのはいつものことなんですが。 今回は何をやらかしたかというと、よくある

  • ✓DO: 〇〇してください
  • X DO NOT: 〇〇はしないでください

みたいなやつを、DOもDO NOTもどっちも「しないで」と訳してしまっているという問題。 「しないで」も不自然だし、ましてDOの方は真逆の意味になっているという誤訳。

どうしてこうなる… みたいな気持ちはもちろんあるものの、こういう「普通の文章」になっていない部分の単語ってのは、機械翻訳では一番ミスを起こしがちな部分です。 たぶん、原文の時点で何らかのアノテーションでも付けておくとかしないと、今後も同様の誤訳は起こりまくるんじゃないかなぁと思います。

フィードバック内容

ということで、「間違ってますよ」とフィードバックが入っているわけですけども。 どう見ても正しく伝わっていない。

おそらく、対応している側も機械翻訳で見ているか、少なくとも日本語ネイティブの人ではなくて、英語ネイティブで日本語を読める人がトリアージュしていそうな感じ。

そこに、

  • フィードバックが「doが"しないで"と翻訳されています。」という短文
    • かつ、「翻訳されています」自体が問題と取れなくもない
  • その下に英語で、オフトピック的に、「DOはDOのままにしてくれ。変に翻訳しても伝わらない」という別フィードバックが混ざっちゃってる

というフィードバックが入ったものだから、あちらの対応としては、後者のオフトピックの方だけを拾って、

  • マイクロソフトでのガイドラインとしては、こういう「DO」みたいなやつも何らかの現地語に翻訳することになっている

と返した(返せてるつもりになってる)という状態。

炎上

これに対して、炎上の仕方はまた違う勘違いが入っているようで。 要するに、「マイクロソフトのガイドラインでは DO は『しないで』と訳すことになっている」と勘違いした日本人が騒がいでいる状況。

で、その状況で結構RTされちゃったわけですけども、 よくある「RTが増えてくるとクソリプがわいてくる」状態(twitterに限った話じゃない)でして、 「どうして Google 翻訳を使わないんだ?」みたいなコメントを付けるやつまで出てきている状態。

さすがにこれは担当者がかわいそうすぎる…

フィードバックの入れ方

ja-jp ページのあまりのひどさになれてしまっている日本人からすると「1行説明でも、このひどさは見りゃわかるだろ」と思うかもしれませんけども。 大体通じません。 というか、英語ネイティブ同士の英語でのやり取りでも、「1行説明」の類は結構通じてない。

逆に、経験上は、こんな感じでスクショぽとぺたしてやれば、大体すぐに対応してもらえます。

正しく状況を理解したうえでも、それどうなの?

まあ、こういう背景を理解したうえでもどうかと思う部分はあると思います。

そもそも機械翻訳いやだ

今もう、人力翻訳予算とか付かないので…

予算を割きたくなるくらい日本が景気良かったのはもう遠い昔の話でして。

機械翻訳の品質が…

今回みたいなのはちょっと特殊なケースだと思います。 前述のとおり、文章になっていなくて記号的に使われている DO みたいな単語は、機械翻訳的にはかなり難しい用法かと思われます。

日本語訳はあてにしていないので英語の原文見せて

見れるで。

原文表示機能

これも、MSDN時代からずっといろいろあったんですよね…

大まかにいうと、「英語が表示されると怖い。出さないで」派と「英語がないと読めない。出して」派のどっちもうるさくて困るそうで。

やっと、

  • 「英語で読む」ボタンが付いた
  • ポップアップのオンオフちゃんと切り替えれるようになった

ってのが実現したんですよ… 活用しましょう。

DO を訳すなよ

これは本当にその通りで、ありとあらゆる方面から何度も何度も刺されているはずなんですけども。

1個本当にひどい話をすると、昔、Xbox インディーゲームズの審査を出そうと思うと、ゲーム画面中に「Game Over」とか「Start」とかの文字すら残してはいけない、1文字でもアルファベットが残っていると「翻訳できてないから許可しない」とか言われたことがあります。

その後、たたかれまくった結果Xboxでは「Game Over」が残ってても大丈夫になったそうです。 で、ほとぼり冷めた頃に今度は Windows ストアアプリの審査で全く同じことが起きました。 「News」とかが残ってると「翻訳できてない」と思われて審査に落ちるという。

要するに、日本人が「文字の混在」に慣れてて、簡単な英単語は読めるというか、日本社会には英字がデザインの一部として溶け込んでる、変に訳されたりカタカナになっているとむしろ日本語的に不自然という文化的背景なんて、日本人にしかわからないということです。

何回言っても伝わらないというか、その日本人の機微に対応しようと思うコストがかかりすぎるというか。マイクロソフトは断固として「現地語」にこだわり続けています。

まあ本当にひどいんですけども、それはそれで、別 issue です。 今回は混ざっちゃったことで勘違いによる炎上が起きたわけでして。


例の事案(✓DO、X DO NOT の誤訳)、追記

$
0
0

一昨日のあれ、まだなんかやってんだ…

なんか、個人的にはこういうのはもっと機械的に処理されてほしく、 言うこと言ったあとは、 なんか2chみたいな荒れ方し始めたから unsubscribe してたんですけども。

機械的に処理してるのが、冷たくあしらったり避難してるように見えてたら嫌だなぁということで、 ちょっとエモい感じの補足を。

最初の1行コメントについて

あー、これはひどい誤訳だわー。よくあるけど。

それじゃたぶん通じてないけど、ご報告ありがとうございます。あとはこっちで処理しておきます。

というつもりだったんだけど、「それじゃ通じない」のところだけを取られて、避難してるみたいに見えたみたいで、そこは本当に申し訳ない…

誤解の原因になったやつについて

これが誤解の原因になってるなぁ…

まあ、わかりみが深いけども。マイクロソフトのドキュメントにはいつも思うけどなんだよあの過剰翻訳。昔、F# を「F番号」とかに訳して炎上してたこともあるけど、そのうち C# も「嬰ハ」とかに訳すんじゃねぇだろうなぁ…

マイクロソフトからの応答について

テンプレのくせに長ぇ…

というか、どうせ機械的なワークフローでやってるんだから、もっとボットっぽいアカウントから返事を返せばいいのに。どうせ翻訳がらみは外部委託とかでしょ。

見せかけだけ高品質にやって、そのくせ中身はそんな品質高くないから問題を助長してるんじゃないかと。 中身相応の貧相なテンプレでいいのに。 こういうとこの品質はもっと落として、その分、翻訳自体に予算割いてもいいと思うんだけど。

今回のこれだって、ボットみたいなアカウントで「ヨク ワカリマセン デシタ」とか付けて Close してた方がまだ炎上しなかったんじゃないかと。

👎 400件(当時)について

👎 400件(当時)とか初めて見たわ… どこから出てきたんだよ、普段そんな人数いないじゃないか…

技術的な話だと全く出てこないのに、翻訳になると急に。 わかった、お前らの仕事は開発じゃなくて文章整形だな。 Excel 方眼紙のマス目をドット単位でそろえるのが主なお仕事の方々だな。 「1mm ずれてるから作り直し!」みたいな乗りで down vote しに来たな。

そういう仕事ばっかりしてるから、「日本の相手はコストかかりすぎるから撤退」とかやられる…

ピックアップRoslyn 8/22: ldftn + calli

$
0
0

結構前から提案は出ていて、1週間くらい前にそこそこ仕様が固まったやつがあるんですが。

C# で、デリゲートではなく、関数ポインターを生で扱うための仕組み。

C# のデリゲートも、そもそもの関数ポインターって概念も、どっちもちゃんと説明しようと思うと結構大変で、どうかこうか迷っていたものの…

Bing.com runs on .NET Core 2.1

そんな中、昨日話題になっていたブログ:

Bing が .NET Core 2.1 化したよという話なんですが、

  • .NET Standard 1.x 時代は .NET Framework に対して足りてないものが多くて断念したけど、2.0 で行けた
  • crossgen コマンドを使って、CI 時にネイティブ イメージを作ってデプロイしてる
  • .NET Core 2.1 のパフォーマンス改善の恩恵を受けて30%高速化

という感じの内容。で、パフォーマンスに関しては、「.NET Core 2.1 ではこんな改善があった」リストも5個ほど紹介。

そのパフォーマンス改善の1つが「ldftn + calli」がらみ。これらの命令は、関数ポインターの読み込みと、その呼び出し命令です。 通常はP/Invokeでしか出てこないような命令で、 現状の C# では普通にやってこの命令が出力されるようなコードは書けません。

(なのに、Bing チームは動的コード生成(IL 出力)でldftn + calliを使っていて、実際それで高速になっているとのこと。)

という話があったので、いい加減に上記の「Compiler intrinsics (calli, ldftn, etc)」の話を書こうかなと。

デリゲートの中身

とはいえ、まずは C# のデリゲートの中身がどうなっているかの話を。

例えば以下のようなデリゲート型を定義したとします。

delegate int A(int x, int y);

内部的に、このデリゲートに対して、以下のようなクラスが生成されます。

// 実際には MulticastDelegate 型から派生していて、MulticastDelegate 側に処理があったりする。
// C# では書けない処理が入ってて、InternalCall (.NET ランタイム内の特殊処理)になってる。
// 細かいところは端折ってるけど、やらないといけないことは概ねこんな感じ。
class A
{
    // obj.Method みたいに書いた時の obj を渡す
    object _target;

    // obj.Method みたいに書いた時の Method に当たる情報を渡す
    // Method 本体が置かれてるメモリ上のアドレス
    IntPtr _functionPointer;

    // multicast 用
    A[] _invocationList;

    public virtual int Invoke(int x, int y)
    {
        // _target をロード
        // x をロード
        // y をロード
        // _functionPointer をロード
        // calli 命令 (現状の C# では出力できない命令)
        // ret 命令
    }

    // single cast
    public A(object target, IntPtr functionPointer)
    {
        _target = target;
        _functionPointer = functionPointer;
    }

    // multicast
    public A(A[] invocationList)
    {
        _target = this;
        // _functionPointer に MulticastInvoke のアドレスを代入
        _invocationList = invocationList;
    }

    public static A Combine(A a, A b)
    {
        // a, b がそれぞれ _invocationList を持ってたら1つの配列にまとめたりしてるかも
        return new A(new[] { a, b });
    }

    private int MulticastInvoke(int x, int y)
    {
        var ret = 0;
        foreach (var item in _invocationList)
        {
            ret = item.Invoke(x, y);
        }
        // 最後の1個しか戻り値が返らない
        return ret;
    }
}

C# では「他の言語でいう関数ポインターのようなもの」としてデリゲートを使うわけですが、 実は、.NET の IL 命令上は生の「関数ポインター」も持っています。 メソッドの中身に当たる IL コードがメモリ上のどこに配置されているか、その先頭アドレスを指すのが関数ポインター(function pointer)です。

で、デリゲートは以下のようなものになっています。

  • インスタンスと関数ポインターのペアを持つクラスになる
  • マルチキャスト用に、複数のデリゲートを1つにまとめる機能を持つ
  • デリゲート型インスタンス d に対して d(x, y) みたいな呼び出しをした場合、実際に呼ばれるのは d.Invoke(x, y) というメソッド(しかもInvokeはvirtual)

これはそこそこ重たい仕組みになっていたりします。

  • 常にクラスのインスタンスが new される
    • 特に、静的メソッドの時は _targetnull で、関数ポインター1個だけしか情報を持たないにも関わらず、無駄にクラスを作ってる
    • クラスなので、P/Invoke のときに GC されないように固定が必要で面倒
  • マルチキャストのために結構特殊な処理が入ってる
    • マルチキャスト機能はイベントのためにあるものの、それ以外の用途でマルチキャストを必要とすることはない

なので、「マルチキャスト不要で静的なメソッドに対しては生の関数ポインターを使わせてほしい」という要望が出てきているようです。

ldftn と calli

ちなみに、ldftn. calli は、それぞれ関数ポインターをロードする命令と、その関数を呼び出す命令です。

ldftn (load function)は、現状のC#でも、デリゲート型のインスタンスを作るときに使われています。 例えば以下のようなコードを書いた場合、

static class Program
{
    static void Main()
    {
        A a = Sum;
    }

    static int Sum(int x, int y) => x + y;
    public delegate int A(int x, int y);
}

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

ldnull // 静的メソッドなので target が null
ldftn int32 C::Sum(int32, int32) // これが関数ポインター読み込み
newobj instance void A::.ctor(object, native int) // デリゲート型のコンストラクターに関数ポインターを渡す

一方、calli (call indirect)の方は前節のデリゲート型のInvokeメソッドの中や、P/Invokeで使われているくらいで、 大多数の C# コードからは生成されません。

Compiler intrinsics

で、当初案はどうだったかというと、 「通常の C# コードからは絶対に出力されないcalli, ldftnとかの命令を直接出力するための仕組みが欲しい」というものでした。

昔から、「C# コード内に IL コードを埋め込みたい」(いわゆるインライン アセンブラー)という要望もあったりはします。 でも、それは「C# コンパイラー チームが C# 以外の言語も保守しないといけない」、「C# コンパイラーが過度に複雑になる」などの理由で否定されています。

そこで出たのが Compiler intrinsics という提案。 特定のメソッド(例えば CompilerIntrinsic 属性が付いてるもの)の呼び出しがあったら、 それをcalli, ldftnとかの命令に置き換えるというもの。 これであれば、C# の文法的にはただのメソッド呼び出しなので、コストは低く所望の動作が得られるだろうという感じです。

関数ポインター intrinsics

とはいえ、結局、ディスカッションの過程で例示された実用途は、 「マルチキャスト不要で静的なメソッドに対しては生の関数ポインターを使わせてほしい」だけでした。

(だったら、最初から、「マルチキャスト不要で静的なメソッド」専用の、light weight なデリゲート構文を追加する方がいいのではないかという話もあって、別途、Static delegateっていう提案も出ていたり。)

ということで、結局、Compiler intrinsics はldftn + calli専用になりそうです。

  • ldftn の方は、単にメソッドの前に & を付けることで関数ポインターを取れるようにする
  • calli だけ、CallIndirect属性を使ったintrinsics (C# コンパイラーが特別扱いするメソッドを用意)にする

ピックアップRoslyn 9/12: nullable参照型、target-typed new、Index 演算子など

$
0
0

夏休みな時期を抜けたからか、7月末辺りからの Design Notes が何件かアップロードされました。

どれも、C# 8.0に向けて実装している機能の、割と細かい課題とそれに対してどうするかの決定についての話。

! 演算子(nullチェックの抑止演算子)

nullable(もしくは C# 7.X 以前のコード由来)な変数だけど、実際には null が来ないはずという時に使うのが!演算子。単に null チェック警告を出さないようにするもの。

入れ子の場合

入れ子の場合 (List<string>からList<string?>に代入するときとか)のチェックをどうするかが議題に。

キャストと ! 演算子は似た性質のものだけども、こういう場合、キャストなら警告を出して、!なら出さないべき。

意味のない !

元々非nullなものに ! を付けたり、2重に!!とか付けた場合をどうするか。

コンパイラーとしては何もしない(警告を出さない)。チェックするとしたらIDEのリファクタリング機能として提供。

nullable 型の dereference

ここでいう dereference (脱参照。参照先の値を得る操作)は、x に対して x.Substring(1) とか、メンバー呼び出しすることを指してる。

以下のように、nullable なものに対して何らかのメソッドを呼んだ場合、 x が null だったら例外が出るはずで、だったら、呼び出して以降は null チェック済みと考えていいはず。

string? x = y; var z = x.Substring(1); ...

実際そう扱うみたい。 正確には、try-catch があった場合、try句内のdereference以降は非 null として、 catch 句内では nullable として扱う。

null コントラクト属性

5/22 に書いた、属性でnullチェックの有無を指示する方式のこと。

現在の候補一覧: Recognize annotations on methods that affect nullability analysis #26761

とりあえず今上がってる候補でプロトタイプ実装を初めて見ようみたいな空気。

実行的チェック コードの挿入

nullable 参照型は、現状、コンパイル時にだけ使うアノテーションになってる。 古いコンパイラーを使ったりでチェックをすり抜けたとしても、特に実行時チェックは働いてない。

それに対して、実行時チェックするコードを挿入するような機能もあってもいい? 例えば、void M(string s!) { } みたいに、引数の後ろに ! を付けたら、引数の null チェック コードを挿入するとか。

とりあえず正式な提案ドキュメントを書いてみる。

Nullable<T>

nullable 参照型に対するフロー解析、一部は Nullable<T> 構造体の .Value にも使えるはず。

class? 制約

void M<T>(T? t) where T : class? は認める?

T? は、T が非 null の時にだけ付けれるようにしたい。T : class? の時点でT が nullable なので、T? は認めない。

List<T>.FirstOrDefault()

List<T>T が制約なしの場合、FirstOrDefault() の戻り値はどうなるべき?

MaybeNull扱いに。

nullable参照型のgeneric制約

void M<T, U>()
    where T : class
    where U : T
{ }

interface I { }
void M<T>() where T : I { }

みたいなのの、T は非 null?

非 null 扱いにする予定。

target-typed new

左辺からの型推論で、new () だけでnewできるやつに関して。

タプルに関して

今、タプルに関しては new (int, int)(0, 0) みたいな書き方を認めてないけども。 new (0, 0) の場合は認めるべき? 7要素以上のタプルの場合、実際に作られるのはnew ValueTuple<..., ValueTuple<...>> みたいな入れ子のValueTupleだけども。

new (0, 0) は認める予定。この場合はタプル構文(8要素以上も大丈夫。上記の入れ子のValueTupleを構築)ではなく、単なるValueTuple構造体のコンストラクター呼び出しという扱い(なので8要素以上の時には使えない/入れ子が必要)になる。

throw new()

throw new() って書いた時、暗黙的に new Exception() 扱いすべき?

しないことに。

ユーザー定義の比較、算術演算で new()

x == new () みたいなの、認める。

Index 演算子

^1 って書き方で、「配列の末尾から1要素目」みたいな扱いにするための ^ 演算子のこと。

  • 構文的に、単項演算子扱い? → そう
  • ユーザー定義演算子? → なしで。今のところ int 以外のオーバーロードの需要をあんまり感じない
  • 優先順位は? → 単項演算子扱いになったので、他の単項演算子と同列
  • ^^1 とかは認める? → 単なる単項演算子扱い + int 以外認めない + ^ の結果は Index 型なので、^^1 は必然的にエラーに
  • .. についても同様 → .. の方は2項演算子ではなく、専用の構文扱いにする

Compiler intrinsics

8/22に書いた、ldftn + calli のやつ

この ldftn + calli を出力できる仕組み自体は取り組みたい。ただ、8/22 時点の提案からはちょっと修正が必要そう。例えば:

  • インスタンスに対して &instance.M みたいな書き方は認めない
  • calli は、_calli_stdcall(args, f)みたいな特殊キーワードでやる方がいいかも
  • 呼び出し規約をどうするかは呼び出し側に書きたい

ピックアップRoslyn 9/26

$
0
0

Design Notes 2件追加。

オーバーライド時の nullability

C# 8.0では、参照型に対して単にTと書くと非null、T?と書いた時だけnullを許容するようにしたいわけですが。 十数年の資産がある中、破壊的変更にならないように、属性を使ってnullチェックの有無を切り替えれるようになる予定です。 ちなみに、以下のような選択肢があります。

  • null チェックをしない
  • null チェックをする
    • チェックしていないクラスを参照した場合、T をnull許容扱いする
    • チェックしていないクラスを参照した場合、T を非null扱いする

で、基底クラスと派生クラスで選んだ選択肢が違う場合にどう扱うべきかという話題が。

static local functions

ローカル関数に対して、意図せずローカル変数をキャプチャしないように、「static修飾子を付けたらキャプチャ不可」という修飾をできるようにしようというのが静的ローカル関数(static local functions)。 これに対して以下のような話題。

  • 全部キャプチャするか、全部しないかだけ?キャプチャ リスト(「この変数だけキャプチャする」みたいな指定を明示的に行う機能)とかも用意する? → しない。全部するかしないかの2択
  • ローカル関数への属性指定はできるようにする? → する予定
  • ローカル関数内で、その外と同名の変数を使える(shadowing、外側のやつを隠す処理)ようにする? → 考えているけど、staticなものだけに対してやるってのは嫌。既存の、普通のローカル関数でもできるようにしたいし、将来的にはラムダ式でも考えたい
  • ジェネリック関数内の静的ローカル関数でも、型引数はキャプチャする? → Yes
  • 「static ラムダ」はやる? → やらない。ローカル関数と比べると短く使うことが多いし、式に埋め込んで書くものなので。

XML Doc コメント

XML Doc コメントに対して、いろいろタグを追加したいとかいう話もかなり積もっていたりします。

Doc コメントは、Visual Studio上でのツールチップ表示とか、Webページ出力したりとか、ツールの対応が必要になります。 チーム外との折衝が必要なので、C# チーム的には常に腰が重いようで、ほんとに要望がたまりにたまっていたり。

で、今回のDesign Noteでようやく検討したみたいなんですが、「やるなら一気にやらないと」、「ツールのバージョンアップのタイミングでやりたいので、C# 的にもメジャー リリースのタイミングで対応したい」とのこと。 (といっても、マイルストーンは「X.0」。少なくとも8.0ではやらない模様。)

foreach のインデックス アクセス最適化

C# は昔から、配列に対して foreach (var x in array) と書くと、for (var i = 0; i < array.Length; i++) { var x = array[i] } 相当のコードに最適化されます。 C# 7.2では、Span<T>に対しても同様の最適化を掛けるようにしました。

で、ここでの議題は、配列とSpan<T>だけを特別扱いするのか、それとももっと汎用な仕組みを提供するのか。

今回のDesign Meetingの結果、この提案の形ではやらないけども、この手のforeachの最適化自体は引き続き何かしら検討したいとのこと。

readonly object initializer

readonlyなフィールドに対してもオブジェクト初期子を使えるようにしたいという提案がありまして。

Record 型の提案とかなり被ってるところがあるし、今出ている提案の形で進む前に、もっと汎用な仕組みができないか検討したいとのこと。

readonly functions on structs

構造体なフィールドにreadonlyを付けると、かえってコピーが多発してパフォーマンスを落とす場合があったりします。 これを避けるためのreadonly structなんですが、 メソッド単位でも「このメソッド内ではフィールドの書き換えはしません」という修飾がしたいという提案があります。

興味はあって、引き続き、今の提案の形で進めていくとのこと(今すぐにやるとは言っていない)。

params Span

今のC#は、可変長引数で使えるのは配列だけなわけですが。 Span<T>で可変長引数を受けたいという提案あり。

ただ、これを制限なく実装したければ、.NETランタイムのレベルでの修正が必要です。 (stackallocを使って可変長引数を渡したいものの、現状、ランタイムの制限で、stackallocが使えるのはunmanagedな型だけです。)

とりあえずC# 8.0目標で進めたいものの、ちょっとやるべきことが多い状態。

nullable reference types 関連の機能を nullable value types にも

  1. nullable value types → 要するに C# 2.0 からあるnull許容型のこと
  2. nullable reference types → C# 8.0 で入る予定の null チェック機構

1の方は、Nullable<T>構造体を使った実装で、TT? が実際に違う型で、T? から T を取り出すときには ValueプロパティとかGetValueOrDefault()メソッドを介します。

それに対して2の方は、nullを追跡するためのアノテーションであって、TT? もどちらも実行時には同じ型になっていて、コンパイラーのnullチェックが掛かるだけです。

まあ、nullable 絡みは途中のバージョンから追加するには結構厳しい機能で、 特に2の方はずっと無理だといわれ続けた結果8.0まで入らなかったくらいなので、 ちょっと無理があるのはしょうがなく、1と2で結構な差が生じています。

とはいえ、差を縮めるように1の方に手を入れることは考えてもよさげ。

要するに、Nullable<T>構造体に対して、例えば以下のようなものも検討したいという話。

  • nullチェックをしないままValueプロパティにアクセスさせない(したら警告)
  • nullチェック後であれば、T?のままで(?.でなく.で)Tのメンバーにアクセスできるようにする
  • nullチェック後であれば、Tを引数に取るメソッドに対してT?の変数でオーバーロード解決できるようにする

いずれは実装したいみたいですけども、C# 8.0時点では「将来これを実装する段階になって破壊的変更にならないかだけ注意する」という扱い。

ピックアップRoslyn 10/14: #nonnullディレクティブ、IAsyncEnumerable

$
0
0

Design Notes 2件追加

10/1 のは、nullable 参照型がジェネリクスに絡むときの話。 例えば以下のような、型推論とかについての検討。

using System.Collections.Generic;

class Program
{
    static void Main()
    {
        IEnumerable<string> nonNull = new[] { "" };
        IEnumerable<string?> nullable = new[] { default(string) };

        // 配列の要素の型推論。
        // これは、IEnumerable<string?>[] になってるみたい。
        var array = new[] { nonNull, nullable };

        // ジェネリック メソッドの型引数の型推論。
        // 配列の要素と似たような推論になるはず。
        // が、201/9/11 版の実装では IEnumerable<string> で推論されてる。
        // この挙動が変だよね、と言うのが議題。
        var ret = M(nonNull, nullable);
    }

    static T M<T>(T x, T y) => x;
}

10/3 の方は、null チェックのコンテキスト切り替えの再検討と、 IAsyncEnumerableインターフェイスの実装方法の決定について。

null チェックのコンテキスト切り替え

改めての説明になりますが、元々 null の存在を前提にしている C# にとって、 nullable 参照型(T だと非 null、T? で null 許容)の追加は、何も考えずにやると破壊的変更になってしまいます。 破壊的変更を極力避けている C# にとってそれはまずいので、null チェックの On/Off を切り替える仕組みを用意する予定です。

これまでのプレビュー版では、とりあえず属性ベースでコンテキスト切り替えが実装されていました。 NonNullTypes属性を付けたら On、付けていないかもしくはNonNullTypes(Warning = false)で Off。 が、そのやり方だと苦しそうということで、プリプロセッサ-でやる(#nonnullディレクティブみたいなものを追加する)ことを改めて検討しているそうです。

(これまでの C# だと、属性の有無によってコンパイラーの挙動がガラッと変わるというような実装をしたことがなく、 そういう「モラル的な意味」でもあまり良くないのは元々良くないんですが、技術的にも苦しそうなことがわかってきたとか。)

ということで、やるとしたら以下の3つのうちのどれかになるだろうということで、これらをそれぞれ検討。

  • 修飾子を作る
    • 非同期メソッドの async 修飾子みたいなの
  • 属性でやるなら、かなり特別扱いした「疑似属性」的なものになる
    • const を受け付けない(true, false の直指定しか受け付けない)とか、名前付き引数を認めないとか
  • ディレクティブを使う
    • #pragma warningと同じノリで、#nonnull disable#nonnull restoreで制御
    • プロジェクト全体の On/Off 制御のために、コンパイラー オプションも必要

で、とりあえず、ディレクティブを使ったアプローチで行ってみようという感じになっているみたいです。 (実際もう、pull request も出てて merge 済み。)

IAsyncEnumerableインターフェイスの実装方法

非同期ストリーム(awaityieldの混在と、非同期版 foreach)を実装するにあたって、インターフェイスをどうするかというのがずっと課題になっていました。 同期版だと、IEnumerable<T>(と、同じ名前のメソッドを持ってさえいればOK)を使うわけですが、 それの非同期版であるIAsyncEnumarable<T>はどういうメソッドを持つべきか。

結局、以下のように、IEnumerable<T>とほぼ同じで単にAsync語尾を付け、ValueTaskを返す作りにしたいとのこと。

public interface IAsyncEnumerable<out T>
{
    IAsyncEnumerator<T> GetAsyncEnumerator();
}

public interface IAsyncEnumerator<out T> : IAsyncDisposable
{
    ValueTask<bool> MoveNextAsync();
    T Current { get; }
}

public interface IAsyncDisposable
{
    ValueTask DisposeAsync();
}

他の選択肢としては、以下のようなものが検討されていました。

public interface IAsyncEnumerator<out T> : IAsyncDisposable
{
    ValueTask<bool> WaitForNextAsync();
    T TryGetNext(out bool success);
}

というのも、パフォーマンス的には後者の方がだいぶ良いことがわかっています。 ただ、これは今回新たに追加する非同期版だけの話ではなくて、 既存のIEnumerable<T>についても同じ課題を抱えています。

IEnumerable<T>の方で軽く試してみた感じ、かかるオーバーヘッドがほんとに倍くらい変わり得ます。 というのも、仮想メソッド呼び出しのコストはそこそこあるので、MoveNext/Currentの2回の呼び出しに分かれているより、TryGetNextの1回で済む方が明らかに速くなります。

上記のIAsyncEnumerator<T>では、WaitForNextAsyncTryGetNextの2つのメソッドがありますが、WaitForNextAsyncの方は呼び出しがかなり少なくなる想定なので、実質的にはこちらでも「TryGetNextの1個だけになるので速い」ということが言えます。

が、以下のようなデメリットもあります。

  • (foreachとかのコンパイラー生成に頼らず手動で)WaitForNextAsyncTryGetNextを使ったコードを書くのはかなり大変になる
    • .NET の仕様上、bool TryGetNext(out T) だと共変にできなくて、T TryGetNext(out bool)なのがキモい
    • 特に、Zip みたいに複数のenumerableが絡むとかなり大変
  • 同期版でもどの道同じ問題があるんだから、もしやるなら、同期版の方も含めて foreach の拡張を後から考えるべき
    • 同期版が MoveNext/Current なのに非同期版だけ TryGetNext にするのは差が大きすぎる

結局、デメリットがきつすぎるということで、同期版と同じMoveNext/Current型のインターフェイスにこだわりたいということになったようです。

ValueTask 実装

このIAsyncEnumerable<T>に関する検討を始めた当初は、Taskに掛かるコストが特に懸念されていました。 が、ValueTaskを使った最適化が進んだ結果、案外、ValueTask<bool> MoveNextAsync()にすれば低コストになりそうというのもわかってきた結果、上記の決断に至ったというのもありそうです。

そちらの検討も、corefx の方に issue が立っています。

前にちょっと書きましたが、.NET Core 2.1 世代で、ValueTaskTaskだけじゃなくて、IValueTaskSourceインターフェイスを受け付けるようになりました。 このインターフェイスを実装した独自のクラスを作ることで、非同期処理に掛かるコストを下げれる場合があります (作ったインスタンスをキャッシュ・再利用したり)。 非同期ストリーム(awaityieldの混在)の実装はまさにその場合に該当していて、 IValueTaskSourceを使ったコード生成をしようという流れになっています。

あと、IValueTaskSource自体は.NET Core 2.1世代(NuGetパッケージを参照すればそれ以外でも利用可能)で追加されましたが、このインターフェイスをちゃんと実装するのはそれなりに面倒です (いくつか、これを実装したクラスは現在もあるんですが、全部internalだったりします)。 そこで、汎用に使えるManualResetValueTaskSourceという実装クラスも、これを機にpublicにしたいという話もついでに出ています。

C# 8.0 の予告

$
0
0

一昨日、C# 8.0 に関するブログが出たわけですが。

個人的には「最近全然ブログ書かない C# チームが働いただと…」的な感想もあるんですが (C# 7.3 のときとか「半年前にリリースしてたわ」みたいなブログでした)。 近々プレビュー版が公開されるであろう C# 8.0 の予告記事です。

Visual Studio 15.9 正式リリースに続いて近々、Visual Studio 16.0 のプレビュー版も公開されて、 それと一緒に .NET Core 3.0 と C# 8.0 もプレビュー公開になると思われます。

.NET Framework 4.8 は未サポート?

で、「.NET Framework 4.8 は .NET Standard 2.1 に追従しないので、C# 8.0 に対応しない」みたいな感じのことが話題になっていますが。 これ、多少不正確でして。 正しくは、

という3つの機能に制限が掛かるだけ。他の機能は普通にどの TargetFramework でも動きます。 null 許容参照型とかパターン マッチングの完全版とか switch 式とかは .NET Framework 1.0 ですら動くと思われます。

必要ライブラリ

まあ、Range/Index構造体なんて結構小さい型なので、 例のごとく自分で同じ名前・同じ機能の構造体を書いてしまえば普通に古いランタイムでも C# 8.0 の機能を使えます。

といっても、Ranges の機能は、配列とか Span<T> が対応していて初めて役に立つものです。 (以下のような書き方をするためには、配列側が対応している必要あり。)

int[] data = { 1, 2, 3, 4, 5, 6 };
Span<int> span = data[1..^1];
foreach (var x in span)
    Console.WriteLine(x); // 2, 3, 4, 5

既存の型に Range 型対応を混ぜ込むのはちょっと無理なので、 その意味では .NET Standard 2.1 でないと大して役に立たない機能になります。

Async Streams の方も似たような感じ。 IAsyncEnumerable<T>インターフェイス自体の移植は簡単ですが、 それに対応したライブラリがないとあんまりおいしくないかもしれません。

インターフェイスのデフォルト実装

本当にどうあがいても .NET Framework 4.8 では動かせないのはこちら。 インターフェイスのデフォルト実装だけです。

ちなみに、どんな感じで「動かせない」かというと、

  • TargetFramework net48 でも、LangVersion 8.0 自体は選べる
    • 前述のとおり、大半の機能は普通に使えます
  • TargetFramework net48 を選んだ場合、デフォルト実装を使ったところだけコンパイル エラーになる

みたいな感じ。 デフォルト実装自体そんなに使う機能でもないと思うので、 大抵の状況では特に問題にならないと思います。

RuntimeFeature クラス

デフォルト実装の話、 要するに、LangVersion だけじゃなくて、TargetFramework によっても文法に分岐が掛かることになります。

ちなみに、正確にいうと、TargetFramework 自体を見て分岐しているのではなくて、 RuntimeFeatureクラスのプロパティがあるかないかで分岐しています。

以下のようなクラスなんですが、このDefaultImplementationsOfInterfacesプロパティが存在するランタイムでだけデフォルト実装が使えます。

public static partial class RuntimeFeature
{
#if FEATURE_DEFAULT_INTERFACES
        public const string DefaultImplementationsOfInterfaces = "DefaultImplementationsOfInterfaces";
#endif
    public const string PortablePdb = "PortablePdb";
    public static bool IsSupported(string feature) { throw null; }
}

.NET Standard 2.1

ちなみに、ちょっとわかりにくいですが、

  • 登場時期: .NET Core 3.0 = .NET Standard 2.1 = .NET Framework 4.8
  • 持っている機能: .NET Core 3.0 > .NET Standard 2.1 ≒ .NET Core 2.1 > .NET Framework 4.8 = .NET Standard 2.0

みたいな感じ。 .NET Standard 2.1 の主だった新機能はValueTask がらみと Span<T> がらみです。 .NET Core 2.1 ですでにおなじみ(?)のやつ。

(Ranges と Async Streams、最初は .NET Core 3.0 でないと使えないのでは疑惑も多少。 .NET Standard 2.1 に入るのかな…)

.NET Framework の今後の扱い

ということで、C# 8.0 への対応という意味では .NET Framework でもそこまで大した問題にはならないと思います。

が、まあ、「.NET Framework 4.8 は最新のものに追従しない」というのは事実。

とりあえず、

  • 同世代の .NET Core 3.0 では Windows 限定機能(WPF, UWP)にもついに対応する
    • もう .NET Framework の方でしか使えない機能が残っていないはず
    • 新規案件で .NET Framework を使うメリットがもう何もない
  • 保守モードな既存のアプリにまで .NET Core への移行を要請しないだけ良心的
    • そのための「保守アップデート」が .NET Framework 4.8
    • セキュリティ パッチとかはきっちり当てて出す

という感じ。 この辺りに関しては、 C# 8.0 のブログ以前に、 今月初旬に出てる以下のブログの方ですでに告知済みだったり。

こっちを見る限り、.NET Framework 4.8 が追従しないのは、デフォルト実装だけじゃなくて、 Span<T>がらみの方が主みたいです。

Span<T>構造体」で書いているように、 Span<T>にはslow版(古いランタイムでも動く実装)とfast版(.NET Core 2.1 以降でないと動かない安全かつ高速な実装)の2種類あります。 そのSpan<T>を使ったライブラリの中には、fast版の安全性保証がないとまずいものもいくつかあって、 そういうものは .NET Standard 2.0 以前向けには提供されていません。

.NET Framework 4.8はこのfast Span<T>対応もできておらず、 なので、.NET Standard 2.0 のまま据え置きということになります。

ピックアップRoslyn 12/1

$
0
0

10月31日~11月28日当たりの Design Notes 追加。

ちなみにこの辺りの実装、pull request を探してみたら Milestone が 16.0.P2になっているものが結構あって、近々出てくれそうな Visual Studio 16 Preview 1 ではまだ入ってなさそうなもの多めです。

Nullable Reference Types

今回の Design Notes の半分くらいは null 許容参照型がらみ。

あと、書きかけではありますが、やっと null 許容参照型の提案ドキュメントがアップロードされたみたいです。

(これまで、Design Notes に検討事項が散見されていただけで、まとまったページが全くなかったという…)

10月31日のやつは、なんか問題提起だけで結論が入ってなくて、なんかふんわりとした感じ。

  • 明示的な null チェック
    • 8.0 というバージョンから急に null 許容参照型を追加する都合で、string x は「古いコード由来だとnull許容なんだけど、8.0以降+#nullable enableの時には非nullとして扱う」みたいな挙動になる
    • string x に対してif (x == null) があるとき、「明示的に null チェックがあるということは、x 自体は null があり得る」ということなので、string (? が付かない)であっても null 許容扱いする
    • みたいなの、やるべきかどうか。TypeScript はやってる
  • リファクタリング
    • null かどうかのフロー解析は、リファクタリングの影響を受けるという話
    • 例えば、if の中身だけを「メソッド抽出」リファクタリングすると、if の条件式内でやった null チェックの情報を失って(メソッドをまたいだ解析はしないので)、警告が出る
  • ! 演算子
    • 場所によって意味変わりすぎ… (前に置くと否定、後ろに置くて「null forgiving」(null チェックを意図的にやらない)アノテーション)
  • ラムダ式中での代入
    • ラムダ式でキャプチャした変数までフロー解析するべきかどうか

11月5日の:

  • #nullable の影響する範囲はどこまでか
    • 例えば以下のようなコードを書いた場合、disable になるのは第2型引数のstringと、Dictionary 自体(Dictionaryのシグネチャの一部分である>を範囲に含んでいるので)
Dictionary<string,
#nullable disable
    string>
# nullable enable
  • null 許容参照型
    • usingディレクティブ中では認めない? → そのつもり
    • typeofnameofは? → 値型の nullable と同じ挙動にしたい。なので、typeofでは認めて、nameofでは認めない

11月14日の:

  • (同じく C# 8.0 で入る) switch式の網羅性(exhaustiveness)とnull
    • 「非 null 参照型だから null は来ないはず」判定のときに、実際には(フロー解析漏れ/C# 7.X 以前のコード由来のせいで) null が来た時どうするか?
    • MatchFailureException例外を投げることにする。MatchFailureExceptionの基底はInvalidOperationException にする

11月28日の:

  • 多重配列問題
    • string[]?[]string[][]?、「null 許容配列の非 null 配列」と「非 null 配列の null 許容配列」どっちがどっち?
    • → 現状、string[]?[] の方が「非 null 配列の null 許容配列」
    • 多重配列の順序は元々ややこしい…

パターン マッチング

  • x is (a, b) みたいなパターン
    • x がタプル(ValueTupe<T>構造体等)なら普通にタプル扱いで分解(要素ごとにマッチング)
    • Deconstructメソッドがあれば(拡張メソッドでの実装を含めて)それを使って分解
    • ITuple インターフェイスを実装していたら、そのインデクサーとかを使って分解
    • (優先度は上から順。ITupleインターフェイスを実装していて、かつ、Deconstrcut メソッドも持っていたらDeconstructの方優先)
  • 0, 1 要素
    • x is () 認めてくれるらしい(0引数の Deconstruct 呼び出し)
    • x is (1) とかも認めてくれるらしい(1引数のDecontruct呼び出し)
    • これは、パターン マッチング(isswitch-case)の時だけ働く。既存の分解代入・分解変数宣言では、キャストとかとの弁別ができないので残念ながら認められない

インターフェイスのデフォルト実装

C# では、base.Member みたいな書き方で、基底クラスのメンバーを呼べます。 (override していても、この書き方なら基底クラス側の実装が呼ばれる。)

で、C# 8.0でインターフェイスのデフォルト実装が入ることで、 「メソッドの実装の多重継承」ができるようになってしまい、 base.Memberだけだと「どっちの基底だよ」と不明瞭に。

そこで、「どの基底インターフェイスのメンバーか」を指定するための構文が必要になります。 候補はたくさん並んでいますが、採用するのは base(BaseInterface).Member みたいな書き方にしたいそうです。

非同期ストリームのキャンセル

非同期ストリーム(IAsyncEnumerable<T>インターフェイス、非同期 foreachyieldawaitの混在)に CancellationToken を渡せるようにするという話。 元々「できないとまずいよね」という検討はされてるんですが、やっと「どうするか」まで決めたみたいです。

まず、インターフェイス。GetAsyncEnumeratorの引数にCancellationTokenを足すみたいです。

interface IAsyncEnumerable<T>
{
    IAsyncEnumerator<T> GetAsyncEnumerator(CancellationToken token = default);

あと、拡張メソッドで IAsyncEnumerable<T> WithCancellationToken<T>(this IAsyncEnumerable<T> e, CancellationToken token) も用意。

非同期 foreachyieldawaitの混在については、 細かい単位でCancellationTokenを渡せるような構文を用意するかどうかという検討はしたものの、 とりあえず現状はそういう特別なことはしないという結論に。


タプル要素名の大文字・小文字

$
0
0

タプルの要素名は(int x, int y) みたいに camelCase (先頭小文字) で書くべきか、 (int X, int Y) みたいに PascalCase (先頭大文字) で書くべきか、 割かし最近、この問題が再燃してたりしました。

背景1: C# のコーディング規約

大体のプログラミングでは、別に大文字・小文字に意味があるわけではなく、 x と書こうが X と書こうが原理的には自由です。 しかし、実践的には、「その言語の標準ライブラリ辺りに合わせる」というのが一般的かと思います。 現在の C# であれば、「Naming Guidelines」辺りに合わせるのが無難でしょう。

(ちなみに、上記の規約は public な部分にしか言及していません。 割かし「private なところは自由にしたらいいんじゃないか」な文化です。 corefxなんかは独自にC# Coding Styleを持っていたりしますが、 これはあくまで corefx に pull request を出すときに気を付けるべき規約であって、 全ての C# 利用者に対して強制するものではありません。 )

今日の主題は casing/capitalization (変数などの先頭を大文字にするか小文字にするか)になります。 今日の話に関係するのは以下の2つ。

  • メンバー名は大体 PascalCase
    • フィールドであっても、public なものは PascalCase
  • 引数名・ローカル変数名は camelCase
public class Class
{
    // フィールドも、public なものは大文字始まり
    public int PublicField;

    // private なものについては割かし自由
    // ほとんどの人は小文字始まり。
    // 先頭に _ を付けるかどうかは好みが分かれるものの、 _ を付ける人の方がちょっと多い印象。
    private int _privateField;

    // 引数は小文字始まり
    public void M(int parameter)
    {
        // ローカル変数は小文字始まり
        int localVariable = parameter;
    }
}

背景2: タプル戻り値

やりたかったことは、要するに LINQ の Zip メソッドにオーバーロードを足したいというもの。 今あるオーバーロードだと、以下のような書き方をよくやると思います。

var x = new[] { 1, 2, 3 };
var y = new[] { "one", "two", "three" };
var zip = x.Zip(y, (i, s) => (i, s));

今の Zip は最後の引数で必ずデリゲートを1個渡す必要がありますが、 大抵の場合はこの例のように「単にタプルで返したい」で十分です。 そこで、以下のようなオーバーロードを足す流れになりました。

static class Enumearble
{
    public static IEnumerable<(TFirst First, TSecond Second)> Zip<TFirst, TSecond>(
        this IEnumerable<TFirst> first, IEnumerable<TSecond> second)
        => first.Zip(second, (x, y) => (x, y));
        // 実際には効率化のためにもうちょっと複雑な実装。返す値自体はこれと同じ。
}

問題: タプル戻り値の casing

先ほどの Zip の新オーバーロードですが、戻り値が IEnumerable<(TFirst First, TSecond Second)>です。 問題となるのは FirstSecond の部分。

タプルの要素名の casing をどうするべきかは結構意見が割れます。 なぜかというと、

  • タプルは構造体の一種なんで、各要素は public なフィールドである
    • だとすると、一般的な規約では PascalCase (先頭大文字)
  • タプルは「変数をペアリングしたもの」あるいは「引数リストの一般化」である
    • だとすると、camelCase (先頭小文字)

前者の立場(構造体のフィールド派)は以下のような感じ。

// 立場1: 構造体のフィールド派
static void A()
{
    // 以下の2行の差は「匿名か、名前付きか」だけ。
    var p = new Point(1, 2);
    var q = (1, 2);

    // タプルは「名前がない型」なだけで、各要素はフィールドみたいなもの。

    // 大体、上記のように要素名を省略した場合、Item1, Item2 と、PacalCase な名前で値を参照することになる。
    System.Console.WriteLine(q.Item1);

    // 以下のように、PascalCase にしておいた方が「名前付き」の場合とそろってていい。
    // このタプル(無名の型)から、自動リファクタリングで Point 型みたいなものを生成することもあり得えて、その場合 PascalCase の方が自然
    var r = (X: 1, Y: 2);
    var x1 = p.X;
    var x2 = r.X;
}

後者の立場(引数の一般化派)は以下の通り。

// 立場2: 引数の一般化派
static void B()
{
    var p = new Point(1, 2);
    var q = (1, 2);

    // タプルは「引数リストだけがむき出しになっている」という状態。

    // 「名前付き引数」を使うと以下のような書き方になるわけで、それに合わせる方が自然。
    var r = new Point(x: 1, y: 2);
    var s = (x: 1, y: 2);

    // s.x という書き方も、「変数 x, y が s によってペアリングされてる」程度の認識。
    var sum = s.x + s.y;
}

// 引数リストとタプル戻り値がほぼ同じ書き方。
// タプルは引数の対となるもの。
static (int x, int y) Swap(int x, int y) => (y, x);

どちらが有力か

C# チームは「引数の一般化派」です。 なので、タプル(C# 7.0)がリリースされてからの2年ほど、 大抵の場所で camelCase が使われてきました。

それに対して、今回、2年越しで corefx 方面に「構造体のフィールド派」が多かったみたいです。 その結果、前述のZipの戻り値は PascalCase で作られて、 一度それの pull request はマージされました。

ここで、両者の齟齬が発覚。 もめ始めて、

  • いったん Zip のやつは revert
  • camelCase か PascalCase かちゃんと決めて、規約に書いとこうぜ
  • 決めかねる。というか、タプルを public なところに使うこと自体よくない
    • Zip みたいに「2要素で確定」みたいなものならともかく、通常は、要素(フィールド)を増やすだけで破壊的変更になってしまうようなものは public には使いにくい
  • 規約に書くなら「タプルは public なところに使うものじゃない」がいい

みたいな流れに。

結局、前述の Zip 新オーバーロードは、以下のような構造体を返す作りにおそらく変更されることになりそうです。

public readonly struct ZipResult<TFirst, TSecond>
{
    public ZipResult(TFirst first, TSecond second) => (First, Second) = (first, second);
    public TFirst First { get; }
    public TSecond Second { get; }
    public void Deconstruct(out TFirst first, out TSecond second) => (first, second) = (First, Second);
}

規約に関しては、タプル要素名の casing についてはやっぱり両論あって決まらなさそう。 その代わり、corefx 内では「DO NOT: タプルは public API で使うな」規約を足しそうな雰囲気になっています。 (これももちろん、あくまで corefx 内の話です。corefx は「破壊的変更を起こしそうなもの」を特に強く避ける文化なのでかなり保守的。)

Directory.Build.*

$
0
0

なんか、Gist に書き捨ててそのまま放置なものが結構増えてきたので、 しばらくそれを元にブログに起こしていこうかという気分に。

ここ2年くらい、.NET Core や C# のテーマの1つがパフォーマンス改善だったせいもあって、だいぶ Unsafe でだいぶきわどい最適化の話が多めになるとは思います…

(ちなみに、今日のは全然その系統ではなく、きわどさもない話。)

Directory.Build.*

Visual Studio 2017 の頃から、所定のフォルダー以下にあるすべての csproj に対して掛かる共通設定を記述できるようになりました。以下の名前のファイルを置くことで、その内容が自動的にcsprojにインポートされます。 (dotnetコマンドでのビルドにも有効です。)

  • Directory.Build.propscsprojの先頭にインポートされる
  • Directory.Build.targetscsproj の末尾(NuGetパッケージに含まれている targets ファイルよりも後)にインポートされる

そういえばあんまり紹介していなかったで、今日はこれの話でも。

全フォルダーに影響

このファイルを置くと、本当にありとあらゆるcsprojに影響を及ぼします。 ビルド エラーを起こすようなミスを書いてしまうと、全プロジェクトがきっちり全滅します。 (そういうヤバさもあるので、これまでこの手の一括設定系の機能はあんまり提供されてこなかったんですけども。ここ数年、Visual Studio チームもだいぶ軟化しています。)

そんな、「全てに一律にかかってほしい設定」ってのがどれくらいあるかという話ではあります。

Deterministic

公式ドキュメントでは、「Deterministic オプション」を例に挙げています。

<Project>
  <PropertyGroup>
    <Deterministic>true</Deterministic>
  </PropertyGroup>
</Project>

これも Visual Studio 2017 (C# 7.0) の辺りで入った C# コンパイラーの機能なんですが、ソースコードを変更しない限り生成される DLL/EXE が常に同じバイナリになるというオプションです。

当たり前っぽく聞こえる話ですが、これまで、タイムスタンプが含まれてしまったり、 partial 定義している型を並列処理したとき順序保証が緩かったりで、ビルドのたびに生成物が変化していました。 そのせいで、CI ツールの類で毎度処理が走ってしまい、CI が当たり前な今の時代、だいぶ負担になっていたみたいです。

ただ、いきなり挙動を変えてしまうと既存の CI を壊す可能性があったので、オプションで切り替え可能に作ってあります。 今は、 .NET Core なプロジェクトであれば既定で Deterministic オプションが true になりますが、.NET Framework なプロジェクトの場合は既定が false だそうです。レガシーな .NET Framework は既定動作を変えなかったという話です。

ということで、.NET Framework でも常に true にしたいときに使うのが上記の設定。

LangVersion latest

僕が常用しているのはこれ。「LangVersion オプション」を常に latest に。

<Project>
 <PropertyGroup>
   <LangVersion>latest</LangVersion>
 </PropertyGroup>
</Project>

C# 7.0 以降、7.1、7.2、7.3 と、マイナー アップデートをしてきました。 細かく頻繁なリリースなので追いかけれない人というのを懸念してか、

  • default … 最新のメジャー バージョンを使う(今だと、C# 7.0)
  • latest … 最新のマイナー バージョンを使う(今だと、C# 7.3)

というような設定になります。 名前通り、規定値は default。つまり、何もしないと C# 7.0 までしか使えない。

うるせー、俺は常に最新の C# しか使わん。 と言う人にお勧めなのが、Directory.Build.props<LangVersion>latest</LangVersion>オプションを入れてしまう方法。 本当におすすめ。是非。

パッケージ バージョン

NuGet パッケージの面倒なところに、「バージョンの衝突があったとき、一番古い奴が使われる」という挙動があります。

NuGet パッケージのバージョン衝突の解決

まあ、バージョン違いのものを参照している時点でいろいろ問題は起こしがちなので、 できれば全部のプロジェクトでバージョンをそろえたいです。 が、それはそれですごくめんどくさい。 Visual Studio にはソリューション全体の NuGet パッケージをまとめて管理する機能もありますが、ソリューションが分かれたりすると大変面倒です。 また、まとめてバージョン アップできても、Git の差分が多くて嫌になったりします。

そこで、Directory.Build.targetsが使えます。 例として、Google.Apisパッケージでも参照してみましょう。 Directory.Build.targets (propsだとダメ。最後に読まれるtargetsの方)に、以下のようにUpdate属性指定でタグを書きます。

<Project>
  <ItemGroup>
    <PackageReference Update="Google.Apis" Version="1.36.1" />
  </ItemGroup>
</Project>

配下にある csproj では、Version を指定せず、Include だけ指定します。

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

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

  <ItemGroup>
    <PackageReference Include="Google.Apis" />
  </ItemGroup>

</Project>

これで、Directory.Build.targets 側に書かれた設定で「上書き」されて、 バージョンが 1.36.1 にそろいます。

Directory.Build.targets 側に Include を書かないのは、無条件にパッケージ参照されないようにです。Updateはすでに同じキーのIncludeがある場合にだけ働きます。

C# 8.0 の null 許容参照型

C# 8.0 で、null 許容参照型(単に T と書くと非 null で、参照型でも T? と書いて初めて null 許容になるフロー解析機能)が入りますが、 T の意味を変えてしまう手前、opt-in (明示的にオプション指定しないとこの機能が有効にならない)になります。

ソースコード中に #nullable ディレクティブを書くことでその行以下の opt-in/opt-out ができますが、それに加えて、プロジェクト全体で opt-in するための csproj プロパティも用意されています。 今のところ、NullableReferenceTypesというタグ名になりそう。

<PropertyGroup>
  <NullableReferenceTypes>True</NullableReferenceTypes>
</PropertyGroup>

既存のプロジェクトに対していきなりこのオプションを指定するのはちょっと勇気が要ります(相当数の警告が出ます)が、 新規に作り始めるプロジェクトであれば、この設定を入れてしまった方がいいでしょう。

RuntimeFeature クラス

$
0
0

先日 C# 8.0 予告なブログで書いた通り、 C# 8.0 で入るインターフェイスのデフォルト実装は .NET ランタイム側の修正が必要な機能です。

今日は、そういう「ランタイム側機能」についての話を少し。

ランタイム側機能

C# の言語機能は、C# コンパイラーがちょこっと頑張ってよい具合にコード生成して、 古い .NET Framework ランタイム上でも動くものが多いです。 「古いランタイム上では動かない新機能」というと、実は .NET Framework 2.0 でのジェネリクスの導入まで遡ります。 .NET Framework 2.0 は2005年リリースですし、 C# 8.0 には実に13年ぶりにランタイムの方に修正を求める機能が入ったことになります。

契機となったのはやっぱり .NET Core の存在です。 .NET Core は、オープンソースで開発ペースも速く、 side by side (1台のPCに複数バージョンを同時にインストール可能)なのでランタイムの更新がしやすいという利点があります。 ランタイム更新しやすいからこその、13年ぶりの新機能を追加です。

登場直後の .NET Core は .NET Framework の下位互換のような存在でしたが、 .NET Core 2.0 くらいから互換性が増してきて、 .NET Core 3.0 ではついに Windows 限定な WPF や UWP などの GUI フレームワークも .NET Core 上で動くようになりました。 一応、完全新規の案件であれば .NET Framework の方をわざわざ使う理由がないくらいにはなっています。 ようやく、次のステップに進む段階に入ったといえます。 これも、このタイミングで13年ぶりの新機能追加に至った要因でしょう。

RuntimeFeature クラス

しかし、ランタイムの新旧によって使えない機能があるとなると、 それを検知・コンパイル時にエラーにする仕組みが必要になります。 コンパイルできたはいいけど、実際に動かそうとした段階で無理だったというのでは困ります。

その検知機構として用意されたのが、RuntimeFeatureクラス(System.Runtime.CompilerServices名前空間)です。 以下のようなクラスになっていて、const stringなメンバーが存在するかどうかで、その機能を使えるかどうかを判定します。

public static partial class RuntimeFeature
{
#if FEATURE_DEFAULT_INTERFACES
    public const string DefaultImplementationsOfInterfaces = "DefaultImplementationsOfInterfaces";
#endif
    public const string PortablePdb = "PortablePdb";
    public static bool IsSupported(string feature);
}

今のところ生えているメンバーは、

  • DefaultImplementationsOfInterfaces … 今回追加されたインターフェイスのデフォルト実装の可否
  • PortablePdb … 動的コード生成で Portable PDB を解釈できるかどうか

の2つです。

PortablePdb

ちなみに、PortablePdbの方について補足。 まず、PDB はデバッグ情報が掛かれたファイルで、 Visual C++ の頃から同名の拡張子のファイルは作られていました。 C# でも、ビルド時に dll や exe と一緒に拡張子が pdb のファイルが作られていると思います。

拡張子自体はずっと同じ pdb ですが、内部の形式については最近ガラッと変わりました。 以前の pdb は、仕様がオープンになっておらず、 pdb を読み込めるデバッガーが Windows 依存でした(なので、通称 "Windows PDB")。 そこで、.NET Core では、せっかくなので仕様自体をオープンにした Portable な pdb 形式を作ることにしたそうです。 それが「Portable PDB」。

PDB は基本的に C# コンパイラーが生成するものなので、RuntimeFeature (ランタイム側)とは無関係そうに見えます。 では PortablePdb は何のためにあるかと言うと、動的コード生成です。 例えば C# スクリプト実行であっても、内部的にはちゃんと PDB を生成して、 デバッグ情報が取れるようにしてあります。 このとき、生成する PDB を Portable PDB にしていいか、クラシックな Windows PDB でないとダメかを判別するための機構がないと困るので、RuntimeFeature.PortablePdbがあります。

その他の RuntimeFeature 機能

その他に、RuntimeFeatureクラスには (.NET Standard 2.1 から)以下のような bool 型の静的プロパティもあります。

public static partial class RuntimeFeature
{
    public static bool IsDynamicCodeSupported { get; }
    public static bool IsDynamicCodeCompiled { get; }
}

先ほどの2つとは違って、こちらは実行時に値を確認して使う用みたいです。 そのランタイムで、

  • IsDynamicCodeSupported … そもそも動的コード実行は可能か
  • IsDynamicCodeCompiled … 動的コード実行はコンパイルされているか(= インタープリター実行ではないか = パフォーマンスよく実行できるか)

の判別に使います。 例えば、動的コンパイルができない環境で式ツリーCompile メソッドを使ったりすると、 高速化のためにやってることなのにかえって破滅的に遅いコードになってしまいます。 それを避けるために分岐に使うのがこれらのプロパティ。

今後入るかもしれないランタイム側機能

とりあえず、DefaultImplementationsOfInterfaces は最初の一歩です。 これからは定期的に、こういう .NET ランタイム側の修正が必要な機能が追加されていくものと思われます。 (おそらく、基本的にはメジャー バージョンアップのタイミングでの追加。 そんなに高頻度で追加はしないと思われます。)

例えば、以下のような issue ページがあります。 .NET ランタイム(CLR)に修正を入れれるなら実現できそうな C# 機能の一覧。

挙がっている内容は、以下のようなものです。

CLR unification of types across assemblies

別アセンブリで定義された「同名で同じメンバーを含む型」を同一視したいというもの。

DI 用途でほしかったり。 あと、匿名クラス(内部的に匿名クラスを生成してる)が public なところで使えない理由にもなっているので回避方法が欲しいという要望があります。

Make void a first-class type

要するに、Func<void>とかTask<void>と書かせろ、 Actionや非ジェネリックTaskとの分岐がめんどい、というあれ。

Covariance and contravariance for classes

現状、変性が認められているのはインターフェイスとデリゲートだけなわけですが、どうにかしてクラスでも認めてほしいというやつ。

Task<object>Task<string> を代入したいとか、そういうやつ。

|, &, and ~ operators on a type parameter with the enum constraint

where T : Enum が付いてるとき、その型の変数に対してビット演算したいというやつ。 今、列挙型とジェネリクスの相性が悪すぎてつらく。

Union and intersection types

最近 TypeScript に入ったあれ。 string | int で「stringint のどちらか」みたいな型を作ったり、 IA & IB で「2つのインターフェイスIAIBの両方を実装した(両方のメソッドを使える)型」を作ったり。

ある程度は C# コンパイラーのレベルでできるんですが、 ランタイムに手を入れて型システムのレベルで対応した方がよいという話。

Support generic indexers

インデクサーに型引数を取りたいと。 ↓みたいな。(今は、このTがどうやっても使えない。)

public interface IOptions
{
    T this<T>[OptionKey<T> key]
    {
        get;
        set;
    }
}

Higher-kinded polymorphism

ジェネリクスの型制約に複雑な条件を付けたいというやつ。 例として挙がってるのは↓みたいなコード。

public static T<A> To<T, A>(this IEnumerable<A> xs)
    where T : <>, new(), ICollection<>
{
    var ta = new T<A>();
    foreach (var x in xs)
    {
        ta.Add(x);
    }
    return ta;
}

methods in enums

列挙型に直接メソッドを持ちたいというやつ。 拡張メソッドでの実装だとちょっとつらいこともあり。

Visual Studio 2019 Preview 1

$
0
0

Connect やってましたね。

とりあえず、関連ブログ:

とりあえず、.NET Core 2.2 正式リリース & .NET Core 3.0 プレビュー提供開始。 Visual Studio 2019も preview 1 がダウンロードできるようになりました。

あと、WPF、WinForms 等がオープンソースになったみたいです。 (重ね重ねの注意になりますが、.NET Core 3.0 で動く/オープンソースになったといっても、Windows 限定です。)

C# 8.0

大してアナウンスされていませんが、Visual Studio 2019 Preview 1 でひそかにちょこっとだけ C# 8.0 を試せるようになっていたり。

ただ、

  • LangVersion default は C# 7.0 だし、latest は 7.3 のまま
    • C# 8.0 を試してみたければ LangVersion 8.0 を明示的に指定
    • LangVersion beta とか experimental みたいなモニカーもなさそう
  • 実装されている機能は 「Language Feature Status」参照。現時点では以下のものだけっぽい
    • Nullable reference type
    • Ranges
    • Null-coalescing Assignment
    • Alternative interpolated verbatim strings
  • どうも、C# 8.0 の正式サポート開始は Visual Studio 2019 の最初のリリースではやらず、その後、 .NET Core 3.0 が出るタイミングからにしたいらしい

今使える機能

Language Feature Status の C# 8.0 のところに並んでいる15個のうち、「Merged to dev16 preview1」になっている4個だけが今試せるっぽいです。

ちなみに、ちゃんと、C# 8.0 の機能を使おうとすると、「プロジェクトを C# 8.0 にアップグレードしますか?」と聞かれます。

Upgrade this project to C# language version 8.0 *beta*

むっちゃ beta を強調されてますが。

以下、軽くサンプルを。(github にも上げてあります)

Nullable reference type

// 有効にするには #nullable ディレクティブが必要。
#nullable enable

using System;

class Program
{
    static void Main()
    {
        Console.WriteLine(LengthSum("abc", "xyz"));
        Console.WriteLine(LengthSum("abc", null));
    }

    static int LengthSum(string a, string? b)
    {
        // こう書いてしまうと b のところで警告。
        var len0 = a.Length + b.Length;

        // これなら OK。b?. なので、b の null チェック済み。
        var len1 = a.Length + b?.Length ?? 0;

        // こんな感じで if で null チェックしても OK。
        // チェック済みな個所では b. で大丈夫。
        var len = a.Length;
        if(b != null) len += b.Length;

        return len;
    }
}

Ranges

using System;

class Program
{
    static void Main()
    {
        var data = new[] { 0, 1, 2, 3, 4, 5 };

        // 1~2要素目。2 は exclusive。なので、表示されるのは 1 だけ。
        Write(Slice(data, 1..2));

        // 先頭から1~末尾から1。表示されるのは 1, 2, 3, 4
        Write(Slice(data, 1..^1));

        // 先頭~末尾から1。表示されるのは 0, 1, 2, 3, 4
        Write(Slice(data, ..^1));

        // 先頭から1~末尾。表示されるのは 1, 2, 3, 4, 5
        Write(Slice(data, 1..));
    }

    // 最終的に、.NET Core 3.0 には Span<int> に Range 型を受け取るインデクサーが入るはず。
    // 今はその実装がないので自前で同じ機能を作る。
    static Span<int> Slice(Span<int> data, Range range)
    {
        int getIndex(int length, Index i) => i.FromEnd ? length - i.Value : i.Value;
        var s = getIndex(data.Length, range.Start);
        var e = getIndex(data.Length, range.End);
        return data.Slice(s, e - s);
    }

    // 表示確認用。Span の中身を , 区切り表示。
    static void Write<T>(Span<T> items)
    {
        var first = true;
        foreach (var x in items)
        {
            if (first) first = false;
            else Console.Write(", ");
            Console.Write(x);
        }
        Console.WriteLine();
    }
}

Null-coalescing Assignment

x ??= y で、if (x == null) x = y; の意味に。

using System;

class Program
{
    static void Main()
    {
        NullCoalescingAssignment("abc"); // "abc" が表示される
        NullCoalescingAssignment(null);  // "default string" が表示される
    }

    static void NullCoalescingAssignment(string s)
    {
        s ??= "default string";
        Console.WriteLine(s);
    }
}

Alternative interpolated verbatim strings

$@ の順序しか受け付けなかったやつが、@$ も認めるという話。

// こっちは C# 6.0 からあるやつ。
var s1 = $@"\\\ {x}";

// これまでは $ と @ の順番逆にできなかった。
// C# 8.0 から @$ でも OK。
var s2 = @$"\\\ {x}";

サポートに関して

Mads (C# チームの PM)の動画の説明欄には「included in Visual Studio 2019」とか書いてあるんですが…

C# チームの中の人が gitter で以下のようなことを言っており:

The C# 8 language support will not RTM with initial VS2019 release. The features and the language version will be there but as "beta", meaning some breaking language changes may still occur. C# 8 will RTM in an update of VS2019, aligned with .NET Core 3.

VS2019 リリースの時点ではRTMにはならない。ベータ扱いで、まだ破壊的変更の可能性残る。 C# 8のRTMはVS2019のアップデートで、.NET Core 3.0とそろえてやる。

とのこと。

「機能としては乗ってるけどまだベータ」とか、混乱されそうでちょっと怖いですが。 「default」が 8.0 に切り替わらない限りには大丈夫なのかな…

おまけ: Regexパーサー

今年の初めに紹介した Regex パーサーが Visual Studio 2019 に組み込まれたみたいです。

Regex パーサー

構文ハイライトが付くのと、不正な正規表現の検出、訂正をある程度やってくれるます。

C# 8.0 小さな修正

$
0
0

Visual Studio 2019 Preview 1 が出て、 さすがに C# 8.0 に入る機能・入らない機能がある程度見えてきたので、 今日からしばらくその辺りの紹介をしていこうかと。

とりあえず今日は、「1記事使うほどでもないような小さい奴」をまとめて紹介。

  1. 文字列補完、$@ の順序緩和
  2. ??= (null 合体代入)演算子
  3. 構造体の宣言時、refpartialの順序緩和
  4. 分解の右辺に default
  5. 入れ子の{}内での stackalloc
  6. unmanaged 制約付きの型引数に、ジェネリックな型を渡す

ちなみに、VS 2019 Preview 1 で実装されているのは上の2つだけです。

順序緩和

C# のキーワードには、並び順を自由に変えられるものがいくつかあります。 代表的なのはクラスやメソッドに対する修飾子ですが、例えば以下の3行は全く同じ意味になります。

static public readonly int x;
public readonly static int x;
readonly static public int x;

一見するとこれらと同じように順序不問そうに見えるのに、なぜか順序に厳しいものもあります。 やむを得ない理由があってそうなっているものもあるんですが、 例えば、部分クラスpartialは、 「C# 2.0 から追加したキーワードなので、1.0 時代のコードを壊さないように、順序を厳しくした」という理由で「classまたはstructの直前でないといけない」という制限が付いています。C# 7.2 で入ったref構造体も同様に、refキーワードはstructの直前にないといけません。

しかし、いくつかは理不尽、あるいは、過剰で、

  • 参照引数な拡張メソッドref this Tでないとダメだった
  • 構造体に対する refpartial の両方付けたければref partial structの順でないとダメ
    • partial ref struct でもいいはず
  • 文字列補間 の($)と逐語的リテラルの(@)を同時に指定したければ$@の順でないとダメ
    • @$ でもいいはず

とかいうものもあったりします。 こいつらはほんとにどっちが正しいのかわからず、よく間違います。

拡張メソッドの ref thisthis refは、 今は順序緩和されていてどちらでも使えます。 しかもこの修正、パッチ リリースでこっそりと入っていたり。

ということで、この度、C# 8.0 では後者2つも順序緩和されるみたいです。 どちらも最初から認めてくれててもいいレベルなんですけどね…

null 合体代入

「null だったら何か適当な既定値で上書き」みたいな処理は結構頻出かと思います。

static void M(string x = null)
{
    if (x == null) x = "default string";
    // x に対して何か処理
}

あるいは、遅延初期化のために、「初期値にnullを入れておいて、初回アクセス時に有効な値で上書き」みたいなことも結構書きます。

public string Name => _name ?? (_name = GetName());
private string _name = null;

後者の例では null 合体演算子 ?? と代入 = を組み合わせていますが、まあ、まさにやりたいことはこれ。 + に対する += のように、?? に対する ??= が欲しいという要望は結構あります。

ということで、その??=演算子が C# 8.0で入ります。

static void M(string x = null)
{
    x ??= "default string";
    // x に対して何か処理
}
 
public string Name => _name ??= GetName();
private string _name = null;

これ、VS 2019 Preview 1ですでに実装されていますけども、 取り組むことになったの、そこそこ最近なんですよね。 あと、地味な機能なのでそんなに話題にも登らず、アピールもされず。 なんか気が付いたら決まっていて、 気が付いたら実装されてて、 気が付いたらマージされた印象。

大した機能じゃなくて実装が簡単とは言え、ちょっとびっくり…

分解の右辺に default

C# 7.1 で defaultってのが入ったわけですが。 要は、左辺から推論が効く限りには、default(T)(T)を省略してdefaultだけで掛けるようになるというやつ。

このdefaultの型推論、C# 7.x までは、以下のような状況では利きませんでした。

(int x1, int y1) = default; // ダメ
(int x2, int y2) = default((int, int)); // これならOK
(int x3, int y3) = (default, default); // これでもOK

この、1行目の「ダメ」ってなっている方を、C# 8.0からはOKにするみたいです。

確かに、なんかきわどい… (int x, int y) に対して分解代入できる型は別にタプルに限らないわけで、 じゃあ、このdefaultは何に推論されたのか…的な不思議さは一瞬ちょっと感じます。 (まあ、でも、便利さ優先でほしい機能。)

入れ子の{}内での stackalloc

C# 7.2 で安全に使える stackallocが入りました。 ですが、ref構造体の制限から、非同期メソッド内ではこの機能が使えませんでした。

Span<int> x = stackalloc int[32];
 
// ここで x を使うのは安全なはずだけど、今は問答無用でエラー。
 
await Task.Delay(1);
 
// await をまたいで stackalloc を使おうとするのは明確にまずい。
// これは制限されていてもしょうがない。

これに対して、C# 8.0では、以下のように一段{}でくくればOKになります。 要するに、{}でくくることによって、絶対にawaitをまたがないことが保証されれば({}内にawaitがなければ)認めても安全ということです。

{
    // {} でくくったのでこれが書けるようになる。
    Span<int> x = stackalloc int[32];
}
await Task.Delay(1);

unmanaged 制約付きの型引数に、ジェネリックな型を渡す

C# 7.3 でunmanaged制約が入りましたが、微妙に使いにくい点がありました。

Unmanaged<int> x; // int は unmanaged なので OK
 
// 以下のものは C# 7.3 ではダメ
Unmanaged<(int, int)> y; // int しか含まないはずなのに…
Unmanaged<Unmanaged<int>> z; // 再帰的に unmanaged 制約を満たしてそうなのに…

要は、「ジェネリック型は問答無用ではじく」という状態です。

ちなみに、同様の事情はref構造体にもありまして。 ただ、ref構造体の方は、かなり厳密にチェックしてはじかないとまずい(セキュリティ ホールの原因になりかねない危険性あり)ので、 こちらは絶対にジェネリック型を使えないそうです。

そして、C# の仕様書上、ポインターにも同様の制限があります。 C# 2.0の頃からずっと、ジェネリックな型に対してポインターを使えませんでした。

しかしどうも、ポインターに関しては別にこの制限は要らなかったらしいです。 あくまで、「1つでも参照型を含んでいたらダメ」にすべきで、 再帰的にunmanaged制約を満たしているのならジェネリックかどうかは関係ないはずです。

なので、C# 8.0で、unmanaged制約でのジェネリック型の利用制限は撤廃するし、 仕様書のアンマネージ型に関する記述も修正すべきという話になっています。

Viewing all 482 articles
Browse latest View live