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

xin9le.net

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

Roslyn の開発進捗が更新されました

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 { ... }

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

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

Tips Office

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

このフォルダ以下にある全部の 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;
            }
        }
    }
}

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

C# 7 をライブコーディングで解説してきました vol.2

C# Community

f:id:xin9le:20160719005008p:plain

7/16 (土)、福井大学でソフトウェア技術者サミット in 福井 2016が開催され、そこに参加/登壇してきました。ハッシュタグは #sesfukui (Software Engineer Summit in Fukui)。

開催の運びとなったのは VSUG DAY - THE FINAL - の懇親会で僕の師匠である小島さん (@Fujiwo) が「同じぐらいの規模のヤツ、福井でもやろう!」と言ったことに始まります。小島さんの人脈と運営力のおかげで東京でも滅多に集まらないような素晴らしい方々が終結しました。そんな中、(登壇者の中では) 最年少のペーペーが偉そうに C# 7 の最新機能 (7/16 現在) について話してきました。

セッション資料

セッション資料は前回同様、ライブコーディングによるデモのおさらいという形で簡単にまとめてあります。PDF 形式で Docs.com に置いてあるので、ご自由にダウンロードなどしてください。

タイトルスライドのデザイン、今回は「福井の夏」ということで名物である三国花火を選んでみました。たぶん、みんな気付いてないと思うw

ざっくりと内容

簡単に言うと、C# ユーザー会 //build/ 2016 振り返り勉強会 でやった内容 + 追加された以下の機能の紹介でした。

機能は増えたけど説明する時間は全く増えていない & 余計なことしてペース配分をミスるというアレで、Type Switch についての説明ができなかったというガッカリぶり。とても反省しています...。次があるかは分からないけど、同じミスはしない所存!(キリッ

世界で最も新しいコンパイラ

当日会場に着いてから Roslyn の最新のコミットを pull してビルドしたものでデモしました。正真正銘世界最新の C# コンパイラというキャッチーな煽り文句を言いたかっただけなのですが、やっぱり何があっても as-is なワケで案の定デモ中に Visual Studio が落ちるというイタイ目に...。まぁそれも一興ということでw

他の方々の資料 / レポート

型分解 - タプルから変数への展開

C#

7/12 (火) の早朝、長らく検討/開発されてきた待望の Deconstructions (= 型分解) が Roslyn の master ブランチにマージされました!(型分解というのは僕のテキトーな訳で、正式名称は未定)

タプル構文が複数の値をひとつにまとめる機能だったのに対し、Deconstructions はその逆の複数の値への分解をサポートします。C# 7 への搭載の期待もさることながら、直近では Visual Studio 15 Preview 4 にも含まれるのではないかと思われます。タプル構文については以下を参照ください。

f:id:xin9le:20160714055622p:plain

注意事項

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

基本的な記法

まず、System.ValueTuple を分解しつつ基本の書き方を見ていきましょう。以下のような 3 種類の書き方があります。(もっとあったらゴメンナサイ...

//--- こんなタプル型のインスタンスがあったとする
var t = (123, "abc");

//--- Let's 分解
(int x, int y) = t;  //--- 型を明示
(var x, var y) = t;  //--- 一部 (or 全部) の型を推論
var (x, y) = t;      //--- すべてを型推論で解決

//--- それぞれの別々の変数として利用できる
Console.WriteLine($"({x}, {y})");  //--- (123, abc)

既存の変数への割り当て

先の例では変数を生成しつつ値を分解/割り当てしましたが、すでに宣言された既存の変数に対しても適用できます。この場合は型の指定は必要ありません

int x;
string y;
(x, y) = (123, "abc");  //--- ValueTuple を生成して即座に x, y に分解

配列要素やプロパティへの割り当て

変数にだけ代入できるわけではありません。もちろん配列の要素や setter のあるプロパティにも適用できます。

//--- 配列要素の入れる
var a = new int[2];
(a[0], a[1]) = (1, 23);

//--- プロパティに入れる
class Person
{
    public string Name { get; set; }
    public int Age { get; set; }
}
var p = new Person();
(p.Name, p.Age) = ("xin9le", 31);

逆コンパイル

あまりに見慣れない書き方過ぎてもはやワケわからん状態かと思うので (?)、逆コンパイルしてどう実現されているかを確認してみましょう。

//--- これは
var (x, y) = (123, "abc");

//--- こう展開される
ValueTuple<int, string> t = new ValueTuple<int, string>(123, "abc");
int x = t.Item1;
string y = t.Item2;

変数を並べた順に .Item1 .Item2 ...のプロパティが割り当てられていることが分かります。

暗黙的型変換

(当然ですが) 型を指定することで、暗黙的型変換の適用により互換のある型として値を受けとることができます。もちろん、互換のない型を指定するとコンパイルエラーになります。

var t = (123, "abc");
(long x, object y) = t;  //--- OK!!
(char x, string y) = t;  //--- int は char で受けられないので NG!!

任意の型の分解

ここまで ValueTuple 型の分解を見てきました。Deconstructions はタプル構文の対になる機能なので ValueTuple 型にしか適用できないのか、と言うと当然そんなことはなく、ちゃんと任意の型に対して適用できます。ただ、当然ながら何も追加実装をしなくても値の分解ができるわけではありません。既存の型をどのようなルールで分解するかがコンパイラには分からないからです。そのルールは以下のように Deconstruct メソッドを実装することで実現/決定します。

static void Main()
{
    var p = new Person();
    var (name, age) = p;  //--- ここで Deconstruct メソッドが呼ばれる
    Console.WriteLine($"({name}, {age})");  //--- (xin9le, 31)
}

class Person
{
    public string Name { get; } = "xin9le";
    public int Age { get; } = 31;

    //--- 型分解のための特殊なメソッド
    public void Deconstruct(out string name, out int age)
    {
        name = this.Name;
        age = this.Age;
    }
}

拡張メソッドとしての実装

上記の方法では Deconstruct メソッドをインスタンスメソッドとして実装しましたが、拡張メソッドとして実装しても OK です。以下のような感じです。

class Person
{
    public string Name { get; } = "xin9le";
    public int Age { get; } = 31;
}

static class PersonExtensions
{
    public static void Deconstruct(this Person self, out string name, out int age)
    {
        if (self == null)
            throw new ArgumentNullException(nameof(self));
        name = self.Name;
        age = self.Age;
    }
}

複数の分解方法の提供

Deconstruct メソッドはひとつでなければならないという制限はありません。複数の分解方法を提供したい場合は、複数の Deconstruct メソッドを実装することもできます。

static void Main()
{
    var v = new Vector3 { X = 1, Y = 2, Z = 3 };
    var (x2, y2) = v;      //--- 引数 2 つの方が呼び出される
    var (x3, y3, z3) = v;  //--- 引数 3 つの方が呼び出される
}

class Vector3
{
    public int X { get; set; }
    public int Y { get; set; }
    public int Z { get; set; }

    public void Deconstruct(out int x, out int y)
    {
        x = this.X;
        y = this.Y;
    }

    public void Deconstruct(out int x, out int y, out int z)
    {
        x = this.X;
        y = this.Y;
        z = this.Z;
    }
}

逆コンパイル

もう想像できているかもしれませんが、任意の型の分解がどのように実現されているかを確認してみましょう。以下のように展開されます。原理が分かれば簡単ですね!

var person = new Person();

//--- これは
var (name, age) = person;

//--- こう展開される (Deconstruct メソッドの呼び出しになる)
int x;
string y;
person.Deconstruct(out x, out y);
int name = x;
string age = y;

ちなみにすでにお気付きかもしれませんが、先に紹介した ValueTuple 型とは展開のされ方が全然違います。ValueTuple には Deconstruct メソッドの実装もないですし、かなり特別扱いされていることが分かります。

任意の型分解のまとめ

  • メソッド名は Deconstruct
  • 引数に out キーワードを付ける
  • 第 1 引数から順番に変数にマッピングされる
  • インスタンスメソッド or 拡張メソッドとして実装する
  • インスタンスメソッドと拡張メソッドがある場合はインスタンスメソッドが優先される
  • 分解方法 (Deconstruct メソッド) は複数あっても構わない

System.Tuple の分解

System.ValueTuple は特別扱いされて展開されることが分かりました。では参照型になっただけの System.Tuple はどうかと言うと、コンパイラによる特別扱いはありません。なので、同じように型を分解をするためには拡張メソッドの実装が必要になります。

//--- こんな感じ
public static class TupleExtensions
{
    public static void Deconstruct<T1, T2>(this Tuple<T1, T2> value, out T1 item1, out T2 item2)
    {
        item1 = value.Item1;
        item2 = value.Item2;
    }
}

しかし、さすがに毎度×2 個々人が実装するのは無慈悲過ぎるので、CoreFx では System.Tuple 型に対する Deconstruct 拡張メソッドを提供してくれています。よかった。

ちなみに、ValueTuple と似たような型に KeyValuePair がありますが、これも Deconstruct 拡張メソッドを用意しておくと幸せになれると思います。現状では提供されていませんが、Tuple と同様 .NET Framework / .NET Core に標準搭載されるかも (?) しれません。

こんなこともできる / これはできない

ちょっとオマケではありますが、挙動を調べてみたものを並べてみます。執筆時点での話なので、できないものも将来的にできるようになるかもしれません。

foreach で使える

変数を受けるのは、当然 foreach でもできます。場合によってはシンプルになって良いかもしれませんね。

var collection = new []
{
    (1, 'a'),
    (2, 'b'),
    (3, 'c'),
};
foreach (var (x, y) in collection)  //--- こんなのも OK
    Console.WriteLine($"({x}, {y})");

クエリ式では使えない

foreach で使えたのでクエリ式でも行けるかも!と思ったけれど、使えませんでした。

//--- できてほしいけれど、現状はまだダメ
var query = from (x, y) in collection
            where x >= 2
            select y;

out var では使えない

先日紹介した変数宣言式 (= Out Variable Declarations) で使えるかも試してみましたが、ここでも使えませんでした。できたら結構便利そうなんだけどなぁ...。

bool OutVar(out ValueTuple<int, string> result)
{
    result = (123, "abc");
    return true;
}

if (OutVar(out var (x, y)))  //--- コンパイルエラー
    Console.WriteLine($"({x}, {y})");

入れ子の分解ができる

入れ子になっていてもちゃんと分解できます。なんとも親切!

//--- こんな入れ子になったものも
(int, (string, char)) GetValueTuple()
    => (1, ("abc", 'あ'));

//--- 分解/展開できちゃう!
var (x, (y, z)) = GetValueTuple();
Console.WriteLine($"({x}, {y}, {z})");  //--- (1, abc, あ)

超簡単スワップ! キタ━(゚∀゚)━!

タプル構文と型分解のおかげで、値のスワップが神がかり的に簡単になります!.Swap(x, y) メソッドよ、安らかに眠れ... (-人-)

var x = 1;
var y = 2;
(x, y) = (y, x);  //--- なんとこれだけ!直観的!
Console.WriteLine($"({x}, {y})");  //--- (2, 1)