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

ピックアップRoslyn 10/30: 分解、宣言式、ワイルドカードの内部仕様変更

$
0
0

Roslynリポジトリのブランチやマイルストーンの動きを見るに、次のVisual Studio 15 PreviewはもうRCなようですが。 RC (リリース候補)の段階で大きな変更をするわけもなく、最近のC#チームの動きは大体「ただひたすらバグ修正」なわけです。

そんな中、仕様変更の話が。

まあ、内部挙動的な話なので、C# 7の見栄え上はほとんど変わらないはず。分解出力変数宣言で、(A < B, C > D)みたいな解釈の難しい書き方がどう解釈されるか、みたいな細かい挙動が変わるだけです。

ただ、プログラミング言語としてのC# 7的には大した変化じゃないですが、CodeAnalysis API(いわゆる、Compiler as a Service。C#コンパイラーの中身を誰でも見れるようにするって意味でのC# 7 API)的にはもちろん目立つ変更になります。要は、SyntaxNodeの種類が変わります。

ワイルドカード

ワイルドカード(wildcard)は、仮引数、分解、出力変数などの値を受け取る側で、別にその値を使わない場合に無視する記法がほしいというやつ。 以下の、*みたいなやつ。

using System;
using System.Diagnostics;

class Base
{
    public event EventHandler<string> E;
    protected virtual void M(int x, int y) { }
    public void Deconstruct(out int x, out int y)
    {
        x = 1;
        y = 2;
    }
}

class X : Base
{
    public X()
    {
        E += (*, *) => Debug.WriteLine("log message");

        Deconstruct(out *, out var y);
    }

    protected override void M(int *, int *)
    {
    }
}

案としては _*? なんかが出ていました。これまで、* が最有力だったんですが、_ の方にするかも、とのこと。

  • _
    • 今現在、_ 1文字はC#の識別子として有効なものなので、_ をワイルドカードに使うのは破壊的変更になりかねない
    • 一応、_を使っている場所があれば識別子として、使ってなければワイルドカードとして扱うみたいな処理を入れれば破壊的変更にはならない
    • ただ、それやるとユーザー的には混乱しないかという話はある。まあ、現状すでに、多くのユーザーが_を「使わない引数」として使ってるので大丈夫ではないか
  • *?
    • 破壊的変更は起こしにくい
    • ただ、int *みたいな書き方が、「int型をワイルドカードで無視」なのか、「ポインター型int*」なのかという不明瞭さがある
    • ?も、null許容型のint?との不明瞭さがある
    • この不明瞭さの解決の方が、_の方の問題よりも難しそうという意見に傾いてきた

宣言式

元々の計画としては、式の途中のどこにでも変数宣言を書ける「宣言式」(declaration expressions)という機能が検討されていました。 現在でも、将来的には宣言式を追加する見込みは非常に高いですが、C# 7では入りません。

一方、C# 7に入る出力変数宣言は、この宣言式のサブセットみたいなものです。 なので、内部的には(CodeAnalysis API的には)、出力変数宣言は最初から「declaration expression」という名前になっています。

分解

一方、現状では、分解は、分解専用のステートメントとして実装されています。 これが、将来的に宣言式を入れたときに邪魔になりそうなわけです。 分解も、宣言式を使って表現した方がいいだろうということで、C# 8の際に破壊的変更にならないように、今から変えてしまいたいとのこと。

例えば、現状、C# 7では書けないものの、将来的には認められそうな書き方として、以下のようなものがあります。

var tuple = (1, 2);

// タプル構築 + 分解(しかも、新しい変数 x, y を宣言しながら)
var t = (int x, int y) = tuple;

この、int xint yの部分は、まさに、宣言式であるべきだろうという話です。

付随して、タプル、分解、出力変数宣言などでモデルをそろえておかないと、細かい挙動で差が出て気持ち悪いというのもあります。 例えば、以下のようなコードはどう評価されるべきか。

(A < B, C > D) = F(A < B, C > D = value);

以下の2つの解釈ができます。

  • A < BC > Dという2つの式を持つ
  • A<B, C>というジェネリック型の変数Dを宣言式で宣言している

これが、現状の、宣言式と分解で異なる実装をしてる状態だと、それぞれ逆の解釈になるそうで、まずい。 今のうちに統一挙動にしてしまいたいということに。

foreach の変更

C# 7的に、分解が書けるのは以下の3カ所。

  • 分解ステートメント: var (x, y) = t;
  • forステートメント: for (var (x, y) = t; cond; iter)
  • foreachステートメント: foreach (var (x, y) in list)

このうち、forは現在の文法構造で、この「分解も宣言式で定義する」という変更に耐えれるそうです。

一方、foreachには変更が必要そう。

まとめ

宣言式とワイルドカードっていう、C# 7では入らない/入らなさそうな機能に関する話ではあるけども、 C# 7の時点からちゃんと考えて作っておかないと後から困りそう。 なので今から、宣言式とワイルドカードを前提とした作りにしておきたい、という話。


Roslynに提案issueを立てた話: nullの扱いに関して

$
0
0

MVP Global Summit (グロサミ)に行ってきてたわけですが。

なんか、行きの飛行機内で思いついてしまって、そのまま向こうで頑張って issue 投稿、 せっかくだからグロサミ中に Mads (C# のPM)を捕まえて「こんな問題見つけちゃって、昨日ちょうどissue立てたんだけどどうしよう?」みたいな話を振ってきたり。

(先に具体例を書いておけば、どれだけ英語がつたなくても結構意図は伝えられる。)

(ちなみに、元々は帰国後にゆっくりページ書くつもりだったんだけど、なんかグロサミ参加の妙なテンションに任せて、 kekyoさん藤原さん室星さんとかと部屋の飲みの最中にこの方々も巻き込んでissue書きました。)

勢いで立てたものなので整理できてないんですが… たぶん2パートに分けた方が良さそう。 以下、改めて要約した内容(英語に訳して再投稿予定)。

2つの提案

nullの取り扱いと関連して、以下の2つを提案する。

  • 非default値型: default(T)の状態を認めない値型が必要ではないか
  • nullable-like型: 参照型とNullable<T>以外にも、?.??が使える型をユーザー定義できるようにすべきではないか

非default値型

Method Contracts、特に参照型の非null保証を入れようと思うと、確実な初期化処理が必須になる。

しかし、構造体の場合、defautl(T)など、既定値によって初期化処理を通らない「0/nullクリア」が発生する。 既定値の「0/nullクリア」のせいで、Contractsや非null保証のフロー解析が狂う可能性がある。

例1: 非null保証

パフォーマンスのために、参照型を1つだけ持つようなラッパーを構造体で作ることがある。 (例: ImmutableArray、) 今、パフォーマンスはC#の1つの大きなテーマであり、こういうケースは今後より一層増えるだろう。

例として以下のような構造体を考える。

struct Wrapper<T> where T : class
{
    public T Value { get; }
    public Wrapper(T value)
    {
        Value = value ?? throw new ArgumentNullException(nameof(value));
    }
}

レコード型非null保証が入れば、単に以下のように書けるだろう。

struct Wrapper<T>(T Value) whereT : class

単にTと書けば非nullとなり、nullを受け付けたければT?と書くようになる。 問題は、この構造体をdefault(Wrapper<T>)で作ると、T Value (本来は非nullであるはず)がnullになってしまうことである。

例2: 値の制約付きの構造体

以下のような、値に制約の入った構造体を考える。この例は、正の数しか受け取れない数値型である。

struct PositiveInt
{
    public int Value { get; }
    public PositiveInt(int value)
    {
        if (value <= 0) throw new ArgumentOutOfRangeException(nameof(value));
        Value = value;
    }
}

C#にレコード型Method Contractsが入ると、この構造体はおそらく以下のように書ける。

struct PositiveInt(int Value) requires Value > 0;

これでValueプロパティが常に0より大きいことが保証できているように見えるが、default(PositiveInt)のせいで、Valueに0が入ることがあり得る。この値は無効なはずである。

提案: 非defaultフロー解析

現在提案されている参照型の非null保証は、フロー解析に基づいている。 値型が既定値でないことも、同じフロー解析で行えるはずである。

そこで、non-nullable reference typesに対して、non-defaultable value typesを提案したい。 何らかのアノテーション、例えば[NonDefault]属性を付けた構造体は既定値を取ってはいけないとするのはどうだろうか。

[NonDefault]
struct Wrapper<T>(T Value) whereT : class

[NonDefault]
struct PositiveInt(int Value) requires Value > 0;

このとき、non-nullable reference typesに倣って、以下のように警告を出す。

PositiveInt x = default(PositiveInt); // warning
PositiveInt? y = default(PositiveInt); // OK
PositiveInt z = y; // warning
PositiveInt w = y ?? new PositiveInt(1); // OK

non-defaultable value typesに対するT?Nullable<T>を必要としない。 何故なら、default(T)は無効であり、x.HasValueを確かめなくても、x == default(T)で値を持っていないことが確認できるからである。 non-defaultable value typesに対してはnulldefault(T)と同一視してもいいかもしれない。

また、通常の構造体の中では、non-nullable reference typesのメンバーを持てないようにすべきだろう。 non-nullable reference typesを持てるのは、参照型か、non-defaultable value typesのみである。

nullable-like型

現在、参照型とNullable<T>構造体はC#コンパイラーによって特別な地位を与えられている。 すなわち、null条件演算子(?.)とnull合体演算子(??)の利用である。

しかし、これらの型以外にも、無効なインスタンスを?.??で伝搬/差し替えしたいことがある。

この挙動はmonad的であり、LINQTask-likeを使って無理やり解決しようとしている例もある。 しかし、悪用・乱用の類であり、決して読みやすいコードにはならないだろう。 無効なインスタンスの伝搬/差し替えは、やはり、?.??を使うべきである。

例1: UnityEngine.Object

Unityのゲーム中のオブジェクトの共通基底クラスとなるUnityEngine.Objectoperator ==をオーバーロードしていて、オブジェクトが持っているネイティブ リソースがすでに破棄されているとき、オブジェクトをnull扱いする(x == nullが真になる)。

しかし、参照型に対する?.??では、オーバーロードした==は呼ばれない(brtrue命令によるnullチェックに展開される)。 そのため、以下のように、?.を使ったコードが正しく動かない。

int? X(UnityEngine.Object obj)
{
    // OK
    if (obj == null) return null;
    return obj.GetInstanceID();
}

// runtime exception
int? Y(UnityEngine.Object obj) => obj?.GetInstanceID();

これまではUnityがC# 3.0にしか対応していなかったので問題にならなかったが、 Unity 5.5でC# 6.0に対応しようとしている。 この?.の挙動がはまるポイントになるだろう。

例2: Expected

無効な値としてnullを使うことを嫌う人が一定数いるが、その理由の1つが、「なぜ無効な値を返す必要があったのか」という原因に関する情報が消えることである。 そのため、Nullable<T>の代わりに、Tと例外のunion型にあたるExpected<T>のような型を作って使おうとする人がいる。 例えば、C++でそういう動きがみられる

struct Expected<T>
{
    public T Value { get; }
    public Exception Exception { get; }
}

もしC#でもそういう型を作るのであれば、?.を使った例外の伝搬や、??を使った例外からの復帰があってもいいのではないだろうか。

Expected<string> x = new Expected<string>(new Exception());
Expected<int> y = x?.Length;
string z = x ?? "";

提案: ユーザー定義のnullable-like型

所定のパターンを実装した型であれば?.および??を使えるようにすることを提案する。 Task-likeに倣ってこれをnullable-likeと呼ぼう。

例えば、以下のようなパターンはどうだろうか。

struct NullableLike<T>
{
    public T Value { get; }
    public bool HasValue { get; }
    // propagate a valid value
    public NullableLike<U> Propagate<U>(U value);
    // propagate an invalid value
    public NullableLike<T> Propagate();
}

これで、先ほどのExpected<T>の例であれば、以下のように展開する。

Expected<string> x = new Expected<string>(new Exception());
Expected<int> y = x.HasValue ? x.Propagate(x.Value.Length) : x.Propagate<int>();
string z = x.HasValue ? x.Value : "";

ちなみに、このパターンに沿ったExpected<T>の実装は以下のようになる。

struct Expected<T>
{
    public T Value { get; }
    public Exception Exception { get; }

    public Expected(T value)
    {
        Value = value;
        Exception = null;
    }
    public Expected(Exception exception)
    {
        Value = default(T);
        Exception = exception;
    }

    public bool HasValue => Exception == null;
    public Expected<U> Propagate<U>() => new Expected<U>(Exception);
    public Expected<U> Propagate<U>(U value) => new Expected<U>(value);
}

Visual Studio 2017 Release Candidate

$
0
0

来てた。

(Connect前のリーク騒動ありましたが… Connectをもって)

もう今、クロスプラットフォームを推す状況なので、Visual Studio for Mac (Xamarin Studioのリブランディング)と、モバイル開発、Azure辺りなんかが前面に出てますが。

とりあえず、「C#な人」的に言えることは、RCになって、C# 7として予定されている機能は全部載ったはず。うちのサイトのC# 7の新機能のページに書かれている内容は全部試せるようになっているはずです。

(ソースコード取ってきてビルドして最新の状況を試していたりすると、1か月前くらいからC# 7の全機能使えていたので、自分の中ではそこまでニュース性が大きくなかったりはするんですが。やっぱりインストーラーで入れれる製品で試せるようになると嬉しい。)

ちなみに… リポジトリのマイルストーンを覗いてしまうと、 この後RC 2, RC 3が控えていることが見えてしまっているわけですが。 RC 3かー… たぶんまだ結構変更入るんだろうなー… とはいえ、今回のやつでRC (リリース候補)の名前を冠してgo-live (自己責任でプロダクション環境で使ってOK)になりました。 使うか、仕事で…

ピックアップRoslyn 7/12

$
0
0

Mads、怒涛のClose祭り。

久しぶりに、C# Design Notesが投稿されてました。というか、5月のNotesが今投稿されているなど。 要するに、Visual Studio 2015関連作業がやっと落ち着いたってことなんですかね、きっと。

C# Language Design Review, Apr 22, 2015 #3910

https://github.com/dotnet/roslyn/issues/3910

式ツリー

式ツリー化できる構文とできない構文の差が大きくなりすぎてる。 全部考えると非常に労力がかかって、その努力に見合うと思えない。

ただ、?. と string interpolation に関しては、新しいノードを導入しなくてもできそう。それくらいは考えてもよさそう。

nullable 参照型

互換性問題があるので、以下の2つのアプローチが考えられる。

  • two-type: 既存のTの意味を変えて、これを非nullにする。nullableはT?
  • three-type: 既存コードを壊さないようにTは残す。非nullがT!、nullableがT?

もちろん互換性の問題をクリアできるんなら two-type が理想的。nullチェックに対して、何らかのopt-in (コンパイラーの規定動作じゃなくて拡張にするとか、アセンブリ レベルの属性でTの意味を新旧切り替えるとか)を提供する必要がある。

Wire format

Wire format(JSONとか、要するに通信用に使う形式)問題。どうやったらJSON化とかの定義を楽にできるか、特に、シリアライズのレイヤーを緩い型付けのままどうやって強い型付けのロジックとつなぎこむか。

考えうるやり方は以下のようなもの。

  • 実行時に、辞書式のデータとインターフェイスとの相互変換する機能の類を提供
  • TypeScript みたいな「コンパイル時のみの型」(コンパイル結果からは型情報を消して辞書式アクセスに置き換え)
  • F# みたいな「型プロバイダー」の提供

C# Design Notes for May 20, 2015 #3911

https://github.com/dotnet/roslyn/issues/3911

ローカル関数、つまり、関数(メソッドとか演算子とか)の中で別の関数を定義したいという話。

まあたまにある、「メソッド X の中で、X からしか使わないメソッドを作りたい」という要件。これに対して、現状は無理で、代わりに XInternal みたいなメソッドを用意したり。あるいは、ラムダ式で代用するわけですが、ラムダ式はいくつかの制限が面倒なので改善したいと。

ラムダ式を拡張するか、ローカル関数用の構文を追加するかは検討中な物の、現状は後者、つまりローカル関数の追加で話が進んでいそう。というか、Design Notes 公開前に、pull-req とかブランチが見えてるんですが。

ちなみに、現状のラムダ式との違いは、

  • ジェネリックを使える
  • 再帰呼び出しもできる
  • イテレーターにもできる(yield return 書ける)
  • デリゲートの型を明示する面倒がいらない(ラムダ式は Func<int> みたいなやつの明示が必須)
  • というかデリゲートを介さない(オーバーヘッドが減る)

という感じ。

紹介されている用途の例:

  • イテレーターに引数チェックを追加
  • クイックソートとかの再帰アルゴリズムの実装
  • キャッシュ(2度目以降の呼び出しは辞書に格納した値を返す)
  • 特に、Taskのキャッシュ

C# Design Notes for May 25, 2015 #3912

https://github.com/dotnet/roslyn/issues/3912

いくつか、提案 issue に対してやるかどうかの判定。これに伴って、いくつかの issue が閉じられました。

その他、いくつか簡単な検討あり。

  • params IEnumerable: 性能的に、単に配列使うんじゃなく、もっといい実装を検討したほうがよさそう。
  • immutable な型に対する初期化子: 「wither」と一緒に検討したい。
  • vararg 呼び出し: params IEnumerable と一緒に検討したい。

C# Design Notes for Jul 1, 2015 #3913

https://github.com/dotnet/roslyn/issues/3913

Tuple がらみ、その後の状況(まだこれが最終決定じゃなく、いったんコミュニティからの感想受け付け用)。

名前関連

まず名前関連。あまり強く型付けしすぎないというのと、「C#のTupleはメソッド引数の延長」という発想の元、

  • 名前なし(匿名)Tuple作れるようにする
  • 同じ位置に同じ型が来ているTupleは、名前が違っても同じTuple型とみなす
    • ただし、(first: "John", doe: "Doe")みたいな、名前を明示する場合は順序の入れ替えあり
    • 名前違い同型のTupleで配列を作ると、型推論結果は匿名Tupleの配列

とかに。

エンコーディング

具体的な実装と、Tupleに対応していない他の言語・古いC#との繋ぎ。

たぶん、


public struct ValueTuple
{
    public T1 Item1;
    public T2 Item2;
    public T3 Item3;
}

みたいな型と属性で実現することになるはず。他の言語・古いC#からはItems1とかでメンバーアクセスすることになるけども、C# 7.0からはどう見えるべきか。混乱を避けるにはItems1でのアクセスは隠すべきだろう。

分解(deconstruction)

↓こういう分解用の構文を用意すべきかどうか。


(int sum, int count) = Tally(...);   

用意するならいくつか検討すべき疑問が残るものの、今のところその疑問を避けるべく、分解用の構文追加はしないでいる。

その他の課題

今のところ arity 0、1のTuple は考えていない。これらがあると便利だと思う(特に、コード生成の観点で)。でも、おそらく多くの疑問も起こすことになる。

その他いくつか、型変換(例えば、covariantな変換とか)も考えるに値するけども、今のところその検討は置いておく。

C#小ネタ集: C#をWeb上で試す

$
0
0

小ネタ

ここのところ、twitterで脊髄反射的に飛びついたきり、gistとかに書き散らかして終わりにしちゃってるネタがちらほら溜まってきたので、ここらでブログかしようかなと思います。 C#小ネタ集とか銘打って。

C#小ネタ集と言いつつ、あんまりC#が関係ない物もありますが。 結構溜まってるんで、数日は連続で書くことになるかなと思われます。

Web上で試したい

初日のネタは、C#をWeb上で触ってみようという話。

C#の入門記事とか書いてるとよく言われ続けていたのが、「Visual Studioをインストールするのがめんどくさい」って話です。 まあ、めんどくさいですよね。

そこで、今はC#にもWeb上でさらっと試すことができる場所があるので、今日はその紹介をします。

もちろん、じっくり腰を据えてコードを書くときにはもちろんVisual Studioを起動しといた方が楽なんですが。 (日々の仕事がC#とかだったりするとどうせ常時Visual Studioが起動してる状態にあるので別に立ち上げも大した面倒はありません。) とはいえ、入門向けにいきなり「Visual Studioを入れてください」は、今時ちょっとないよなぁ。

こういうのは、もうVisual Studioなどの統合開発環境に馴れてる人は忘れてる感覚です。 でも、自分が普段使わないプログラミング言語をちょっと試してみたいときのことを想像してみてください。 実務で使うわけでもなし、本腰入れてがっつり勉強するでもなし、ほんのちょっとしたネタを確認したいだけ。 そのためにわざわざ環境構築をしたいですかと言われると、まあ僕だったら嫌です。 実際、GoとかSwiftとかの挙動を見たいときは、まずWeb上で試します。 コピペ程度のコードしか書けませんが、試してみるだけならそれで十分なことが多いです。

C#をWeb上で

ということで、まずは公式サイト。

前にも1度ブログで取り上げていますが、 今は、公式サイト(dot.net!)で、試しにその場でC#を書いて実行結果が見れる機能が付いています。

dot.net内のStart codingコーナー

まあもちろん、paiza.ioとかideoneみたいなコード共有サイトを使ってもいいんですけども。 やっぱり公式サイトでやってないと、今時不格好ですよね… ということで、dot.netというドメイン取ったのと、その中でオンラインC#実行ができるようになってたことに気づいたときはうれしかったものです。

コード共有しつつ、C#をWeb上で

dot.netのオンライン エディターは保存してURLで他の人と共有したりはできません。

最近だと、適当に書き捨てのつもりで書いたコードも、 Gistとかに置いておいて、twitterとかで垂れ流したりもします。 そういう用途では、さすがに公式サイトのオンライン エディターは使えません。

そこで、ちょうどこれをやってくれるサイトがあります。 要するに、Gistと連携して、Gistに保存したC#コードを編集、実行してくれるサイト。 そういうサイトも作ってくている方がいらっしゃいます。

最新のC#をWeb上で

dot.net上にあるオンライン エディター中のC#は、「リリースされている最新版」です。 当然と言えば当然。 現時点ではC# 6ということになります。

dot.net内のStart codingはC# 6

GitHubリポジトリで開発途中の最新版を試したければ、まあ、通常、ソースコードをcloneしてきて、ビルドして、実行してみたり、そのデイリー ビルド結果を取ってきて、Visual Studio拡張などをインストールしてみる必要があります。

ですがここで、これも前にもブログで触れましたが、オンラインで割と最近のバージョンのC#を試せるサイトを作ってくれている人がいます。

RoslynのGitHubリポジトリからいろんなブランチを定期的に取ってきて、それぞれでどういう挙動をしているか確認できるサイトです。 実行結果というか、コンパイル結果のILとか、それを古いバージョンのC#で書くにはどうすればいいかという逆コンパイル結果を表示してくれます。

TryRoslyn

GitHub上のC#コードを解析

Visual Studioが常時起動しているとしても、 GitHubで見かけたC#コードを、わざわざCloneしてきて手元で見ようとはなかなか思わないですよね。 そのままブラウザーで読みたいです。

その一方で、GitHub上でコードを読んでいると、 クラス名などのシンタックス ハイライトがいまいちだったり、 「定義に移動」したくなるけど、当然GitHub上でそんなことできるはずなくイラッとしたりします。

ブラウザー中で、正しくハイライトされ、「定義に移動」とか「すべての参照を検索」ができるページがほしいです。

マイクロソフトが公式に提供している参照コードがちょうどそんな感じです。

これらのサイト、Roslynを使ったWebページ生成ツールで作ってるそうです。 ツール自体もオープンソースになっていて、以下のリポジトリで見れます。

で、要は、このツールを任意のGitHubリポジトリに対してかけてくれるサービスがあるとすごく幸せなわけですが… これがまた、そういうサービスを作ってくれてる大変親切な方がいらっしゃいます。

小ネタ コンパイル結果を覗いてみよう

$
0
0

めったにはないんですが、パフォーマンス チューニングとかを始めると、C#のコンパイル結果がどうなっているかを覗きたくなることがあります。C#の場合は、C#コード → IL (.NETの中間コード) → ネイティブ コードという2段階の変換が掛かります。

ということで、その極々まれにやりたくなることをやってみましょう。ILとネイティブ コードをそれぞれ覗いてみます。

例として、以下のようなC#コードを考えます。単純にvirtualなメソッドを呼び出すだけのコードです。主に、Mainメソッドの中身を見ていきます。

using System;

class Base { public virtual void M() => Console.WriteLine("Base.M"); }
class Derived : Base { public override void M() => Console.WriteLine("Derived.M"); }

class Program
{
    static void Main()
    {
        Base b = new Derived();
        b.M();
    }
}

IL逆アセンブル

まずはILがどうなっているかを覗いてみましょう。ildasm というツールを使います。ildasmは、以下の場所にあります。

  • "C:\Program Files (x86)\Microsoft SDKs\Windows\v10.0A\bin\NETFX 4.6.1 Tools\x64\ildasm.exe"

ちなみに、ildasmは IL disassembler (IL逆アセンブラー)の略です。

10.0Aや4.6.1という数字の部分はバージョンによって変わります。あと、x64フォルダーの中と、直上の両方にildasm.exeがあります。ildasmはバージョン違い、x86かx64か違いでいくつか見つかりますが、まあ、大体どれも同じです。

これを使って先ほどのC#コードのコンパイル結果を覗いてみると、Mainメソッドは以下のようになっています。

.method private hidebysig static void  Main() cil managed
{
  .entrypoint
  // コード サイズ       11 (0xb)
  .maxstack  8
  IL_0000:  newobj     instance void Derived::.ctor()
  IL_0005:  callvirt   instance void Base::M()
  IL_000a:  ret
} // end of method Program::Main

ILは、インスタンス生成や仮想メソッド呼び出し用の命令を持っています。

  • newobj: インスタンス生成
    • new objectの略
    • 引数には、コンストラクターを参照するためのIDを渡します
    • 命令1バイト + オペランド4バイト
  • callvirt: 仮想メソッド呼び出し
    • call virtualの略
    • 引数には、メソッドを参照するためのIDを渡します
    • 命令1バイト + オペランド4バイト
  • ret: メソッドを抜ける
    • return

ILは、アセンブリ言語ではあるものの、かなり高級言語なことがわかります。仮想メソッド呼び出しのようなオブジェクト指向プログラミングの基本操作が1バイト・1命令で書けるので、ILにコンパイルされた命令列はサイズがかなり小さくなります。

JIT結果の逆アセンブル

ILからネイティブ コードへの変換はJIT (Just-in-Time)、すなわち実行時に行われます。そこで、ILから生成されるネイティブ コードを覗くためには、実際に実行してみる必要があります。Visual Studioには、実行時にJIT生成されたネイティブ コードを覗く機能が備わっているので、これを使って覗いてみましょう。

場所は以下の通りです。メニューを [Debug][Windows][Disassembly] とたどったところにあります。ただし、このウィンドウは、デバッグ実行中しか表示されません。

クイック起動から逆アセンブルを表示

メニューから逆アセンブルを表示

これで、先ほどのプログラムを実行して、Mainメソッドの辺りでブレイクして中身を見てみると、以下のような状態になります。

逆アセンブル結果

ブレイク ポイント

この画面で、黄色い矢印は、ブレイク ポイントを仕掛けて止めた位置になります。ネイティブ コードの中から、コードを読み進めつつ自分が調べたいコードがどこかを探し出すのは至難のわざなので、こうやって、見たい場所にブレイク ポイントを仕掛けて止めるのがいいでしょう。

ただ、Releaseビルド(というか、最適化オプションを付けてコンパイル)すると、ブレイク ポイントの位置がずれて、自分が狙った位置でプログラムが止まってくれなくなります。最適化された結果のネイティブ コードを確認したいときにはちょっと困ります。

そこで、ちゃんと狙った場所でプログラムを止めるためには、Debuggerクラス(System.Diagnostics名前空間)のBreakメソッドを使うといいでしょう。このメソッドを書いた位置で必ずプログラムが止まってくれます。

        System.Diagnostics.Debugger.Break();
        Base b = new Derived();
        b.M();
        System.Diagnostics.Debugger.Break();

生成されたネイティブ コード

さて、先ほどのMainメソッドからどういうネイティブ コードが生成されるかを改めてみてみましょう。 x64環境で実行すると以下のようになります。

01960450  mov         ecx,1874E58h  
01960455  call        018630F4  
0196045A  mov         ecx,eax  
0196045C  mov         eax,dword ptr [ecx]  
0196045E  mov         eax,dword ptr [eax+28h]  
01960461  call        dword ptr [eax+10h]  

それぞれの命令がどういう意味かというと、以下の通りです。

Mainメソッドから生成されるネイティブ コード

仮想メソッドの仕組みが分かってもらえるでしょうか。2段ほど間接参照が挟まります。

この例でいうと、コンストラクター呼び出しは通常のメソッド呼び出しになるわけですが、こちらは最初からメソッドのアドレスがわかっているので直接呼び出しています(01960455の行)。

一方、メソッドMの呼び出しは、オブジェクトのヘッダー → 型情報テーブル → メソッドのアドレスと参照してからのメソッド呼び出しになります(0196045A~01960461の行)。

実行が必要

ここで話したように、JIT結果を覗くためには実際に実行してみる必要があるわけですが…

開発機上での実行だと、Visual Studio = Windowsデスクトップ = Intel CPUなわけで、x86かx64のコードを見ることになります。 一応、UWPプロジェクトを作って、ARMなWindows 10 Mobile実機をPCにつないで実行することで、ARMコードの逆アセンブルも見れることは確認しています(もっと楽な方法ないかな?)。Xamarin.Androidではダメでした。

小ネタ corefxlabとSystem.Memory

$
0
0

corefxlab

.NETチームは以下のリポジトリの1つに、corefxlabというものがあります。 名前通り、corefx (.NET の標準ライブラリ)入りを目指して試験的なコードを入れておく場所で、 安定したものからcorefxに移転になったりします。

corefxの方にいきなり入らず、一度「lab」を経ているわけで、 それなりに「これまでの.NETではあまり取り組んでなくてこなれていないもの」が含まれています。

その最たるものがパフォーマンス。 .NETは、これまでどちらかというと生産性や安全性が優先だったわけで、 「そんなコード書いてまでパフォーマンス求める?」みたいなものは少なかったんですが、 最近ではそういうコードが増えてきています。

もちろんこれまでもパフォーマンスは必要だったわけですが、 そういう場合はすぐにネイティブ コードに逃げていました。 .NETはネイティブ コードとの相互運用もしっかりしているわけで、それも1つの解決策です。

ですが、.NETもこなれてきて、.NET単体でのパフォーマンスに目を向ける余裕も出てきました。 それになんだかんだ言って、ネイティブ側、.NET側の双方で配慮しないとパフォーマンスが出ないことも多々あります。 それに取り組んでいるのがcorefxlabです。

といっても、そういう「そんなの書いてまで」的なコードは、型の中に閉じ込めています。 corefxlab中のライブラリの中身は結構な頑張り具合になっていますが、 単にビルド済みのライブラリを使うだけならそこまで変なライブラリでもないでしょう。

System.Memory (旧 System.Slices)

そんなcorefxlab出身のライブラリの中から例として1つのライブラリを紹介します。

昔はSystem.Slicesという名前でcorefxlabにありました。 というか、まあ、さらに追加で試験的なコードを書く場所として、corefxlab側にもSystem.Slicesが残ってはいます。(本当にやばそうなコードはcorefxlab側に取り残されています。Castとか。)

Slices (切れ端、断片)という名前は歴史的経緯のようです。 現在Span (区間)と呼ばれている構造体も、昔はSliceという名前だったみたいです。 今となってはどこにもSliceという名前は残っていないし、corefxに移る際にはSystem.Memoryというパッケージ名に変わりました。

Span<T>構造体の持つ機能を単純化して書くと、以下のようなコードと同類の機能を提供するものです。

struct Span
{
    public byte[] _data;
    public int _offset;
    // 本来は範囲チェックも必要なので length も必要

    public Span(byte[] data, int offset = 0)
    {
        _data = data;
        _offset = offset;
    }

    // C# 7登場に合わせて ref uint に変更予定
    public unsafe uint this[int index]
    {
        get
        {
            fixed (byte* p = _data)
            {
                var q = (uint*)(p + _offset);
                return q[index];
            }
        }
        set
        {
            fixed (byte* p = _data)
            {
                var q = (uint*)(p + _offset);
                q[index] = value;
            }
        }
    }

    public Span Slice(int startIndex) => new Span(_data, startIndex + _offset);
}

この単純化して示せている部分で言うと、Spanの機能は以下の通りです。

  • 配列の一部分だけを参照する
  • 元の型(この例だとbyte)とは違う型(同 int)の値を、配列に対して直接読み書きする

これに対してさらに、本物の Span<T> は以下のようなものです。

  • 型を自由に変えれる
    • 任意の型の配列を受け取れる
      • ジェネリックを使える
    • Cast<T, U>()で、他の型に強制変換できる
      • T型の配列に、Uの値を直接書き込める
  • ヒープ(配列)、スタック(stackalloc)、ネイティブ バッファー(ポインター)を統一的に扱える
  • fixed (それなりに負担がある操作)が不要でパフォーマンスがいい

たったこれだけのことなんですが、通常のC#では書けなかったりします(後述)。

まあ、とにかく、以下の例のようなことが、そこそこ高パフォーマンスで実行できます。

using System;

struct Data
{
    public byte A;
    public byte B;
    public short C;
    public int D;
}

class Program
{
    static void Main(string[] args)
    {
        // データ書き込み用のバッファーを byte 配列で用意
        var data = new byte[24];

        // byte のまま書き込み
        var b = new Span<byte>(data);
        b[0] = 1;
        b[1] = 2;

        // short を書き込み
        var s = b.Slice(2).Cast<byte, ushort>();
        s[0] = 0x0403;
        s[1] = 0x0605;
        s[2] = 0x0807;

        // int
        var i = s.Slice(3).Cast<ushort, uint>();
        i[0] = 0x0C0B0A09;
        i[1] = 0x100F0E0D;

        // 構造体
        var l = i.Slice(2).Cast<uint, Data>();
        l[0] = new Data
        {
            A = 0x11,
            B = 0x12,
            C = 0x1413,
            D = 0x18171615,
        };

        // 1~24が並ぶはず
        Console.WriteLine(string.Join(" ", data));
    }
}

構造体の読み書き

特に、構造体を直接読み書きできるのは、使い方次第では非常に強力です。 構造体内部のレイアウトを気にしなければ、きわめて低コストなシリアライズが実現できる可能性があります。 (データ構造を値型のみの固定長にしないといけなかったり、 文字列を直接持てなかったり、 ARMで使うならバイト境界をまたげないはずだし、制限はきつい。 可能性があると言っても設計はそこそこ難しそう。)

レイアウトに関しては、以下の点に注意が必要です。

  • エンディアン
    • サーバーもクライアントもIntel系かARM系のCPUを使う状況下ではリトル エンディアン固定で考えても特に問題は出ない
    • .NETが移植されてる先の大部分がリトル エンディアン(XBox?知らない子ですね…)
    • というか、ビッグ エンディアン自体がそこまでもう残ってない
      • 「ネットワーク エンディアン」とか言われてTCPとかの世代ではビッグ エンディアンが使われてるけども、最近だとQUICとかはリトル エンディアン
      • XBoxもXBox Oneで、PSもPS4でリトル エンディアンなCPUに変更
      • Java がビッグ エンディアンなのが唯一つらいところ…
  • バイト境界
    • 32ビットCPUか64ビットCPUかで4バイト境界か8バイト境界かが変わるので注意
    • 心配ならStructLayout属性を付ければどの環境でも同じレイアウトにできる

C# で書けないコード

「通常の C# では書けない」と言いましたが、理由は、ポインターに対する制限が強いせい:

  • ジェネリック型引数に対してポインターを作れない
  • ポインターと参照を相互変換できない
  • ポインターだけではGC的に安全に参照を持てない

じゃあ、どうやってSpan<T>構造体を書いているかというと… ILアセンブラーです。 ILが必要な部分は別の、System.Runtime.CompilerServices.Unsafeというライブラリに追い出していますが、こちらの中身はほぼ全部ILです。

  • IL: Unsafeクラス
    • C#でコンパイル → ildasm で逆アセンブル → 属性中のコードでメソッドの中身を上書きするツール(ILSub)を通す → ilasm でアセンブル という黒魔術

IL を書くのは大変だし、ビルド手順も面倒になるしでなかなか保守がしんどそう。

まあ、こういうコードをC#だけで書けるようにしたいという提案は出ているんですが、 採用されるかどうかは不透明(少なくとも直近の作業には載ってない)。 参考: Compiler intrinsics

小ネタ 式の評価順序

$
0
0

C#小ネタと言いつつ、IL小ネタになりがちだったので、今日はC#小ネタらしく。

最初にちょっとしたクイズ。 まず、中身は何でもいいんですが適当な2引数のメソッドを用意します。 例として、単純な足し算でも用意しておきましょう。

static int F(int x, int y) => x + y;

以下の2つのコードの挙動は同じでしょうか?違うでしょうか?

1つ目: 一時変数を使用

var temp = F(2, 3);
var result = F(1, temp);

2つ目: 1つの式で計算

var result = F(1, F(2, 3));

まあ、同じですね。副作用を残さない限りは。

オペランドの評価順序

ということで、今日はオペランドの評価順の話です。 上記の2つのコードを、わざと副作用付きに書き換えてみます。

そのためにとりあえず、副作用を起こすメソッドを追加。 Consoleにログ出力した後、引数を素通しするだけのメソッドです。 値渡し版と参照渡し版を用意。

// WriteLine + 素通し
static T Log<T>(T x) { Console.WriteLine(x); return x; }
static ref T Log<T>(ref T x) { Console.WriteLine(x); return ref x; }

1つ目(一時変数を使用)を改めて:

var temp = F(Log(2), Log(3));
var result = F(Log(1), temp);
2
3
1

2つ目(1つの式で計算)を改めて:

var result = F(Log(1), F(Log(2), Log(3)));
1
2
3

C#では、式の評価は、上から下へ、左から右へ逐次実行です。 なので、一時変数を導入すると結果が変わります。

1つの式の中でも、演算子の優先順位や結合方向とは無関係に、評価は一律左から右です。 代入(優先度が低い上に右から左に結合)が混ざっていようと、そのオペランドの評価は左から右です。

例えば以下の通り。

bool x = false, y = true;
Log(ref x) = Log(ref y) = Log(1) + Log(2) * Log(3) > Log(4) & Log(5) <= Log(6) - Log(7) | Log(8) == Log(9);
False
True
1
2
3
4
5
6
7
8
9

名前付き引数のオペランド評価

前節の結果はそんなに不思議なことないでしょう。コードから挙動を予想しやすいって意味では書かれてる順番通りが一番です。それに、パフォーマンス的にも悪い選択ではありません。例えば、

var result = F(1, F(2, 3));

というようなコードであれば、コンパイル結果は以下のような感じになります(必要なところを抜粋)。

ldc.i4.1
ldc.i4.2
ldc.i4.3
call       F
call       F

元のC#のオペランドと同じ、1, 2, 3の順でldc (load constant)しています。 副作用を起こすためにLogメソッドを挟む場合、このldcのところが数命令に置き換わりますが、命令の並ぶ順序はこの場合と同じです。

ということで、素直に実装すればいいだけ…

でもなくて、まあ、ほとんどの式は素直に実装していいんですが、一部めんどくさい奴がいます。例えば、名前付き引数。 以下のようなコードを書いたとします。 x, yを逆に並べています。

F(1, F(y: 2, x: 3));

すると、コンパイル結果は以下の通り。C#ソースコード上は1, 2, 3だったものが、IL的には1, 3, 2になります。

ldc.i4.1
ldc.i4.3
ldc.i4.2
call       F
call       F

この結果は副作用がないからこそ、C#コンパイラーの最適化が掛かってこうなっています。 副作用がないことがわかっている場合、評価順を並べ替えを行います。

一方で、副作用があるとそうはいきません。あくまで、C#では左から右への評価が必要です。

例えば以下のようなコードをでは、ちゃんと、1, 2, 3の順での評価が必要です。

F(Log(1), F(y: Log(2), x: Log(3)));

コンパイル結果は以下の通りです。これまでは必要のなかった一時変数(stloc: ローカル変数へのストア)が必要になります。

ldc.i4.1
call       Log
ldc.i4.2
call       Log
stloc.0    // 一時変数!
ldc.i4.3
call       Log
ldloc.0
call       F
call       F

ちなみに、これ、C# 4.0の時にはバグってて評価順が狂ってた(逆順になってた)そうです。 C# 5.0でバグ修正した結果、破壊的変更になっていたり(めったにこんなコード書かない上に、バグの修正なので特に問題にはならず)。

タプルの要素の評価順序

もう1個変な例を挙げておきましょう。C# 7で導入されるタプルと分解で、以下のように、swapコードを書けるようになりました。

var x = 1;
var y = 2;
(x, y) = (y, x);
Console.WriteLine($"{x}, {y}");

これ、同じ処理をタプルを使わず書くとすると、まあ、以下のようにしますよね。

var temp = x;
x = y;
y = temp;

こいつらにも副作用を加えてみましょう。

まず、タプルを使うもの。

var x = 1;
var y = 2;
(Log(ref x), Log(ref y)) = (Log(y), Log(x));

ちゃんと、これも左から右に順に評価されます。すなわち、ref x, ref y, y, xの順。なので結果は以下の通り。

1
2
2
1

で、これをタプルなしで副作用も込みで全く同じ挙動にするためにはどうするか。

先ほどの類推で以下のように書いてしまうと、副作用の順序が変わります。

var temp = Log(x);
Log(ref x) = Log(y);
Log(ref y) = temp;
1
1
2
2

正しくは、以下のように書かないと同じにはなりません。

ref var rx = ref Log(ref x);
ref var ry = ref Log(ref y);
var vy = Log(y);
var vx = Log(x);
rx = vy;
ry = vx;

まとめ

副作用があっても常に一定の結果になるように、C#では、オペランドの評価順が常に左から右、書かれている通りの順序で行われます(まあ、割かし最近のプログラミング言語では大体同じで順序保証があります)。

ただ、順序保証がない場合に比べて、保証のためのコストがちょっとだけかかります(なので、古いプログラミング言語では「コンパイラーの実装ごとに変えていい」となっているものも結構あります)。

まあ、これだけ書いておいて身もふたもない結論で締めますが、副作用起こすような式を書くやつが悪い。


小ネタ オブジェクト初期化子

$
0
0

今日の小ネタは、オブジェクト初期化子について、意外と知られてないらしい話。

問い

唐突ですが問題です。以下の3つのコードはそれぞれどういう意味でしょう。

var x = new Line
{
    A = new Point { X = 1, Y = 2 },
    B = new Point { X = 3, Y = 4 },
};
var x = new Line
{
    A = { X = 1, Y = 2 },
    B = { X = 3, Y = 4 },
};
var x = new Line
{
    A = new { X = 1, Y = 2 },
    B = new { X = 3, Y = 4 },
};

ついでに、将来的に認められるようになるかもしれないパターンをもう1つ。

var x = new Line
{
    A = new() { X = 1, Y = 2 },
    B = new() { X = 3, Y = 4 },
};

答え合わせの前に

答えを説明する前に、コード中に出ていた2つの型、PointLineについて。

まず、Pointの方は、どのパターンであっても以下のような感じである必要があります。

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

構造体でもいいんですが、その場合、Line側に参照戻り値が必要になります。

Lineの方は、2パターンあります。 1つは、プロパティが書き換え可能なもの。

class Line
{
    public Point A { get; set; }
    public Point B { get; set; }
}

もう1つは、getのみのプロパティに対して、コンストラクター、もしくは、プロパティ初期化子で初期値を与えているものです。

class Line
{
    public Point A { get; } = new Point();
    public Point B { get; } = new Point();
}

答え

パターン1

パターン1のやつは、一番シンプルというか、多くの方がこれのつもりでオブジェクト初期化子を使っているのではないかと思います。

public static void Q()
{
    var x = new Line
    {
        A = new Point { X = 1, Y = 2 },
        B = new Point { X = 3, Y = 4 },
    };
}

public static void A()
{
    var x = new Line();
    x.A = new Point { X = 1, Y = 2 };
    x.B = new Point { X = 3, Y = 4 };
}

展開結果を見ての通り、x.Ax.Bに対する代入が発生するので、A, B は set アクセサーを持つ必要があります。

その結果、書き換え可能な方の Line 実装に対してならこの構文を使えますが、 getのみの方の Line 実装には使えません。

パターン2

意外と知られてないのはこいつですね。

public static void Q()
{
    var x = new Line
    {
        A = { X = 1, Y = 2 },
        B = { X = 3, Y = 4 },
    };
}

public static void A()
{
    var x = new Line();
    x.A.X = 1;
    x.A.Y = 2;
    x.B.X = 3;
    x.B.Y = 4;
}

オブジェクト初期化子は再帰的に書けます。 その場合、この例の x.A.X というように、全部展開されて、そこに代入が行われます。

ここで注意が必要なのは、x.A の初期化は外からは行われないということです。 もしも、Line のコンストラクター内で A を初期化していなければ、当然のようにぬるぽります。

つまり、getのみの方の Line に対しても使える代わりに、 書き換え可能な方の Line 実装みたいにコンストラクター内での初期化をしていないものに対してこの書き方を使うと実行時エラーを起こします。

パターン3

並べられると、似たようなもので全然違う結果になるので気持ち悪くなりますが、 まあ、個別によく見るとそんなに不思議なものではないと思います。

パターン3は、単に匿名型を代入しているだけ。

public static void Q()
{
    // 実はコンパイル エラー
    var x = new Line
    {
        A = new { X = 1, Y = 2 },
        B = new { X = 3, Y = 4 },
    };
}

public static void A()
{
    var x = new Line();
    // この new { } は匿名型。
    // A, B は Point 型なので、匿名型だと型があってない。
    // つまり、コンパイル エラー: 匿名型を暗黙的に Point に変換できません
    x.A = new { X = 1, Y = 2 };
    x.B = new { X = 3, Y = 4 };
}

C#だと、コンパイル時にエラーなことがわかるんでそんなに問題はないと思うんですが。 もしも実行してみないとこの差がわからないとか言われたらちょっと殺意を覚えますね…

パターン4

パターン4は将来の話。今現在はコンパイル エラーになります。 どういう構文が追加されそうかというと、左辺からの型推論です。

public static void Q()
{
    var x = new Line
    {
        A = new() { X = 1, Y = 2 },
        B = new() { X = 3, Y = 4 },
    };
}

public static void A()
{
    var x = new Line();
    // new() って書き方で、左辺から型推論してくれる構文が入りそう。
    // この場合、A, B が Point なので、new () は new Point() の意味。
    x.A = new Point { X = 1, Y = 2 };
    x.B = new Point { X = 3, Y = 4 };
}

ものすごいほしい型推論機能です。早く実装されないかな…

とはいえ、まあ、こういう、並べると気持ち悪いコードが書けますよ、と。 { }new { }new() { }で全部意味が違うという。

小ネタ コレクション初期化子

$
0
0

昨日のオブジェクト初期化子に続き、今日はコレクション初期化子の話。

コレクション初期化子ってのは、例えば以下のようなやつのことです。

// この、{} の部分がコレクション初期化子。
var x = new List<int> { 1, 2, 3, 4, 5 };

このコレクション初期化を使える条件は、Add メソッドを持っていて、かつ、 IEnumerable を実装していることです。

最低限の実装をしてみると、以下のような感じ。

class MyList : IEnumerable
{
    List<int> _list = new List<int>();
    public void Add(int value) => _list.Add(value);
    public IEnumerator GetEnumerator() => _list.GetEnumerator();
}

static void ListSample()
{
    var x = new MyList { 1, 2, 3, 4, 5 };

    foreach (var item in x)
        Console.WriteLine(item);
}

この、コレクション初期化子は以下のように展開されます。

var x = new MyList();
x.Add(1);
x.Add(2);
x.Add(3);
x.Add(4);
x.Add(5);

ここで生じる疑問があります: IEnumerable の実装、要るの?

依存は避けれるなら避けるべきもの

だって、Addメソッドしか使ってなくない?IEnumerableは何にも使ってないよね?

だいたい、C#の文法がIEnumerableに依存しちゃうの? 例えば、foreachであればGetEnumeartorメソッドさえ持っていれば、別にIEnumerableインターフェイスを実装していない型であっても使えます。 LINQもそうで、SelectWhereなど、所定のメソッドさえ持っていれば、クエリ式を使えます。

最近、Build InsiderでTask-likeの話とかも書きましたけど、 言語の文法が何かの型に依存するというのはリスクを持ちます。 可能なら避けるべきものです。

で、コレクション初期化子、IEnumerable 要るの?

たぶん、誤用の防止

まあ、問題になるとすると以下のような例ですかね。

struct Adder
{
    public int Add(int x, int y) => x + y;
}

static void AdderSample()
{
    // こういう誤用を防ぎたかったのかなという気はする
    var x = new Adder
    {
        { 2, 1 },
        { 3, 4 },
        { 5, 9 },
    };
}

Addメソッドだけを条件にしてしまうと、こういうコードが書けてしまう。 で、このAddの呼ばれ方だと、何の役にも立たないわけです。 Adderの内部状態を変えたいわけじゃなてく、単なるオペレーターなわけでして。

もちろん、IEnumerableの実装を義務付けたところで、あえて濫用することはできます。 例えば、以下のような書き方なら現在の仕様でもできます。

class Accumulator : IEnumerable
{
    public int Sum { get; set; }
    public int Add(int value) => Sum += value;

    // 空実装してしまえば、コレクション初期化子の乱用可能
    public IEnumerator GetEnumerator() => throw new NotSupportedException();
}

static void AccumulatorSample()
{
    // コレクションでもないんでもないけど、コレクション初期化子を使える
    var x = new Accumulator { 1, 2, 3, 4, 5 };
    Console.WriteLine(x.Sum); // 15
}

とりあえず空実装。

まあ、意図的にやってるので大して問題にはならないんですが。 Adderみたいなのが意図せずコレクション初期化子で使われるのだけは防止したかったんですかね… そのためにGetEnumeratorの空実装しろと…

小ネタ インデックス付き foreach

$
0
0

foreach ステートメントで、インデックス付きで列挙したいことが時々あります。 今回は、そういうときの対処方法について。 というか、C# 7が待ち遠しくなる話。

配列やList<T>であれば以下のようにも書けます。

for (int i = 0; i < length; i++)
{
    var item = array[i];
    Console.WriteLine($"index: {i}, value: {item}");
}

IEnumerable<T>の場合にはこうは書けず、 現状だと、以下のようにループの外側に1個変数を作る必要があったりします。

var i = 0;
foreach (var item in items)
{
    Console.WriteLine($"index: {i}, value: {item}");
    i++;
}

ループの外側に変数iが漏れるのが嫌なのと、 あと、continueが絡むとi++するのが大変になったりします。

Selectのオーバーロードの1つを使って、以下のような書き方も一応できます。

foreach (var x in items.Select((item, index) => new { item, index }))
{
    Console.WriteLine($"index: {x.index}, value: {x.item}");
}

ただ、これだと無駄にオブジェクトがnewされます(匿名型は参照型なのでヒープ確保が発生します)。ループの中でのヒープ確保はできれば避けたい負担です。 それに、x.itemみたいな書き方がちょっと嫌な感じです。

C# 7であれば、タプルを使うのがいいかもしれません。ついでに、分解構文も使えば多少すっきりします。

foreach (var (item, index) in items.Select((item, index) => (item, index)))
{
    Console.WriteLine($"index: {index}, value: {item}");
}

タプルは値型なので、いくらかヒープ確保が減ります。 また、分解があるおかげでx.とか書く必要がなくなりました。

でもまだちょっとうっとおしいですね。 (item, index) => (item, index)とか毎度書きたくないです。 拡張メソッドを用意しておきたいところ。

public static partial class TupleEnumerable
{
    public static IEnumerable<(T item, int index)> Indexed<T>(this IEnumerable<T> source)
    {
        if (source == null) throw new ArgumentNullException(nameof(source));

        IEnumerable<(T item, int index)> impl()
        {
            var i = 0;
            foreach (var item in source)
            {
                yield return (item, i);
                ++i;
            }
        }

        return impl();
    }
}

これで、以下のように書けます。

foreach (var (item, index) in items.Indexed())
{
    Console.WriteLine($"index: {index}, value: {item}");
}

これなら、まあ、悪くはなさそうです。 こういうメソッド、そこそこ使うことがありそう。

ちなみに、今回はイテレーターを使ってIndexedメソッドを実装しましたが、ガチガチに最適化するなら、以下のように、構造体で実装してヒープ確保をなくすべきかもしれません。

小ネタ チェック例外とUnion型

$
0
0

今日の話は、C# 7で入る型スイッチや、さらにその先で予定されている、パターン マッチングや、代数的データ型(特にUnion型)というもので、例外処理の仕方が変わるかも、という話です。

例外

例外にはいくつかの性質があります。 まず、挙動としては、以下の2点が特徴的でしょう。

  • ロング ジャンプ: 複数のメソッドをまたいで、遠く離れた場所に移動する
  • 型スイッチ: 例外の型を見て分岐処理を行う

例外の使い方・挙動

例外に含められる情報としては、以下の2つが特に有用でしょう。

  • メッセージ(Messageプロパティ): 発生理由を、自由な文章で伝えられる
  • スタック トレース(StackTraceプロパティ): 発生個所を、呼び出し階層すべて含めて伝えられる

例外に含められる情報

これらは便利ではあるんですが、少々過剰気味ではあります。 特に、ロング ジャンプとスタック トレース取得はそこそこ負担が大きな処理で、 例外をthrowしてしまうと、正常動作と比べて2~3桁くらい動作が遅くなったりします。

この大きな負担が嫌で、例外を嫌う人も多いわけです。 少なくとも、準正常系に対して例外を使うのは過剰スペックじゃないかという話になります。

じゃあ、代わりに何が使えるかというと、最近注目されているのがUnion型とパターン マッチング。

例外の使い方

先に過剰スペックじゃないかという話の方を。

例外って言っても、以下の2つに分けられたりするわけです。

  • 異常系: 完全に想定外の事態
  • 純正常系: あんまり起きてほしくはないけども、想定の範囲内で、対処方法がわかってて処理を続けられる

異常系

異常系の「完全に想定外」っていうのは、まあ、「バグが残ってる」という類なので、理想を言えばテストの段階でなくしたいものです。

といっても、バグを100%取れれば苦労はしないわけで、実際には残ります。 バグが残っていた場合の対策も必要なわけで、たいてい、fail-fastでログだけ残して処理を止めます。

処理の止め方としては、まあ状況によってはFailFastメソッドとかで完全にプロセス停止させてもいいんですが、だいたいは今いるページのリロードとか、ログオフしてログイン画面に戻すとか、そういう対処になるんじゃないでしょうか。

こういう用途には、ロング ジャンプやスタック トレース情報は非常にありがたいです。

  • 根っこの方で1か所catchを書くだけでいい
  • どこで発生したかがわかって、その後、バグ修正がはかどる

ということで、こういう用途なら、例外は便利なものです。

準正常系

一方で、準正常系、要するに「嫌だけど、まあ避けれない」、「起きることは知ってる」、「対処もできる」みたいなものもあって、こちらにロング ジャンプやスタック トレースが要るかと言われると、かなり微妙。たいていは、呼び出し元のメソッドですぐにエラーを調べて対処するものなので、長距離移動はしないし、どこで発生したかもスタック トレースを見るまでもなくわかっています。

ということで、C#だと、あんまりこの用途で例外を使うことはないんじゃないかと思います。

一方で、チェック例外を持っているJavaだと、

  • 異常系には実行時例外を使う
  • 準正常系にはチェック例外を使う

みたいな例外の使い方をしたりもします。 チェック例外にはチェック例外のいいところがあって、それが準正常系の処理と相性が良かったという面があります。

チェック例外

準正常系の例として、平方根を求める関数を考えてみましょう。 いろんな言語で、標準の平方根を求めるsqrt関数は以下のような実装になっています。

  • 浮動小数点数(C# だとdouble)を受け取って、浮動小数点数を返す
  • 負の数が来るなど、計算できない値だった場合、戻り値はNaN (Not a Number)を返す

これを、諸事情あって、以下のような挙動に変える必要があったとします。

  • 引数がそもそもNaNだったらInvalidArgumentエラーとする
  • それ以外で、戻り値がNaNになる(引数が負の数)場合、InvalidResultエラーとする
  • それ以外は普通に標準ライブラリを使って平方根を計算して返す

チェック例外を持つJavaだと、以下のように書いたりします。

まず、エラーを表現するために、例外型を定義。

class InvalidArgumentException extends Exception { }
class InvalidResultException extends Exception { }

平方根を求める関数は、以下のような静的メソッドになります。

private static double sqrt(double value) throws InvalidResultException, InvalidArgumentException
{
    if (Double.isNaN(value)) throw new InvalidArgumentException();
    if (value < 0) throw new InvalidResultException();
    return Math.sqrt(value);
}

このメソッドを呼び出す側は例えば以下のようになるでしょう。

try
{
    double y = sqrt(x);
    System.out.println(y);
}
catch(InvalidArgumentException b)
{
    System.out.println("引数の時点でおかしな値");
}
catch(InvalidResultException a)
{
    System.out.println("計算結果がおかしな値");
}

チェック例外のメリットは2つあります。

  • 型の明示(denotation): メソッド定義側で、どういう例外が出るかを明示できる
  • 完備検査(completeness check): 呼び出し側で、出る可能性のある例外をcatchしなかったらコンパイル エラーにできる

この2つは非常に重要な観点なので確かに欲しい機能なんですが、 その一方で、以下の2点が不満という話になります。

  • 戻り値とthrows句の2カ所に分かれた書き方は本当にいいのか
  • おまけでロング ジャンプやスタック トレース情報が付いてくるのは過剰ではないか

Union型とパターン マッチ

ロング ジャンプやスタック トレース取得は要らない、 型の明示と完備検査はほしい… となったときに、関数型言語の類だとちょうどそういう仕組みを持っていたりします。 Union型(直和型)とパターン マッチです。

例えばF#で先ほどと同様のsqrt関数を書こうと思うと、 まず、正常な値か、InvalidArgumentエラー、InvalidResultエラーを表すUnionを作ります(F#の場合は判別共用体(discriminated union types)と言います)。

type SqrtResult =
    | Value of Double
    | InvalidArgument
    | InvalidResult

これを使って、平方根を求める関数は以下のように書けます。

let sqrt x =
    if Double.IsNaN(x) then InvalidArgument
    elif x < 0.0 then InvalidResult
    else Value(Math.Sqrt(x))

呼び出す側は以下のとおり。

match sqrt(x) with
| Value y        -> Console.WriteLine(y)
| InvalidArgument -> Console.WriteLine("引数の時点でおかしな値")
| InvalidResult   -> Console.WriteLine("計算結果がおかしな値")

ちゃんと要件は満たしています。

  • 型の明示: 判別共用体を使って、InvalidArgumentInvalidResultが返る可能性を明示できている
  • 完備検査: 判別共用体に対するmatch (C#でいうswitch)では、条件が足りていなかったら「Incomplete pattern matches」(パターンが不完全です)という警告が出る
  • ロング ジャンプやスタック トレース取得は要らない

完備検査の課題

チェック例外にしても、Union型にしても、完備検査をするためにはちょっとした課題があったりします。 何かというと、後からのパターン追加がかなり厳しい。

エラーのパターンを増やしたとしましょう。例えば、この例で言うと、NaNだけじゃなくて∞もエラーにしたくなって、InvalidArgumentとは別にInfiniteエラーというのを返したくなったとします。

private static double sqrt(double value) throws InvalidResultException, InvalidArgumentException, InfiniteException
{
    if (Double.isInfinite(value)) throw new InfiniteException();
    if (Double.isNaN(value)) throw new InvalidArgumentException();
    if (value < 0) throw new InvalidResultException();
    return Math.sqrt(value);
}
let sqrt x =
    if Double.IsInfinity(x) then Infinite
    elif Double.IsNaN(x) then InvalidArgument
    elif x < 0.0 then InvalidResult
    else Value(Math.Sqrt(x))

呼び出し側も修正しないと、Javaの場合はコンパイル エラー、F# の場合は警告になります。(ビルド設定で「警告もエラー扱いする」という項目もある以上、警告の追加も破壊的変更です。)

おかしな挙動をするくらいならコンパイル時にエラーや警告が出た方が傷は浅くて済むんですが、コードが大規模化してくると結構厄介な問題になります。

  • 関数を作る人と使う人が全然違う。場合によっては組織をまたぐ
    • 使う人にとっては意図しない(例えば別件にかかりきりで修正できないような)タイミングでも、修正を強要される
  • パッケージも分かれる
    • 使う側の再コンパイルなしでパッケージのバージョンを上げたいのに、再コンパイルしてみないと結局、完備検査が働かない

C# で準正常系の処理

さて、やっとC#に関してなんですが、C#で準正常系の処理を書くとしたら、

  • 現状: エラー コードを返すような昔ながらの処理に先祖返り
    • 型の明示はできてる
    • 完備検証はできてない
  • 現在検討されていること:
    • C#にもUnion型の追加
    • switchステートメントでの完備検証の追加

という感じになっています。

現状だと、どうしても以下のような感じに落ち着いたりします。

まず、エラーを示すための列挙型を作ります。

using System;

enum ErrorType
{
    None,
    InvalidArgument,
    InvalidResult,
}

class Program
{
    static ErrorType TrySqrt(double x, out double y)
    {
        y = 0;
        if (double.IsNaN(x)) return ErrorType.InvalidArgument;
        if (x < 0) return ErrorType.InvalidResult;
        y = Math.Sqrt(x);
        return ErrorType.None;
    }

    static void Main()
    {
        var data = new[] { double.NaN, -1.0, 2.0 };

        foreach (var x in data)
        {
            Console.Write(x + " → ");
            double y;
            switch (TrySqrt(x, out y))
            {
                case ErrorType.None:
                    Console.WriteLine(y);
                    break;
                case ErrorType.InvalidArgument:
                    Console.WriteLine("引数の時点でおかしな値");
                    break;
                case ErrorType.InvalidResult:
                    Console.WriteLine("計算結果的におかしな値");
                    break;
            }
        }
    }
}

正直なところ、この先祖返り感は結構な残念さではあるんですが。 先祖返りが起こるってのは、現状(例外機構)に対して結構な不満があるという証でもあります。

将来的に、これがどうなってほしいかというと、以下のような感じでしょうか。

using System;

enum ErrorType
{
    InvalidArgument,
    InvalidResult,
}

class Program
{
    static double | ErrorType M(double x)
    {
        if (double.IsNaN(x)) return ErrorType.InvalidArgument;
        if (x < 0) return ErrorType.InvalidResult;
        return Math.Sqrt(x);
    }

    static void Main()
    {
        var data = new[] { double.NaN, -1.0, 2.0 };

        foreach (var x in data)
        {
            Console.Write($"{x} → ");
            Console.WriteLine(M(x) match
            {
                double y: y.ToString(),
                ErrorType.InvalidArgument: "引数の時点でおかしな値",
                ErrorType.InvalidResult: "計算結果的におかしな値",
            });
        }
    }
}

パーツごとに説明すると、以下のようなものから成り立ちます。

  • Union型(確度 低)
    • double | ErrorTypeみたいな書き方でUnion型を定義
    • この書き方は TypeScript を習ったもの
    • C#でこの書き方ができるようになるかというと微妙。後述する「abstract sealed」なクラスならできるようになるかも
  • 型による分岐(C# 7で入る)
    • switchステートメントのcaseや、is演算子で、型を見た分岐ができるようになる
  • match 式(確度 高)
    • switchを式にしたもの
  • Union型とEnum型の完備検査(確度 高)

Union型は、要するに、A | B と書いた場合、ABかのどちらかの値を持つ型です。 一応、これをC#で似たようなことしようと思うと、以下のように書くことになります。要するに、ただ単にクラスの継承階層を作るだけ。

abstract class AorB { }
class A : AorB { }
class B : AorB { }

ここで問題は、AorBを赤の他人が継承して使えることです。 「完備検査の課題」で説明した通り、完備検査をする場合、パターン追加が破壊的変更を起こします。 AorBを作った人が意図して破壊的変更を起こすならまだしも、 作った人でも使う人でもない第3者のせいでコンパイル エラーが起きるのはさすがに許容できないでしょう。

一応、第3者による継承を防止する手段はあって、以下のように書きます。

// AorB 自体のインスタンスを作れないように abstract
abstract class AorB
{
    // クラスの外からの派生を禁止するためにコンストラクターを private に
    private AorB() { }

    public class A : AorB { }
    public class B : AorB { }
}

ですが、この書き方はネストするのがうざい。 ということで、以下のような書き方で、上記のネスト状態のコードに展開したいという案が出ています。

// 継承前提(abstract)なんだけど、意図しない継承はさせたくない(sealed)という意図で
// abstract sealed と付ける
abstract sealed class AorB { }
class A : AorB { }
class B : AorB { }

abstract sealedという、現在のC#的にいうと矛盾する2つの修飾子を組み合わせて、 継承して使う前提だけど、意図しない第3者には継承させないという型を作ることができます。 こういう構文で、Union型に類するものを提供しようという感じです。

ちなみに、F#の判別共用体は、内部実装的にはこれと同じようなコード(privateなコンストラクター + ネストしたクラス)を生成しています。

小ネタ 同期コンテキストを拾わないTask型

$
0
0

今日も小ネタというかC# 7ネタというか、C# 7に合わせて1個ライブラリ書いたというか。

勢いで、こんなライブラリ1個作ってしまいました。 C# 7向けです(半分くらいはC# 5.0でも使えるものですが)。

ということで、C# 7の機能の1つについて説明。 C# 7で以下のような機能が入ります。

ほぼ、ValueTaskのために入った機能なんですが、まあ、せっかくなので他でも使ってみようというのが今日の話。

同期コンテキスト

C#に限らずいろんなプログラミング言語で、非同期処理の後にメイン スレッドに戻ってこないといけないという制約があったりします。 特に、GUIプログラムの開発環境だとたいてい、UIがらみのクラスはメイン スレッド(UI末ラッド)からしか触れないとかそういう制約があります。

こういう、「メイン スレッドに戻らないといけない」とか、その場その場にある文脈を同期コンテキストと言います。 C#でawaitを使って非同期処理をする場合、 awaitした時点で同期コンテキストを持っていたら、 それを拾って元のコンテキストに戻ってくるようになっています。

同期コンテキストに関する説明、参考URLを探そうとしたものの… 意外とこの時期、真面目に自分のサイトを更新してなくて「書きかけ」ばっかり… 一番真面目に書いてあるのが@ITで書いたSilverlightの記事という… (XamarinとかASP.NET Coreあたりで書き直したい気もしつつ。)

とにかく、C#には同期コンテキストってものがあって、通常、awaitするとそのコンテキスト拾って、スレッド プールからメイン スレッドとかに自動的に戻ってきてくれる仕組みが入っています。

コンテキスト拾いすぎ

とはいえ、これはアプリのレイヤーのためにある機能であって、 逆に、ライブラリの中でコンテキストを拾っちゃうとまずかったりします。 意図しないタイミングでメイン スレッドを止めてしまって、デッドロックを起こしたりします。

ということで、ライブラリ作者は、同期コンテキストを拾わないようにするために、以下のようなコードを書くことを強要されます。

// ConfigureAwait で同期コンテキストを拾うかどうか設定できる
// 引数を false にすると拾わない
await FAsync().ConfigureAwait(false);

ライブラリを書く側の人は毎度毎度、これで苦労します。 正直に言って結構うざい…

コンテキストを拾わない Task

ってことで作ったのが ContextFreeTask。 コンテキストを拾わないTaskです。

冒頭の通り、C# 7では非同期メソッドの戻り値の型を任意に変えれるようになったので、自作してみました。

// Task の代わりに ContextFreeTask を非同期メソッドの戻り値にできる
// この中にある await は同期コンテキストを一切拾わない
private async ContextFreeTask FAsync()
{
    // この時点でどんなコンテキストで動いていようと…
    await Task.Delay(100);
    // コンテキストは拾われないので、元のコンテキストには戻らない
    await Task.Delay(100);
    // 同上、戻らない
}

private async Task GAsync()
{
    // ContextFreeTask に対する await もできる
    // この await も同期コンテキストを拾わない

    await FAsync();
    // コンテキストは拾われない
}

概ね、以下のコードと同じ挙動になります。

private async ContextFreeTask FAsync()
{
    await Task.Delay(100).ConfigureAwait(false);
    await Task.Delay(100).ConfigureAwait(false);
}

private async Task GAsync()
{
    await FAsync().ConfigureAwait(false);
}

戻り値があるとき用、すなわち、Task<TResult> の代わりの ContextFreeTask<T> もあります。

private async ContextFreeTask<string> HAsync(int n)
{
    await Task.Delay(100);
    return n.ToString();
}

中身

Task 1個だけ持つ薄いラッパー構造体で、 ほとんどの処理はTaskや、そのawaiter、async method builderへの丸投げです。 その手前にConfigureAwait(false)SetSynchronizationContext(null);を挟んでいるだけ。

public struct ContextFreeTask<T>
{
    public Task<T> Task { get; }
}

public struct ContextFreeTaskAwaiter : ICriticalNotifyCompletion
{
    private readonly Task _value;
    public void OnCompleted(Action continuation) => _value.ConfigureAwait(false).GetAwaiter().OnCompleted(continuation);
}

public struct AsyncContextFreeTaskMethodBuilder
{
    private AsyncTaskMethodBuilder _methodBuilder;

    public void AwaitOnCompleted<TAwaiter, TStateMachine>(ref TAwaiter awaiter, ref TStateMachine stateMachine)
        where TAwaiter : INotifyCompletion
        where TStateMachine : IAsyncStateMachine
    {
        SynchronizationContext.SetSynchronizationContext(null);
        _methodBuilder.AwaitOnCompleted(ref awaiter, ref stateMachine);
    }
}

やってみて

とりあえず小ネタというか、単にライブラリ紹介だったわけですが…

まあ、この、任意の型を非同期メソッドの戻り値に使える機能、 「C# 7の新機能紹介」でもどう取り扱うかは結構悩みまして。 何せ、実用例がほんとに少ない。 なので、どうしても「ほぼ ValueTask 専用です」的な書き口に。 (ValueTask だけで十分価値は高いんですが。)

一応小ネタっぽい話もすると、この機能、C#チームからも

We estimate that in the eventual C# ecosystem maybe 5 people will write tasklike types that get mainstream adoption.

我々の見積もりでは、最終的に C# エコシステム内において、たぶんせいぜい5人くらいが、メインストリームに採用される tasklike 型を書くことになるだろう。

とか言われています(参考: C# LDM notes from 2016.08.24)。 せいぜい5人。

たぶん確実に使われそうなのとしては、

の3つは確定。 残りせいぜい2個ですか。

まあ、ContextFreeTaskも、作ったはいいけど、大々的に使うかどうかはちょっと悩ましかったり。 誤用(逆に使うとまずいアプリのレイヤーで使われたり。ライブラリでも、publicなところで使ってしまうと、アプリ側でコンテキストを拾い損ねる事態になりそう)がちょっと怖そうですし。

小ネタ フォーマット文字識別子

$
0
0

いい加減、小ネタらしい小ネタを書かないとタイトル詐欺臭いのでほんとに小ネタを。

C#では、以下のようなコードが書けたりします。変数abを用意して、a\u200dbって変数に書き込むと、abの値が変わるという。 要するに、この2つは識別子としては同一扱いされます。

using System;

class Program
{
    static void Main()
    {
        var ab = 0;
        a\u200db = 1; // ab と同じ扱い。\u200d は Zero Width Joiner
        Console.WriteLine(ab); // 1
    }
}

この挙動を説明するには、以下の2つの仕様が出てきます。

1つのUnicodeエスケープ シーケンスは、\uに続けて4桁の16進数を打つか、\Uに続けて8桁の16進数を打つと、その番号に対応したUnicode文字に変換されるというものです。このエスケープ シーケンスは、文字列リテラルの外、どこででも有効です。例えば、以下のようなことも可能。aの文字は、UnicodeではU+61です。

using System;

class Program
{
    static void Main()
    {
        var a = 0;
        \u0061 = 1;
        Console.WriteLine(a);
    }
}

もう1つは、フォーマット文字は識別子に含められるけど、除外して考えるという仕様。 フォーマット文字ってのは、文字を描画方法とかを指定するための不可視文字で、例えば以下のようなものがあります。

  • Zero Width Joiner (U+200D): その左右の文字が不可分なことを表す。ゼロ幅接合子。略称 ZWJ。
  • Left-to-Right Mark (U+200E): 文字を左から右に向かって描画すべきということを表す
  • Right-to-Left Mark (U+200F): 同上、右から左

一部の自然言語でこの手の制御が必要だけども、見えない文字だからこの文字のあるなしで別識別子にはしたくないっていうことでしょう。

これら2つを組み合わせた結果が冒頭のコードです。a\u200dbは、abの間にZWJを挟んだ状態で、結果的に、識別子としてはZWJが無視されて、abとして扱われます。

まあ、見えない文字とか無視すべきですよね。 普通、見えない文字はそもそも識別子として使えなくしてるものなんですが、Right-to-Left Markとかは、アラビア語プログラミングとかすると使うかもしれないですもんねぇ。「使う」というか、もしかしたらエディターによって勝手に挿入されるかもしれず。 その場合、無視すべき、ということなんでしょう。

無視しないプログラミング言語もありますが

Unicode Consortium で規定

この挙動、どうも、Unicode Consortiumのレポートに基づいてるみたいです。

Unicode Technical Report #15の、Annex7: Annex 7: Programming Language Identifiersのところ。 プログラミング言語の識別子に使える文字はどうあるべきか、みたいな話が結構詳細に書かれています。 これに沿っている言語は他にもありそうなので、試してみるといいかも。

小ネタ 隠しメンバー

$
0
0

今日は、C# 上からは見えない隠しメンバーが作られるという話。 「覚えがないのになぜか『すでに定義があります』っていう名前被りのコンパイル エラーが出た」なんてこともあり得ます。

C# は .NET 向け言語の代表的な位置づけの言語ではありますが、だからと言って、C# の機能と .NET IL (中間言語)の機能は同じではありません。 C# コンパイラーによって、ILのレベルでは結構身に覚えのないメンバーが追加されます。

例えば以下のようなコードを書くだけで、自動的に追加されたメンバーがたくさん出てきます。

using System;
using static System.Console;
using static System.Reflection.BindingFlags;

class C
{
    public int X { get; set; }
    public int this[int index] { get { return index; } set { } }
    public event Action E;
}

class Program
{
    static void Main()
    {
        foreach (var x in typeof(C).GetMembers(Public | NonPublic | Instance | DeclaredOnly))
        {
            WriteLine(x.Name);
        }
    }
}

実行結果は以下の通り。

get_X
set_X
add_E
remove_E
get_Item
set_Item
.ctor
X
Item
E
<X>k__BackingField
E

今日はこれらについて説明して行きます。

コンストラクター

これはまあ、わかりやすいですね。C# のクラスは、明示的にコンストラクターを書かなくてもnew Class()と書けるわけで、暗黙的にコンストラクターが1つ作られています(クラスで、コンストラクターを1つも書かなかった場合だけ)。

リフレクション的には、コンストラクターは.ctorという名前で見えます。 ちなみに、生成されるILを覗いてみると以下のような感じ。

.class private auto ansi beforefieldinit C
       extends [mscorlib]System.Object
{
  .method public hidebysig specialname rtspecialname 
          instance void  .ctor() cil managed
  {
    .maxstack  8
    IL_0000:  ldarg.0
    IL_0001:  call       instance void [mscorlib]System.Object::.ctor()
    IL_0006:  nop
    IL_0007:  ret
  }
}

rtspecialnameとかいう特別そうなフラグが付いているのと、 名前が変という以外はただのメソッドです。 中身も親クラスのコンストラクターを呼んでいるだけ。

プロパティ

プロパティは、getsetに応じたメソッドと、 自動実装プロパティであればフィールドが1つ作られます。 今回の例では、Xは自動実装プロパティで、getset共に持っているので以下のようなILが生成されます。

  .field private int32 '<X>k__BackingField'
  .custom instance void [mscorlib]System.Runtime.CompilerServices.CompilerGeneratedAttribute::.ctor() = ( 01 00 00 00 ) 

  .property instance int32 X()
  {
    .get instance int32 C::get_X()
    .set instance void C::set_X(int32)
  }

  .method public hidebysig specialname instance int32 
          get_X() cil managed
  {
    .custom instance void [mscorlib]System.Runtime.CompilerServices.CompilerGeneratedAttribute::.ctor() = ( 01 00 00 00 ) 
    .maxstack  8
    IL_0000:  ldarg.0
    IL_0001:  ldfld      int32 C::'<X>k__BackingField'
    IL_0006:  ret
  }

  .method public hidebysig specialname instance void 
          set_X(int32 'value') cil managed
  {
    .custom instance void [mscorlib]System.Runtime.CompilerServices.CompilerGeneratedAttribute::.ctor() = ( 01 00 00 00 ) 
    .maxstack  8
    IL_0000:  ldarg.0
    IL_0001:  ldarg.1
    IL_0002:  stfld      int32 C::'<X>k__BackingField'
    IL_0007:  ret
  }

意味的には以下のような感じ。

  • <>が含まれる読めた代物じゃないフィールドができる
  • get_set_から始まるメソッドができる
  • フィールドとメソッドにはCompilerGenerated属性が付いてる
  • プロパティの定義自体は、メソッドを参照しているだけ

フィールドは、通常のC#では書けないような記号入りの名前なので特に問題を起こさないんですが、 メソッドの方は被りがあり得ます。つまり、以下のコードはコンパイル エラーを起こします。

class C
{
    public int X { get; }
    int get_X() => 0;
}

しかもエラーを起こすのは get のところ。

インデクサー

インデクサーなどというものは存在しない。いいね?

C#のインデクサーは、ILのレベルではItemという名前のプロパティになっています。

  .custom instance void [mscorlib]System.Reflection.DefaultMemberAttribute::.ctor(string) = ( 01 00 04 49 74 65 6D 00 00 ) // ...Item..

    .property instance int32 Item(int32)
  {
    .get instance int32 C::get_Item(int32)
    .set instance void C::set_Item(int32,
                                   int32)
  }

  .method public hidebysig specialname instance int32 
          get_Item(int32 index) cil managed
  {
    .maxstack  1
    .locals init ([0] int32 V_0)
    IL_0000:  nop
    IL_0001:  ldarg.1
    IL_0002:  stloc.0
    IL_0003:  br.s       IL_0005

    IL_0005:  ldloc.0
    IL_0006:  ret
  }

  .method public hidebysig specialname instance void 
          set_Item(int32 index,
                   int32 'value') cil managed
  {
    .maxstack  8
    IL_0000:  nop
    IL_0001:  ret
  }

意味的には以下のような感じ。

  • Itemという名前のプロパティが作られる
  • プロパティは実は引数を取れる
    • (C#では無理だけど、VBなら引数付きプロパティも書ける)
  • DefaultMember属性でItemプロパティを指定している

つまるところ、インデクサー = 「名前を省略していい引数付きプロパティ」です。

Itemに展開されるので、もちろん、以下のコードはthisのところでコンパイル エラー。

class C
{
    public int this[int index] { get { return index; } }
    int Item { get; }
}

get_Itemメソッドもダメです。getのところでエラー。

class C
{
    public int this[int index] { get { return index; } }
    int get_Item(int index) => 0;
}

Itemプロパティは普通に使いそうな名前なので、罠を踏むとしたらこれが一番頻出しそうなやつです。

ちなみに、回避方法も、まあ、あって、インデクサーから生成されるプロパティの名前は変更できます。

class C
{
    [System.Runtime.CompilerServices.IndexerName("Indexer")]
    public int this[int index] { get { return index; } }

    // ↑これで Item は生成されなくなるので、自前のもの↓と被らなくなる

    int Item { get; }
    int get_Item(int index) => 0;
}

ちなみに、C#コード上はインデクサーにIndexerName属性が付いていますが、 コンパイル結果的にはクラスに対するDefaultMember属性に変換されます。

イベント

最後は、他の言語から来た人が困惑する機能ナンバー1、イベントです。 もっと難しい機能もたくさんありますけど、利用頻度の割に複雑という意味では断トツではないかと。 (使う頻度は多少あるけど、作る頻度はかなり低いんじゃないでしょうか。)

プロパティに近いんですが、getsetの代わりにaddremoveです。 自動実装でフィールドが作れる部分は同じです。

  .event [mscorlib]System.Action E
  {
    .addon instance void C::add_E(class [mscorlib]System.Action)
    .removeon instance void C::remove_E(class [mscorlib]System.Action)
  } // end of event C::E

  .field private class [mscorlib]System.Action E
  .custom instance void [mscorlib]System.Runtime.CompilerServices.CompilerGeneratedAttribute::.ctor() = ( 01 00 00 00 ) 

    .method public hidebysig specialname instance void 
          add_E(class [mscorlib]System.Action 'value') cil managed
  {
    .custom instance void [mscorlib]System.Runtime.CompilerServices.CompilerGeneratedAttribute::.ctor() = ( 01 00 00 00 ) 
    // 結構長いのでさすがに省略
  }

  .method public hidebysig specialname instance void 
          remove_E(class [mscorlib]System.Action 'value') cil managed
  {
    .custom instance void [mscorlib]System.Runtime.CompilerServices.CompilerGeneratedAttribute::.ctor() = ( 01 00 00 00 ) 
    // 結構長いのでさすがに省略
  }

プロパティと似た感じで、

  • フィールドができる
  • add_remove_から始まるメソッドができる
  • フィールドとメソッドにはCompilerGenerated属性が付いてる
  • イベントの定義自体は、メソッドを参照しているだけ

という状態なんですが、ちょっと違うのは、以下の部分。

  • フィールドの名前がイベントの名前とまったく同じ(この例の場合E)
  • 自動生成されるメソッドの中身はほんと長い(参考: 補足: 自動イベント)

C#では許されていませんが、ILレベルだと、メンバーの種類が違えば同じ名前を使えます。

そして、実は、イベントを触っているように見えて、実は裏で作られたフィールドを触っているという事態に。

class C
{
    public event Action E;

    // 登録の側は add_E が呼ばれてるんだけど
    public void Register() => E += Handler;
    void Handler() { }

    // 呼び出し側では、実はイベントの E じゃなくて、フィールドの E
    public void Invoke() => E();
}

このInvokeメソッドの中を見てみると以下のような感じ。ldfld命令はフィールド読み込みのための命令です。

.method public hidebysig instance void  Call() cil managed
{
  .maxstack  8
  IL_0000:  ldarg.0
  IL_0001:  ldfld      class [mscorlib]System.Action C::E
  IL_0006:  callvirt   instance void [mscorlib]System.Action::Invoke()
  IL_000b:  nop
  IL_000c:  ret
}

つまり、イベントを明示的に実装すると、E()みたいな呼び出しはできなくなります。

class C
{
    private Action _e;
    public event Action E
    {
        add { _e += value; }
        remove { _e -= value; }
    }

    // 明示的に add/remove を実装すると、自動実装なフィールドの E が消える
    // ↓このコードが書けなくなる
    public void Invoke() => E();
}

イベントの明示的な実装とかめったにするものじゃないのでそんなに踏まないと思いますが、一応注意が必要です。


小ネタ 隠し演算子(?)

$
0
0

C#には隠しキーワードとして__makerefなどの見慣れないキーワードがあるという話はたまに話題に出てきますが、実は隠し演算子もあります。

演算子 同じ結果の式 意味合い
-~x x + 1 xに向かって値が入っているイメージ
~-x x - 1 xから値が出ていくイメージ

副作用を起こさない(非変)インクリメント、デクリメント(non-modified increment/decrement)です。 実際、以下のようなコードを実行することができます。

var a = 10;
Console.WriteLine(-~a); // 11
Console.WriteLine(~-a); // 9

ideoneとかでも実行できます。 ideoneは確かMonoで動いているはずで、MonoのC#コンパイラーもひそかに対応しているということですね。

こいつは、演算子の形状からtadpole (オタマジャクシ)とか言われたりもします。 (?. なんかもエルビス プレスリーの髪型っぽく見えるという話からelvis演算子とか呼ばれたりもします。それと同種の愛称です。)

x + 1でいいじゃないかと思うかもしれませんが、 単項演算子なので優先度が高いという利点があります。 (x + 1) * (y - 1)というような式が、`-~x*~-y'となります。

一方で、単項演算子の±1であれば、++ (インクリメント)と-- (デクリメント)もあります。 しかし、これらは副作用を伴っていて、xの値を書き換えてしまいます。

演算子 同じ結果の式
++x x += 1
--x x -= 1

副作用を伴う式というはあまり行儀がいいものではないので、その副作用なし版がこっそり用意されているというのが-~~-です。

まあ、利点と言っても微々たるものですし、どっちがプラスでどっちがマイナスかわかりにくいですから、正式採用とはならず、仕様書等には載っていません。





種明かし

さて、まあ、嘘なわけですが。 実行できる嘘。

昔同じネタをちょっと取り上げたことはあるんで覚えてらっしゃる方もいらっしゃいますかね。

要するに、

という話だったんですが…

真に受ける人が多いということは、ちゃんとした解説書かなきゃダメなのかなというのが、今回の本当の主題。

実際、つい最近も、2の補数表現の話とかを知らない人にこの話を説明したりしたんですよね。ピックアップRoslynのついででさらっと流すのはもったいなかったかなぁと。

本当は -~ の組み合わせ

これ、要するに、以下の意味です。

括弧で整理
-~x -(~x)
~-x ~(-x)

単項演算子が2個並んでいるだけ。

たぶん、3つのはまりどころがあります。

  • ~演算子とかめったに使わない
  • ビット反転とマイナスの関係を知らない人がそこそこいる
  • 単項演算子をくっつけて書くのはどうなのよ

ビット反転

真に受ける人が多かった理由の1つは、~演算子とかめったに使わないからでしょうね。 そもそも~がC++とかC#で有効な演算子なことを知らない/忘れている人がそれなりにいるんでしょう。 というか、ビット操作とか2進数での数値表現がまず苦手って人も見ますもんねぇ、ちらほら。

~はビット反転の意味です。以下の例のように、すべてのビットの0と1を逆にします。

ビット反転

ビット反転とマイナスの関係

次に必要な説明は、ビット反転とマイナスの組み合わせでどうして±1になるのかです。 ビット反転~とマイナス-は、常に以下の式を満たします。

~x + 1 == -x

例えば、以下のようなコードを書くと、throwの行は通らずプログラムが正常終了します。

using System;

class Program
{
    static void Main()
    {
        for (int x = 0; x < 256; x++)
        {
            if (~x + 1 == -x) continue;

            throw new InvalidOperationException();
        }
    }
}

これは、以下の図のように考えれば説明が付きます。

ビット反転 + 1

  1. 元の数字と、ビット反転した数字を足すと、全ての桁で 1 + 0 が起きるわけで、 結果は全桁1になります
  2. これに1を足すと、桁上がりによって0が並びます
  3. オーバーフロー(桁上がりしてしまった最上位桁)を無視すると、完全に0になります
  4. すなわち、x~x + 1を足すと0になります
  5. -xというのは「xと足して0になる数」のことを指すわけで、~x + 1-xと等しいはずです。

実際、符号付き(この例で言うと8 bitなのでsbyte)の-123と、 符号なし(byte)の133 = 256 - 123は、ビット表現としては全く同じ1000 0101になります(123のビット反転は132です。132 + 1 = 133 = -123)。

この考え方は、コンピューターのハードウェアを作る上で非常に便利です。

  1. 負の数を「ビット反転 + 1」で表すものとする
  2. この場合、a - ba + ~b + 1となる
  3. つまり、加算回路をそのまま流用して減算回路を作れる

ということで、だいたいのハードウェアでこの「ビット反転 + 1」で負の数を表現する方式が採用されています。 この表現形式を「2の補数(two's complement)表現」と言います。 わざわざ「2の」とかいう名前が付いているのは、他の方式もあったからなんですが、まあ、現存していません。

単項演算子をくっつけて書く

あとは、演算子をくっつける書き方自体がどうなのか、という話はあります。

だって、識別子(変数名とか)の場合、aba bは違う意味になるじゃないですか。 前者は「ab」という名前の1つの識別子、後者「a」と「b」が並んでる。 なのに、~-~ -は同じ意味で、~-に分解される。

~-みたいな、複数文字で1つの演算子にできれば、演算子オーバーロードに幅ができて便利かもしれません。 実際、F#なんかはそれを認めています。

C#でも、~-みたいに書かれると、一瞬それを期待しちゃうのかもしれません。 でも、実際には、~-は2つに分解されて、複数文字1演算子はできません。

おまけ: くっつけて書けるとそれはそれでひどい

常に一貫して「演算子は1文字1文字全部区切る」ってルールならまだしも、いくつか例外が存在しているのがまた面倒です。 ++ (インクリメント)とか>= (大なりイコール)とか=> (ラムダ式の矢印)とかは2文字で1演算子。だったら、「~-で1演算子」も期待しかねない。

インクリメントなんて、+++がそれぞれ別の意味になるもんだから本当にひどくて。 a+++bって式はC#などでは有効な式なんですが、どういう意味になるかぱっと見ではわからない。なんせ、以下の3つの式、どれも有効で、それぞれ違う結果になります。

  • a++ +b
  • a+ ++b
  • a+ + +b
using static System.Console;

class Program
{
    static void Main()
    {
        int a, b;

        a = 1; b = 1;
        WriteLine($"{a++ +b}, {a}, {b}"); // 2, 2, 1: (a++) + b
        a = 1; b = 1;
        WriteLine($"{a+ ++b}, {a}, {b}"); // 3, 1, 2: a + (++b)
        a = 1; b = 1;
        WriteLine($"{a+ + +b}, {a}, {b}"); // 2, 1, 1: a + (+(+b))
        a = 1; b = 1;
        WriteLine($"{a+++b}, {a}, {b}"); // 2, 2, 1: つまり、a+++b は a++ +b 扱い
    }
}

Swiftなんかだと、単項演算子を隣接させるのを禁止してるみたいですね。 -~xはエラーになります。

かつ、単項演算子とオペランドの間は逆にスペースを挟むのを禁止しているようで。 - ~xという書き方もエラー。 括弧を使って-(~x)と書くのを義務付けているようです。

まあ、確かに括弧が付いている方が見やすくて人的ミスは起こしにくいでしょう。 一方で、関数適用はf (x)とか書けるのに、単項演算子は~ (x)とは書けないっていうちょっと気持ち悪い状態ではあります。

小ネタ null関係の演算子

$
0
0

今日は、?.とか??での、nullの判定方法について。

C# 6で導入されたnull条件演算子(?.)ですが、以下の2つの式がほぼ同じ意味になります。

x != null ? x.M() : null
x ?.M()

「ほぼ」であって「完全に同じ」と言えないのは、==演算子を呼ぶか呼ばないかが変わってしまうせいです。 前者(自分で==を呼んでいるやつ)はオーバーロードされた==を呼び出しますが、 後者(?.を利用)は呼びません(直接nullかどうか調べます)。

例えば、以下のように、本当はnullじゃないのにnullを自称する(x == nullがtrueになる)クラスを作ると、ちょっと変な挙動になります。

using static System.Console;

class NonDefault<T>
{
    public T Value { get; }
    public NonDefault(T value) { Value = value; }

    public override string ToString() => Value.ToString();

    // Value が既定値のときに null と同値扱いする
    // null でないものとの x == null が true になることがある
    public static bool operator ==(NonDefault<T> x, NonDefault<T> y) =>
        ReferenceEquals(x, null) ? ReferenceEquals(y, null) || Equals(y.Value, default(T)) :
        ReferenceEquals(y, null) ? ReferenceEquals(x, null) || Equals(x.Value, default(T)) :
        Equals(x.Value, y.Value);

    public static bool operator !=(NonDefault<T> x, NonDefault<T> y) => !(x == y);
}

class Program
{
    // null の時には "null" と表示する ToString
    static string A(NonDefault<int> x) => (x != null ? x.ToString() : null) ?? "null";
    // A とほぼ同じ意味に見えて…
    static string B(NonDefault<int> x) => x?.ToString() ?? "null";

    static void Main()
    {
        WriteLine(A(new NonDefault<int>(1))); // 1
        WriteLine(B(new NonDefault<int>(1))); // 1

        WriteLine(A(null));                   // null
        WriteLine(B(null));                   // null

        // == を呼ぶ呼ばないことによる差がここで出る
        WriteLine(A(new NonDefault<int>(0))); // null
        WriteLine(B(new NonDefault<int>(0))); // 0
    }
}

まあ、普通、こんな==演算子オーバーロードの仕方はしないんですが。 というか、参照型に対する==オーバーロード自体めったにしないんですが。 (通常、==演算子を使うのは、Dictionaryのキーにしたい不変なクラスくらいです。)

ちなみに、このメソッドABのコンパイル結果はそれぞれ以下のようになります。 比較のために表にして命令ごとに並べてみましょう。

A B
ldarg.0 ldarg.0
ldnull brtrue.s IL_0006
call NonDefault::op_Inequality  
brtrue.s IL_000c  
ldnull ldnull
br.s IL_0012 br.s IL_000c
ldarg.0 ldarg.0
callvirt Object::ToString callvirt Object::ToString
dup dup
brtrue.s IL_001b brtrue.s IL_0015
pop pop
ldstr "null" ldstr "null"
ret ret

nullの判定方法(2行目~4行目)だけが違って、残りは全く同じです。 ==演算子を呼ばずに直接nullを調べるならbrtrue命令1個でできます。

ちなみに、brtrueは"branch if true"の略で、 「直前の結果がtrueだったらジャンプする」という命令になります。 整数の0とか、参照型のnullとかはfalse扱い。

この挙動はnull合体演算子(??)でも同様です。

おまけ: throw null

話題は変わりますが、?.の中身をILレベルで覗いたついでと言ってはなんですが、ちょっとしたおまけ。

時々、「throw nullと書くと、throw new NullReferenceException()と同じ意味になる」的な誤解(?)を見かけたりします。 コンパイル結果的には当然、全然違うんですよね。

以下のように書いた場合、

static void X() { throw null; }
static void Y() { throw new NullReferenceException(); }

コンパイル結果は以下の通り。

.method private hidebysig static void  X() cil managed
{
  // コード サイズ       2 (0x2)
  .maxstack  8
  IL_0000:  ldnull
  IL_0001:  throw
} // end of method Program::X

.method private hidebysig static void  Y() cil managed
{
  // コード サイズ       6 (0x6)
  .maxstack  8
  IL_0000:  newobj     instance void [mscorlib]System.NullReferenceException::.ctor()
  IL_0005:  throw
} // end of method Program::Y

割かしそのまんまなILコードです。 nullをロード(ldnull)して、throw命令を実行。 要するに、前者は(throw命令の実行に失敗して)実行エンジンがNullReferenceExceptionを作って投げていて、 後者は自分自身で作ったNullReferenceExceptionを投げている。 まあ、結果的には同じような挙動をするんですが。

Updating Visual Studio 2017 Release Candidate

$
0
0

先月出たVisual Studio 2017 RC、ちょこっとアップデートが掛かりました。

小ネタ集?そんなの休み休み。

ちなみに、MSDNブログには「前のRCを入れてる人はアップデートの通知が出るはずだからそこからアップデートできる」って書いてあるんですが、うちではなんかトラブって、結局アンインストールからのインストールしなおしでした。 (ASP.NET がらみのVSIXパッケージが1個インストール失敗してた。)

新旧 csproj

概ねバグ修正っぽいんですが、個人的にうれしいのは、ようやくだいぶ .NET Core Tooling周りがマシになってきたこと。 昔ながらのcsproj (普通に .NET Framework向けライブラリ作ったり、旧Portable Class Libraryを作ったりしたときのやつ)と新csproj (元々xproj + project.jsonだったもの。 .NET Standardライブラリ作ったり、.NET Coreアプリ書いたりするときのやつ)の混在がやっとまっとうに。 .NET Standardライブラリのプロジェクトを、.NET Frameworkなコンソール アプリとかからプロジェクト参照できるように。

言語パック

あと、表示言語を切り替えれるように。最近もうすっかり、普段は英語UIで使っていて、記事を書くときのスクリーンショットとかのためだけに日本語に切り替えたりしてるんですけども、それが前のRCまではできなかったという。しょうがないからCommunity(日本語)とEnterprise(英語)をside by sideに入れてたんですが、それはそれで、スタートメニューにはそのうち片方(後からインストールした方)しか出てこないとか言う別の不具合が。それもやっと解消。

C# 7

で、C# 7。先月のRCリリースの時点でC# 7の全機能そろったといったな。あれは嘘だ。

えーっと、「C# 7に間に合わせて入れるかどうかまだ迷ってるけども…」と言われていた機能が1個、入ることに決まったようです。 Wildcards(ワイルドカード、万能札)と言われてたものが、Discards(破棄、捨て札)という呼び名に変わりましたが、機能的には同じものです。

要するに、分解out varで、要らない値のところに _ を書けば無視できるという機能。 以下のようなコードが書けます。

using System;
using System.Collections.Generic;

class Program
{
    static void Main(string[] args)
    {
        // 分解と out var では _ が discard 使いになったっぽい
        var (_, sum) = Tally(new[] { 1, 2, 3, 4, 5 });
        (_, var sum2) = Tally(new[] { 2, 3, 5, 7, 11 });
        var (count, _) = Tally(new[] { 1, 3, 5 });

        Console.WriteLine((count, sum, sum2));

        if (int.TryParse("123", out _) && int.TryParse("456", out _))
            Console.WriteLine("successfully parsed");

        // 既存文法に対しては未対応。
        // 計画上は出てたはずだけど、いつ対応するかは不明。
        Func<int, int, int> f = (_, _) => 1;
    }

    static (int count, int sum) Tally(IEnumerable<int> items)
    {
        var count = 0;
        var sum = 0;
        foreach (var x in items)
        {
            count++;
            sum += x;
        }
        return (count, sum);
    }
}

これ、今までのC#だと、_も有効な識別子なので、1度どこかで使ってしまうと、他の場所では二重定義扱いになってコンパイル エラーになります。 それが、とりあえず分解とout varというC# 7からの新構文な場所では、識別子ではなくてdiscards扱いされるようになりました。

ラムダ式の引数など、既存の文脈でどうするかってのはちょっと悩んでたみたいですけども、 とりあえず、「一度でも値の読み出しがあるなら変数扱い、なければdiscards扱い」みたいにしたいそうです。 ちょっと最終的な判断どうなっているのかまでは把握してないんですが、C# 7リリースまでには入るかも。

ピックアップRoslyn 12/14

$
0
0

小ネタ休んだついでにピックアップRoslynも。

Visual Studio 2017の正式リリースまではバグ修正くらいしか作業しない段階に来てるんで大したネタはない… と思っていた時期がありました。

まあ、小ネタ程度の話はあった…

whileとforのスコープ変更

まあ、既存動作を壊す変更ではないんですが、whileforの仕様書上の記述を変更しなきゃという話が出ています。

型スイッチout varのせいで、whileやforの条件式や更新式の中で変数を作れるようになりました。 その変数のスコープはどうなるべきかというのを考えたときに、whileforの展開結果に関する記述をちょっと変更する必要があるっていう話です。

while

まずwhile

while (<cond>) <body>

こういうwhileステートメントがあったとき、これまでだと、以下のように展開するという仕様になっていました。

continueLabel:;
if (!<cond>) goto breakLabel;
{
    <body>
}
goto continueLabel;
breakLabel:;

これが、以下のように変わります。{ } が1段増える。

continueLabel:;
{
    if (!<cond>) goto breakLabel;
    {
        <body>
    }
    goto continueLabel;
}
breakLabel:;

要するに、条件式の中で宣言された変数は、whileの外には漏らさないよというルールの追加です。

(現状のRC版はルール変更前の実装になってる。RTMまでにたぶん変わる。)

for

同様に、for

for (<decl>; <cond>; <incr>) <body>

このforステートメントは、以下のような仕様になっていました。

{
    <decl>
    while(<cond>)
    {
        <body>
    continueLabel:;
        <incr>
    }
}

これが以下のように変更。更新式(<incr>のところ)に { }が増えます。

{
    <decl>
    while(<cond>)
    {
        <body>
    continueLabel:;
        { <incr> }
    }
}

更新式のところで宣言した変数はその中でだけ使えて、forステートメントのbody内でも参照できなくするということのようです。

Design by Contract のコミュニティ実装

C# チーム的には「メリットの割には構文が煩雑になりすぎる」と、今まだちょっと及び腰になっている Design by Contract がらみの構文ですが、 しびれを切らした人が自前実装を始めた模様。

実装的には、StackExchange.Precompilationを使ったビルド時コード書き換えみたいです。なので、実行時コストは高くないはず。

まあ、ビルド時コード書き換えはそこそこはまりどころもあるので、どうしてもDbCが欲しいという人にしかあんまりお勧めはできませんけども… C#に公式に機能追加されるまでのつなぎとしてはいいかも。

パターン マッチング

$
0
0

小ネタ休んだついでに、C# 7思い出話。

そういや、タプルの辺りまでは、C# 7思い出話とか称して、仕様が固まるまでにあった流れなんかもブログに残そうとしてたのを思い出したり。

C# 7の紹介、後半は一気に埋めちゃったのもあって、思い出話をどう書こうか考えてるうちに流れてしまったというか。 ちょっと書くのに苦労した原稿とか、グロサミ参加とか、色々忙しくて忘れていたというか。

とりあえず、今日はパターン マッチングの話。

パターン マッチングを小分けに

元々予定されていたパターン マッチングの全機能はC# 7の時点では入れれない、段階的に実装していくという話は結構早い段階で決まっていました。

ちなみに、その、予定されている全機能で言うと、以下のような書き方とかもできます。

static int Calculate(Node n, int x)
{
    switch (n)
    {
        // この2行はC# 7の時点で書ける
        case Variable v: return x;
        case Constant c: return c.Value;
        // この2行は先送り
        case Add { Le is var l, Right is var r }: return Calculate(l, x) + Calculate(r, x);
        case Mul(var l, var r): return Calculate(l, x) * Calculate(r, x);
    }
}

一方で、どこまでをC# 7に入れるかは、徐々に、スケジュールと相談しつつ決めていたみたいで最近まで全然確定していません。

初期は、本当に型スイッチくらいでした。

続いて、まあタプルが入るんなら分解くらいは要るだろうとなったのか、こいつが実装されます。

throw式もパターン マッチングからの派生です。パターン マッチングのお供として「switch 式」みたいなやつを入れることを考えると、「どのパターンにもマッチしなかったら例外を投げる」という処理が必要で、そのための throw 式です。

分解out varが入るならdiscards (wildcards)もほしいわけですが、これは、_ を使うか * を使うか、既存の文脈でも discards を使えるようにするかどうかとかで悩んでいたみたいで、 本当につい最近実装されています。 先日書いた通り、Visual Studio 2017 RCの初期リリースでは実装されていなくて、Update での実装。

気が付いてみれば、「再帰的な分解」と「switch 式」以外は一通り実装されたのかなぁという感じです。 (再帰的っていうのは、最初に挙げた例でいうAdd { Left is var l, Right is var r }とかMul(var l, var r)とかみたいなパターンです。)

まあ、それぞれを見ると、細かく「先送り」になっているものもあるんですが。例えば以下のコードは、計画上はできることになっているんですが、現状ではコンパイルエラーになります。

// 計画上は、クエリ式の let での分解も予定あり
var q = from x in new [] { 1, 2, 3, 4, 5 }.Select((x, i) => (x, i))
        let (y, z) = x
        select y * z;

// 計画上は、ラムダ式とかの既存の文法にも discards 導入の予定あり
Func<int, int, int> f = (_, _) => 1;

// 計画上は、ラムダ式でも throw 式を書ける予定あり
Action a = () => throw new Exception();

もしかしたら、まだリリースまでにこれらの構文も対応するかもしれませんが、断定はできなさそう。

Viewing all 482 articles
Browse latest View live