xin9le.net

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

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

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++ が見つからないときの対処方法

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

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 がだいぶ近づいているのかなぁという気がしないでもないです。

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

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

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

f:id:xin9le:20160728155236p:plain

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 型がパフォーマンスに大きく寄与すると思われます。

戻り値になれる型の条件

仮に MyTask 型を非同期メソッドの戻り値にするためには、まず以下のシグネチャを持つビルダークラスを実装している必要があります。

//--- コンパイラが非同期メソッドを実現するのに必要な機能が詰まった型
public class MyTaskBuilder
{
    public static MyTaskBuilder Create() => new 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 年前に内部実装について書いているのでそちらも参考にしてください。

戻り値にしたい型自体に属性を付与

このビルダー型がある前提で、以下のように戻り値にしたい型に AsyncMethodBuilder 属性を付けます。プロジェクト全体に対して適用したい場合に利用します。

//--- 非同期メソッドの戻り値となる型に対応するビルダーを属性で指定
[AsyncMethodBuilder(typeof(MyTaskBuilder))]
public class MyTask
{}

static async MyTask TestAsync()
{
    await Task.Delay(1000);
}

戻り値を変更したい非同期メソッド自体に属性を付与

この機能は将来実装される予定になっているものです。2016/10/10 現在では動作しません。こんな感じになるのでは?という推測で書いています。

非同期メソッドの戻り値をピンポイントでカスタムしたい場合には、メソッドに対して個別に AsyncMethodBuilder 属性を付与します。

//--- 非同期メソッドに戻り値に対応するビルダーを属性で指定
[AsyncMethodBuilder(typeof(MyTaskBuilder))]
static async MyTask TestAsync()
{
    await Task.Delay(1000);
}

型自体に属性を付与することは型定義がないできませんが、メソッドに付与する場合は型定義がなくてもできるのがポイントです。これにより既存の型に対して適用するための逃げ道ができます。

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

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

//--- 非同期メソッドの戻り値となる型に対応するビルダーを属性で指定
[AsyncMethodBuilder(typeof(MyTaskBuilder<>))]
public class MyTask<T>
{
    private Task<T> Task { get; }
    public T Result => this.Task.GetAwaiter().GetResult();
    public MyTask(Task<T> task){ this.Task = task; }
}

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

    public static MyTaskBuilder<T> Create() => new MyTaskBuilder<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 プログラミングとの相互運用性を高めるため以下のようなことをしたい場合があるかもしれません。この場合は独自ビルダーを作成してメソッドに対して属性を設定することで解決できます。もちろん標準ライブラリが IObservable<T>IAsyncActionAcyncMethodBuilder 属性を付けてくれれば、このような対応は不要になります。

IObservable<T> に対応

static void Main()
{
    //--- Reactive なシーケンス
    IObservable<T> sequence = SampleAsync();
}

//--- 対象の非同期メソッドに適用
[AsyncMethodBuilder(typeof(IObservableBuilder<>))]
static async IObservable<T> SampleAsync()
{
    await Task.Delay(100);
    return 123;
}

//--- async/await を IObservable<T> に対応させる
public class IObservableBuilder<T>
{
    public IObservable<T> Task => this.tcs.Task.ToObservable();
    //--- その他は前述の MyTaskBuilder<T> と同様なので省略
}

/*
//--- Subject<T> を使って書くとこんな感じ
public class IObservableBuilder<T>
{
    public static IObservableBuilder<T> Create() => new IObservableBuilder<T>();

    private Subject<T> subject = new Subject<T>();
    public void SetResult(T result)
    {
        this.subject.OnNext(result);
        this.subject.OnCompleted();
    }
    public void SetException(Exception exception) => this.subject.OnError(exception);
    public IObservable<T> Task => this.subject;

    //--- その他は前述の MyTaskBuilder<T> と同様なので省略
}
*/

IAsyncAction に対応

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

//--- 対象の非同期メソッドに適用
[AsyncMethodBuilder(typeof(IAsyncActionBuilder))]
static async IAsyncAction UwpAsync()
{
    await Task.Delay(100);
}

//--- async/await を IAsyncAction に対応させる
public class IAsyncActionBuilder
{
    public IAsyncAction Task => this.tcs.Task.AsAsyncAction();
    //--- その他は前述の MyTaskBuilder と同様なので省略
}

世界で 5 人だけ

以下のディスカッションにあるのですが、この機能は C# 7 に入りそうなものの「使うのは世界で 5 人ぐらいのものだろう」と言われています。非同期処理を独自の型で表現したいケースがどれほどあるのかということだと思いますが、まずないでしょう。なので、何か特殊な差し込みをしたいなどのことがない限り、この機能のことを知っている必要はないと思いますw

サクッと簡単!Excel → PDF 変換

会社で今日こんなお願いをされました。

このフォルダ以下にある全部の Excel ファイルを PDF にしてほしい!ファイル数メッチャあるからチマチマやってられーん!Help !!

ということでサクッとツールを作ってあげたわけですが、案外便利なのではないかと思ったので載せておきます。

f:id:xin9le:20160721220738p:plain

Excel → PDF 変換

Office PIA (相互運用アセンブリ) を使って変換するためのエッセンスは以下のような感じです。Workbook.ExportAsFixedFormat メソッドを使いましょうPDF だけでなく XPS 形式での出力も可能です。

var sourcePath = @"C:\Temp\sample.xlsx";
var targetPath = sourcePath.Replace(".xlsx", ".pdf");
var format = XlFixedFormatType.xlTypePDF;
var quality = XlFixedFormatQuality.xlQualityStandard;

var app = new Application();
var workbook = app.Workbooks.Open(sourcePath);  //---ブックを開いて
workbook.ExportAsFixedFormat(format , targetPath, quality);  //--- PDF形式で出力
app.Quit();

ソースコード

とりあえずでワンポイントで動けばいい系なので超絶雑ぃですが、全体像でもこれだけです。app.config に対象フォルダの指定を外出ししてあるので汎用的に利用できます。

using System;
using System.Configuration;
using System.Diagnostics;
using System.IO;
using System.Runtime.InteropServices;
using System.Threading;
using Microsoft.Office.Interop.Excel;

namespace Excel2Pdf
{
    class Program
    {
        static void Main()
        {
            var rootPath = ConfigurationManager.AppSettings["SourceFolderPath"];
            var files = Directory.GetFiles(rootPath, "*.xlsx", SearchOption.AllDirectories);

            //--- 件数
            Console.WriteLine($"対象フォルダ : {rootPath}");
            Console.WriteLine($"ファイル数 : {files.Length}");

            //--- 開始確認
            Console.WriteLine("Please press any key to start.");
            Console.ReadLine();

            //--- 変換
            for (int i = 0; i < files.Length; i++)
            {
                var path = files[i];
                Console.WriteLine($"{i + 1}/{files.Length} : {path.Replace(rootPath, string.Empty)}");
                Convert(path);
                GC.Collect();
            }

            //--- おまじない的に3秒ほど待ってみる
            Thread.Sleep(3000);

            //--- Excel.exe が死ななかったら困るので、念のためプロセスを強制的に殺す処理を入れておく
            var excels = Process.GetProcessesByName("EXCEL");
            foreach (var x in excels)
                x.Kill();
        }

        static void Convert(string sourcePath)
        {
            Application app = null;
            Workbook workbook = null;
            try
            {
                //--- Excelファイルを開く
                app = new Application();
                workbook = app.Workbooks.Open(sourcePath);

                //--- PDFとして保存
                var targetPath = sourcePath.Replace(".xlsx", ".pdf");
                workbook.ExportAsFixedFormat(XlFixedFormatType.xlTypePDF, targetPath, XlFixedFormatQuality.xlQualityStandard);
            }
            catch (Exception ex)
            {
                Console.WriteLine($"エラー : {ex.Message}");
            }
            finally
            {
                if (workbook != null)
                {
                    Marshal.ReleaseComObject(workbook);
                    workbook = null;
                }

                //--- Excelを終了
                app.Quit();
                Marshal.ReleaseComObject(app);
                app = null;
            }
        }
    }
}

こんなので人助けになって喜ばれるんだから、プログラミングはできて損しないですね!