読者です 読者をやめる 読者になる 読者になる

xin9le.net

Microsoft の製品/技術が大好きな Microsoft MVP for .NET な管理人の技術ブログです。

throw 式

C#

2016/09/21 (水) の朝方、throw 式に関する Pull-Request が master にマージされました!これにより、これまでステートメント (文) としてのみ提供されていた throw を式として記述できるようになります。

f:id:xin9le:20160924225231p:plain

throw 式が使えるところ

パッと確認した範囲では、以下の箇所で使えました。

  • 条件演算子 (三項演算子)
  • null 結合演算子
  • 式形式のメンバー

条件演算子 (三項演算子)

条件演算子で throw 式を使ってみると以下のような感じになります。

//--- C# 7 ではこう書ける
static int IntParse(string value)
    => int.TryParse(value, out var result)
    ?  result
    :  throw new ArgumentException(nameof(value));

//--- C# 6 までの書き方
static int IntParse(string value)
{
    int result;
    if (int.TryParse(value, out result))
        return result;

    throw new ArgumentException(nameof(value));
}

C# 7 のコードを逆コンパイルしてみると以下のようになります。C# 6 までのコードと同様の展開がされることが分かりますね。

//--- 逆コンパイル結果
private static int IntParse(string value)
{
    int result;
    if (!int.TryParse(value, out result))
    {
        throw new ArgumentException("value");
    }
    return result;
}

null 結合演算子

null 結合演算子に対しても同じように使うことができます。

//--- C# 7
static object Assert(object x)
    => x ?? throw new ArgumentNullException(nameof(x));

//--- 逆コンパイル結果
private static object Assert(object x)
{
    if (x == null)
    {
        throw new ArgumentNullException("x");
    }
    return x;
}

式形式のメンバー

C# 6 で導入された => によるプロパティやメソッドの簡易記法 (= 式形式のメンバー) でも使えます。例えば下記のように、未実装時の記述がシンプルで楽になりますね。

//--- C# 7
public string Name => throw new NotImplementedException();
public string SayHello() => throw new NotImplementedException();

//--- 逆コンパイル結果
public string Name{ get { throw new NotImplementedException(); }}
public string SayHello(){ throw new NotImplementedException(); }

[おまけ] throw null;

みなさん、nullthrow するとどうなるかご存知ですか?実は NullReferenceException が飛びます。

try
{
    //--- 以下のふたつは等価
    throw null;
    throw new NullReferenceException();
}
catch
{}

違う型の例外でも NullRefereneceException になります。

try
{
    NotSupportedException ex = null;
    string a = null;
    var b = a ?? throw ex;  //--- NotSupportedException は飛ばない
}
catch (Exception ex2)
{
    //--- System.NullReferenceException
    Console.WriteLine(ex2.GetType());
}

throw 式の結合優先度

岩永先生 (@ufcpp) が throw 式の結合優先度を調べて教えてくださいました。まとめてくださった Gist をそのまま拝借するとこんな感じらしいです。

最初どうしてこうなるのか全然わからなかった以下のような例も、結合優先度のおかげで理屈が分かって納得しました。

try
{
    //--- throw null されると思ってたのに、されないで ArgumentNullException が飛んだ
    NotSupportedException ex = null;
    string a = null;
    var b = a ?? throw ex ?? throw new ArgumentNullException();

    //--- けど ↑↑ は結合優先度の問題なので、実はこれと一緒
  //var b = a ?? throw (ex ?? throw new ArgumentNullException());
}
catch (Exception ex2)
{
    //--- System.ArgumentNullException
    Console.WriteLine(ex2.GetType());
}

上記を逆コンパイルすると以下のように展開されます。

try
{
    NotSupportedException ex = null;
    string a = null;
    if (a == null)
    {
        NotSupportedException expr_0A = ex;
        if (expr_0A == null)
        {
            throw new ArgumentNullException();
        }
        throw expr_0A;
    }
    var b = a;
}
catch (Exception ex2)
{
    Console.WriteLine(ex2.GetType());
}

throw 式の型

例えば条件演算子の場合、コンパイラはその戻り値の型を決定しなければなりません。これまでは以下のようになっていました。

//--- こんな継承関係があるとする
class Base {}
class A : Base {}
class B : Base {}

//--- A は Base になり得るので x は Base 型
var x = true ? new A() : new Base();

//--- コンパイルエラー : 継承元を探してくれたりはしない
var x = true ? new A() : new B();

throw 式は条件演算子に書くことができると先に書きました。この場合 throw 式の型はどうなるのかというと、答えは「どんな型にでもなれる」です。こんな型を「bottom 型 (= すべての型から派生している型)」と言います。.NET Framework において双対するのが object 型で、これを「top 型 (= すべての型の基底となる型)」と言うそうです。

//--- x の型は A と推論される
var x = true ? new A() : throw new Exception();

//--- x の型は B と推論される
var x = true ? throw new Exception() : new B();

つまり条件演算子は throw 式 ではない方の型で推論される動きになるのですが、両方を throw 式にしたらどうなるか。これは (当然) コンパイルエラーになります。

//--- 型推論できないのでエラー
//--- Type of conditional expression cannot be determined because there is no implicit conversion between '<throw>' and '<throw>'
var x = true ? throw new NotSupportedException() : new ArgumentNullException();

Roslyn の開発進捗が更新されました - 2016/09/21

C# News

2016/09/21 (水) の朝方、とある Pull-Request のマージにより Roslyn の開発状況/進捗 (Language Feature Status) が更新されました。でも実際に変更を加えたのは 2016/08/30 (火) っぽいので、約 1 か月前から決まっていた内容です。

Language Feature Status

以下はその引用です。

C# 7 / Visual Basic 15 に搭載予定

Feature Branch State
Binary Literals master Finishing
Digit Separators master Finishing
Local Functions master Finishing
Type switch master Finishing
Ref Returns master Finishing
Tuples master Finishing
Out var master Finishing
ValueTask master Finishing
Throw Expr features/throwexpr Prototyping
Expression-Bodied Everything features/exprbody Prototyping

C# 8 / Visual Basic 16 以降で搭載予定

Feature Branch State
Address of Static none Feature Specification
Async Main none Feature Specification
Source Generation master Prototyping
private protected features/privateProtected Prototyping
Non-null Ref Types features/NullableReferenceTypes Prototyping
Bestest Betterness none Feature Specification
Records features/records Feature Specification
With Exprs features/records Feature Specification
Pattern Matching features/patterns Prototyping

C# 7 で搭載見込みの機能は実装完了しているものすべてを記事にしていますので、ご興味があればご覧ください。変更点なども順次追記/修正しています。

変更点

diff から見る前回との差分はザッと以下のような感じです。

機能 概要 変更点
Throw Expr 例外を式でも投げられるように C# 7 での搭載に再昇格
Expression-Bodied Everything コンストラクタなども => で書けるように 新規に C# 7 で搭載する方向
Address of Static 搭載見送り
Bestest Betterness より優れたオーバーロード解決 Better Betterness から名称変更

開発も終盤に差し掛かっているのかなぁと思っていたりしたのですが、一度は見送られた throw 式が再度搭載する方向になるなどの方針変更が入っています。まだまだアクティブな開発が続いている感じで目が離せませんね!最近は Visual Studio 15 Preview 5 のリリース準備も着々と進んでいるように見受けられますし、そこまでにどれだけ開発が進むのか楽しみです。

ちなみにここに書いてあることは記事執筆時点 (2016/09/24) での状況であって最終的な確定事項ではないのでご注意ください。C# 7 の fix はいつになるのか...w

Spy++ が見つからないときの対処方法

Tips Tools

Windows のデスクトップアプリ (今だとクラシックデスクトップアプリって言うんでしたっけ?) 開発者にとっては必需品と言っても過言ではないレベルの神ツールである Spy++。ウィンドウハンドルやウィンドウメッセージを拾う楽しさを知ってしまうと、別プロセスに対して悪さ (?) するのもお手の物ですよね!

f:id:xin9le:20160914020702p:plain

以前は Visual Studio をインストールすれば勝手に付いてきたのですが、Visual Studio 2015 になってからは標準インストールしても入っていないことに気付きました。そんなときの対処メモ。

対処方法

とっても簡単で、Visual Studio 2015 のセットアップで以下の機能をインストールすれば OK!もちろん次の Visual Studio でどうなるかはわかりませんが...

  • [Programming Languages] - [Visual C++] - [Common Tools for Visual C++ 2015]

f:id:xin9le:20160914021421p:plain

セットアップが完了すれば、以下のフォルダに配置されているはずです。

  • C:\Program Files (x86)\Microsoft Visual Studio 14.0\Common7\Tools\spyxx.exe

Roslyn の開発進捗が更新されました - 2016/08/04

C# News

2016/08/04 (木) の朝方、Gafter 先生の Pull-Request が master にマージされ、それにより Roslyn の開発状況/進捗 (Language Feature Status) が更新されました。

以下はその引用です。

C# 7 / Visual Basic 15 に搭載予定

Feature Branch State
Address of Static none Feature Specification
Binary Literals master Finishing
Digit Separators master Finishing
Local Functions master Finishing
Type switch master Finishing
Ref Returns master Finishing
Tuples master Finishing
Out var master Finishing
ValueTask master Finishing

C# 8 / Visual Basic 16 以降で搭載予定

Feature Branch State
Async Main none Feature Specification
Source Generation master Prototyping
Throw Expr features/patterns Prototyping
private protected features/privateProtected Prototyping
Non-null Ref Types features/NullableReferenceTypes Prototyping
Better Betterness none Feature Specification
Records features/records Feature Specification
With Exprs features/records Feature Specification
Pattern Matching features/patterns Prototyping

C# 7 で搭載見込みの機能は実装完了しているものすべてを記事にしていますので、ご興味があればご覧ください。

変更点

diff から見るに、これまでとの差分はザッと以下のような感じです。

機能 概要 変更点
Out var out 引数に渡す変数を宣言と同時に生成 プロトタイプ完了
ValueTask 非同期メソッドの戻り値を任意型に プロトタイプ完了
Async Main Main 関数で async/await を許可 搭載見送り
Source Generation コード生成による処理の差し込み 搭載見送り
Throw Expr 例外を式でも投げられるように 搭載見送り

個人的にかなり残念なのは Source Generation の搭載が見送り項目になったことです。実はこの機能、6 月中は master ブランチに入っていたのですが 2016/07/01 のコミットfeatures/source-generators に逃がされました。

数日かけて実際に動きも (ほぼほぼ全部) 調べてブログ記事を書き終えたのが 2016/06/30 の深夜。社内@neuecc 先生に「こんな機能が来そうじゃモン!」と夜な夜な話して「いざ公開じゃー!」と思ったところで機能削除されて意気消沈していました...(´;ω;`)

ちなみにここに書いてあることは記事執筆時点 (2016/08/05) での状況であって最終的な確定事項ではないのでご注意ください。とは言え最近は Visual Studio 15 Preview 4 のリリース準備も着々と進んでいるようですし、C# 7 の fix がだいぶ近づいているのかなぁという気がしないでもないです。

任意の型を戻り値に持つ非同期メソッド

C#

これまでの非同期メソッドは void / Task / Task<T> のいずれかを戻り値にしなければならないという制約がありました。

非同期メソッドが C# 5 で導入されてから早 4 年。もはやこれを「制約」と感じることはほとんどないくらい馴染んでしまっていますが、C# 7 でこの制約を取り払う機能が導入される予定です。ちなみにこの機能、公式には Task-like (Task っぽい) という名称で呼ばれているようです。

f:id:xin9le:20160728155236p:plain

注意事項

この記事は 2016/07/28 現在にプロトタイピング実装されている機能を実際に動かしてみたものに基づいて書いています。ここに書いてあることは今後変更されたり、リリースまでに機能追加されたりすることが多分に予想されます。すべてを鵜呑みにせず「こんな機能が来そう」くらいで読んでいただければと思います。

ValueTask<T>

ValueTuple<T> の記事でも多少説明しましたが、参照型はヒープ領域というメモリ空間を使用します。このメモリ空間の解放には若干重た目のガベージコレクション処理が走ります。一方値型はスタック領域というメモリ空間を使用し、こちらはより軽量な解放処理となります。これまで提供されてきた Task 型は参照型ですが、値型である ValueTask 型を提供することでいくつかのケースでのパフォーマンス改善が見込まれます。この ValueTask はすでに .NET Core に実装されています。

ValueTask は今回の主題である Task-like の機能に準拠しているため、以下のような感じで非同期メソッドが書けるようになります。

//--- Task 型以外の戻り値! 
async ValueTask<int> GetValueAsync()
{
    await Task.Delay(1000);
    return 123;
}

ValueTask は内部で Task を抱える実装をしているのですが、内包する Task を使うケースと使わないケースがあります。ValueTask にすることで効果が発揮されるのは、このうちの Task を使わないケースです。たとえば、以下のように await を通るか通らないかで変わります。

async ValueTask<int> DoSomethingAsync()
{
    var useInternalTask = true;
    if (useInternalTask)
    {
        //--- このコードパスは内包する Task を利用する
        await Task.Delay(1000);
        return 123;
    }

    //--- こっちのコードパスは内包する Task を利用しません
    return 456;
}

参照型の Task を利用しないコードパスを通ることが多いケースや、モデルの奥深くでだけ非同期処理が行われているような場合は ValueTask 型がパフォーマンスに大きく寄与すると思われます。

戻り値になれる型の条件

非同期メソッドの戻り値にするためには、以下のシグネチャを持つメソッドを実装している必要があります。

//--- 非同期メソッドの戻り値となる型
public class MyTask
{
    //--- 静的メソッドじゃなきゃダメ!
    public static MyTaskBuilder CreateAsyncMethodBuilder() => null;
}

//--- コンパイラが非同期メソッドを実現するのに必要な機能が詰まった型
public class MyTaskBuilder
{
    public void Start<TStateMachine>(ref TStateMachine stateMachine)
        where TStateMachine : IAsyncStateMachine
    {}

    public void SetStateMachine(IAsyncStateMachine stateMachine){}
    public void SetResult(){}
    public void SetException(Exception exception){}
    public MyTask Task => default(MyTask);

    public void AwaitOnCompleted<TAwaiter, TStateMachine>(ref TAwaiter awaiter, ref TStateMachine stateMachine)
        where TAwaiter : INotifyCompletion
        where TStateMachine : IAsyncStateMachine
    {}

    public void AwaitUnsafeOnCompleted<TAwaiter, TStateMachine>(ref TAwaiter awaiter, ref TStateMachine stateMachine)
        where TAwaiter : ICriticalNotifyCompletion
        where TStateMachine : IAsyncStateMachine
    {}
}

コンパイラはダックタイピング的に判定するため、何かの型を継承する必要はありません。MyTaskMyTaskBuilder に当たる部分の型名は任意なので、そのおかげで Task-like とする (= 任意の型を戻り値にする) ことができます。

どうしてこんな Builder が必要なのかは非同期メソッドを逆コンパイルしてみれば分かります。4 年前に内部実装について書いているのでそちらも参考にしてください。

最小限の実装で書いてみる

先の条件を満たしつつ、ほぼほぼ最小限の実装で書いてみると以下のような感じになります。ちょっと意味わからん系かもしれませんが、このくらいしないと任意の型を非同期メソッドの戻り値にできません。若干というか結構ハードル高いです。

//--- 非同期メソッドの戻り値となる型
public class MyTask<T>
{
    private Task<T> Task { get; }
    public T Result => this.Task.GetAwaiter().GetResult();
    public MyTask(Task<T> task){ this.Task = task; }
    public static MyTaskBuilder<T> CreateAsyncMethodBuilder() => new MyTaskBuilder<T>();
}

//--- コンパイラが非同期メソッドを実現するのに必要な機能が詰まった型 (MyTask<T> 専用)
public class MyTaskBuilder<T>
{
    private TaskCompletionSource<T> tcs = new TaskCompletionSource<T>();

    public void Start<TStateMachine>(ref TStateMachine stateMachine)
        where TStateMachine : IAsyncStateMachine
        => stateMachine.MoveNext();

    public void SetStateMachine(IAsyncStateMachine stateMachine){}
    public void SetResult(T result) => this.tcs.SetResult(result);
    public void SetException(Exception exception) => this.tcs.SetException(exception);
    public MyTask<T> Task => new MyTask<T>(this.tcs.Task);

    public void AwaitOnCompleted<TAwaiter, TStateMachine>(ref TAwaiter awaiter, ref TStateMachine stateMachine)
        where TAwaiter : INotifyCompletion
        where TStateMachine : IAsyncStateMachine
        => awaiter.OnCompleted(stateMachine.MoveNext);

    public void AwaitUnsafeOnCompleted<TAwaiter, TStateMachine>(ref TAwaiter awaiter, ref TStateMachine stateMachine)
        where TAwaiter : ICriticalNotifyCompletion 
        where TStateMachine : IAsyncStateMachine
        => awaiter.OnCompleted(stateMachine.MoveNext);
}

既存の型を戻り値にする

例えば、WinRT / UWP 系アプリケーションや Reactive プログラミングとの相互運用性を高めるため、以下のようなことをしたい場合があるかもしれません。

static void Main()
{
    //--- WinRT / UWP の非同期処理インターフェース
    IAsyncAction action = UwpAsync();
}

static async IAsyncAction UwpAsync()
{
    await Task.Delay(100);
}
static void Main()
{
    //--- Reactive なシーケンス
    IObservable<T> sequence = SampleAsync();
}

static async IObservable<T> SampleAsync()
{
    await Task.Delay(100);
    return 123;
}

しかし、結論から言うと現状 (執筆時点) ではできません。非同期メソッドの戻り値にするためには静的な CreateAsyncMethodBuilder メソッドを実装する必要がありますが、既存の型やインターフェースに対してはその拡張ができないためです。この辺りは Extension Everything (= なんでも拡張) という別機能で提供される静的拡張メソッドによる解決で今後できるようになるかもしれません。

Tasklike 属性

CreateAsyncMethodBuilder メソッドをイチイチ作るのは非常に面倒です。それがなくても実現できるよう、以下のような TasklikeAttribute が検討されたりもしています。しかしこれも現状では搭載されておらず、先の「静的拡張メソッド」による解決になる可能性が高いようです。

//--- こんな属性があるとする
namespace System.Runtime.CompilerServices
{
    public class TasklikeAttribute : Attribute
    {
        public TasklikeAttribute(Type builder){}
    }
}

//--- こんな風にどのビルダーを使うかをマーキングする
[Tasklike(typeof(MyTaskBuilder))]
class MyTask {}

//--- 実装の詳細は省略
class MyTaskBuilder { ... }

このあたりも今後の動向に注目したいところです。