xin9le.net

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

LINQ 大好き C#er がアイドルグループ LinQ のセットリスト考えてみた

C#er といえば LINQ が大好き!もちろん LinQ も大好き!?

f:id:xin9le:20160628015532p:plain

昔は TwitterLINQ と呟けば LinQ のメンバーさんにフォローされたり Fav されたりしたものでした。その頃から「せっかく LinQ なんて超素敵な名前だったら LINQ を題材にしたアルバムでも作ればいいのに」なんて思っていました。

数年経ってふとそんなことを思い出したので、気分転換に考えてみました。すべてネタなので真に受けないでください。

恋の Query - あなたとわたしのメソッドチェイン -

  1. from in 九州
  2. GroupBy Kawaii Girls
  3. Join ファンクラブ
  4. ありがとう fans.First();
  5. Select - i なんか要らない -
  6. OrderBy イケメン
  7. Distinct 量産型
  8. Where().Where().Where() - 王子様探し -
  9. 私のファンを SelectMany
  10. foreach 握手会
  11. Take(7) - 神セブン -
  12. 忘れないで Reset
  13. members.Count() == 29;
  14. Union HKT48
  15. お願い、何度も舐めないで
  16. 迷うわ、Any なの? All なの?どっちが好きなの?
  17. yield break; - きっとまた後で -
  18. セピア色の IQueryable
  19. Single - 私だけにして -
  20. Except - 卒業 -
  21. Finally...

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

f:id:xin9le:20160522222757p:plain

ここまで数回に渡って C# 7 の新機能について解説してきました。記事は以下にまとめてありますので是非ご覧ください。

と、そんなこんなまとめていたのを「C# ユーザー会 //build/ 2016 振り返り勉強会」で解説してきました。当日の朝にクローンしてきたばっかりのコードからビルドしたコンパイラを使って、ライブコーディング中心でお届け!実際に動かして説明するのが一番分かりやすいですよね、やっぱり!

セッション資料

セッション資料はライブコーディングによるデモのおさらいという形で簡単にまとめてあります。

これまで数年タイトルスライドが同じだったのですが、いい加減に飽きていたのでちょっとだけ綺麗に作ってみました。//build/ の振り返りということで、//build/ のサイト背景を使ってみました。ちょっとした遊び心でしたが、気付いた方いたかな?w

みなさんの反応

結構な方が見に来てくださっていたので、お察しの通り (?) 実は結構緊張していました。最前列には MVP 達がシッカリ陣取り、後ろの方では岩永先生 (@ufcpp) に見られている...。隅から隅まで圧力のカタマリでライブコーディングしているときに若干指が震えていました!(ほんとに

頷きながら聞いてくれる方、「おぉ!」と驚いた表情を見せる方、バシバシ写真を撮ってくださる方、いろんな反応が見えました。セッション関連のツイートを追いかけてみても、みなさん結構反応してくださっていましたね!特に @chomado さんは写真付きでバンバン投稿してくださっていて、大変ありがたかったです :)

ちょまどさんと言えば今回初めてお会いしたのですが、セッション後に「すごく良かったです!」とすぐに言いに来てくださいました。そういう反応は本当に嬉しいですねー。1 か月以上準備してきてよかった!懇親会での感想やアンケート結果にも「ライブコーディングがとても良かった」系の反応が結構あって、とても自信になります。今後もライブコーディングマンとして頑張ろうかなw

「C# vNext」でググれ!

なんと今なら「C# vNext」でググるとこのサイトがトップに表示されますw

というのをイベント前日に知って最後にネタでブッ込んでみました。岩永さんの「C# でググれ」を真似して「C# vNext でググれ!」、ということで今後ともよろしくお願いします!

型スイッチ

F# などの関数型言語をやっている方にはおなじみのパターンマッチング。「10 年おせーよ!」と言われそうですが、C# 7 にもそんな待ちに待った (?) 機能が搭載されそうです。主に既存の is キーワードと switch キーワードを拡張した、型に対するマッチングになる見込みです。本家ではこの機能を Type Switch と呼んでいるようです。パターンマッチングについては以下にまとめられています。

全体計画の一部を実装

パターンマッチングとしては他にも match 式や let ステートメントなども検討されていますが、それらは C# 8 以降での追加になりそうです。詳細は以下を参照してください。

f:id:xin9le:20160513032103p:plain

is 拡張

C# 6 までの is キーワードは以下のように型判定に使われていました。

if (x is string)
{
    //--- 変数 x が string 型だった場合の処理
}

このように is の後ろに型を指定して判定するだけの機能でしたが、これが大幅に拡張されます。

定数マッチング

特定の値かどうかを判定できるようになります。等価演算子 (==) で良いのではないか?と思えるくらいには存在意義を見出せない...。

//--- 何か値があるとする
var x = 123;

//--- C# 6 まではこんな感じ
var a = x == 123;  //--- True
var b = x == 456;  //--- False
var c = x == 'a';  //--- False

//--- C# 7 ではこんな感じで判定できるように!
var a = x is 123;   //--- True
var b = x is 456;   //--- False
var c = x is 'a';   //--- False
var d = x is "123"; //--- Error!! : Cannot implicitly convert type 'string' to 'int'

型マッチング

特定の型かどうかの判定ができるようになります。...と言うと「それ今までと同じやん!」と思われるかもしれませんが、特定の型判定に加えて変数の生成を同時にやってくれます。以下のサンプルを見てください。

//--- 何か object 型に入った値があるとする
object x = "abc";

//--- C# 6 ではこう
var v = x as string;
if (v != null)
{
    //--- 変数 v に対する処理
}

//--- もしくはこう
if (x is string)
{
    var v = (string)x;
    //--- 変数 v に対する処理
}

//--- C# 7 からはこう書ける!
if (x is string v)
{
    //--- 変数 v に対する処理
}

見慣れない書き方に若干戸惑うかもしれませんが、以下のような動きをしています。

  1. xstring 型かどうかを判定
  2. string 型だった場合、string 型の変数 v に値を代入
  3. 評価結果を bool 型で返す

型マッチングの変数スコープ

先の変数 v のスコープは if 文のスコープのみです。なので、以下のように変数として評価結果を受ける場合は v にアクセスできません

object x = 123;
var r = x is string v;
Console.WriteLine(r);  //--- false
Console.WriteLine(v);  //--- 変数 v を参照できないのでコンパイルエラー!

型マッチングが便利なシーン

以下のような null 許容型の判定などで便利そうです。だいぶスッキリと書ける印象があります。

//--- C# 6 ではこう
int? v = x?.y?.z;
if (v.HasValue)
{
    var v = x.GetValueOrDefault();
    //--- v を使った何か
}

//--- C# 7 ではこう書ける!
//--- null のときの評価は当然 false
if (x?.y?.z is int v)
{
    //--- v を使った何か
}

型マッチングの型判定は厳密

これまでの is キーワードと同様、型マッチングの型判定は非常に厳密です。ですので暗黙的型変換などは期待してはいけません。

object x = 123;

Console.WriteLine(x is int);  //--- true
Console.WriteLine(x is long); //--- false

if (x is int  v1){ /* OK */ }
if (x is long v2){ /* NG */ }  //--- 暗黙的型変換は当然してくれません

逆コンパイル

is 式の判定がどのように行われているか、ILSpy で逆コンパイルして覗いてみましょう。以下のように展開されます。

//--- これを逆コンパイルにかけると...
static void Main()
{
    object x = "abc";
    if (x is string v1) Console.WriteLine(v1);
    if (x is int v2)    Console.WriteLine(v2);
}

//--- こう展開される
static void Main()
{
    object x = "abc";

    //--- 参照型の場合はそのまま as で判定
    string v1 = x as string;
    bool flag = v1 != null;
    if (flag)
        Console.WriteLine(v1);

    //--- 値型の場合は null 許容型として判定
    int? num = x as int?;
    int v2 = num.GetValueOrDefault();  //--- if 文の中で展開すればいいのでは?(と思ったり
    bool hasValue = num.HasValue;
    if (hasValue)
        Console.WriteLine(v2);
}

参照型かどうかの判定の場合はただの as キーワードで、値型かどうかの判定の場合は as キーワード + null 許容型で行われるみたいですね。

switch 拡張

is キーワードが拡張されたのと同様の機能が switch 文としても組み込まれます。書き方がちょっと変わっただけなので、is の書き方が分かれば難しくないですね!

object x = 123;
switch (x)
{
    case "abc":
        Console.WriteLine(x);
        break;

    case int v:
        Console.WriteLine(v);
        break;
   
    default:
        Console.WriteLine("default");
        break;
}

when 句による条件設定 (case guard)

C# 6 では 例外フィルター という catch 句の後ろに when 句を付ける条件分岐機能が追加されました。これと似た感じで、case 句の後ろに when 句を入れて条件分岐する「case guard」と呼ばれる機能が追加されます。以下のように使います。

object x = 123;
switch (x)
{
    case "abc":
        Console.WriteLine(x);
        break;

    case int v when 100 < v:  //--- v が 100 より大きいとき
        Console.WriteLine(v);
        break;

    case int v:  //--- v が 100 以下のとき
        Console.WriteLine(v);
        break;
   
    default:
        Console.WriteLine("default");
        break;
}

これでより細かい条件分岐を簡潔に書くことができるようになりましたね!

逆コンパイル

switch 文も逆コンパイルして展開結果を確認してみましょう。上のコードを展開すると以下のようになります。

object x = 123;
object obj = x;  //--- どーにも納得できないけど一度コピーが入ってる...
if (object.Equals(obj, "abc"))
{
    Console.WriteLine(x);
}
else
{
    int? num = obj as int?;
    int v = num.GetValueOrDefault();
    if (num.HasValue && 100 < v)  //--- when 句で指定した条件も入っている
    {
        Console.WriteLine(v);
    }
    else
    {
        num = (obj as int?);
        int v2 = num.GetValueOrDefault();  //--- これも if の中で展開して欲しい
        if (num.HasValue)
        {
            Console.WriteLine(v2);
        }
        else
        {
            Console.WriteLine("default");
        }
    }
}

if 文を使って上手くやっている感じですね。

タプル構文 - 多値戻り値のサポート

C# 7 では言語機能としてタプル (複数の値をまとめる) 構文 がサポートされそうです。これまでも複数の値を簡易的にパッケージングする機能として System.Tuple<T1, T2, ...> が提供されていましたが、より可読性が高く、パフォーマンスが良くなる形になる見込みです。例えば、単純なものだと以下のようになります。

(string, int, 性別) GetUserInfo()
{
    var tuple = ("xin9le", 31, 性別.男);
    return tuple;
}

タプル構文については以下で議論がまとめられています。

f:id:xin9le:20160511025349p:plain

System.ValueTuple 型の提供

これまで複数の値/型をまとめる汎用型としては System.Tuple 型が提供されていました。これは参照型としての表現なのでインスタンスを作るたびにヒープ領域を使用します。C# 7 ではより高い性能を出すことにも注力しているようで、タプル型も値型としての表現が追加される予定です。現時点ですでに CoreFx リポジトリの master ブランチに System.ValueTuple 型が追加されています。

この ValueTuple 型によって、特に後述の「多値戻り値」として利用する場合のパフォーマンス向上が見込まれています。また C# 7 関連のパフォーマンス改善については以下が大変勉強になります。オススメ!

タプル構文は ValueTuple 型の糖衣構文

C# 7 で追加予定のタプル構文は、先に紹介した ValueTuple 型の糖衣構文として提供されそうです。なので以下は完全に同一のコードとして展開されます

//--- こう書いたものは
var t1 = ("xin9le", 31, 性別.男);
var t2 = new ValueTuple<string, int, 性別>("xin9le", 31, 性別.男);

//--- 逆コンパイルするとこうなる
var t1 = new ValueTuple<string, int, 性別>("xin9le", 31, 性別.男);
var t2 = new ValueTuple<string, int, 性別>("xin9le", 31, 性別.男);

また、戻り値も同様です。

//--- これは
(string, int, 性別) GetUserInfo()
    => ("xin9le", 31, 性別.男);

//--- こう展開される
ValueTuple<string, int, 性別> GetUserInfo()
    => new ValueTuple<string, int, 性別>("xin9le", 31, 性別.男);

つまり、タプル構文は ValueTuple 型の表記を省略するショートカット記法ということです。

多値戻り値としての表現

みなさんご存じの通り、プログラミング言語には関数という処理を行う機能単位があります。何か入力 (引数) を与えたら何か結果 (戻り値) が返ってくる、そんなブラックボックスなイメージのシステムです。以下に C# における引数と戻り値の関係をまとめてみます。

入力 出力 書き方
0 0 void Func();
1 0 void Func(T x);
N 0 void Func(T1 x1, T2 x2, ...);
0 1 TResult Func();
1 1 TResult Func(T x);
N 1 TResult Func(T1 x1, T2 x2, ...);
0 N 未提供
1 N 未提供
N N 未提供

Go 言語など複数の戻り値をサポートしているものもありますが、C# では戻り値はひとつという制限があります。ではこれまではどうしていたのかというと、以下の 2 つの方法でした。

  • Tuple 型などのクラス/構造体に詰めて返す
  • ref/out を使って引数で返す

多値戻り値が言語機能として提供されていなくても、それを代替する手法があるので何とかなっています。しかし、やはりそうは言っても「複数の値を返しているように書きたい」と思うのが開発者の人情ってもんです (たぶん。そこで C# 7 からはタプル構文を利用した擬似的な多値戻り値をサポートします。

ではなぜ「擬似的」かと言うと、先ほど説明した通りタプル構文の実態が ValueTuple 型の糖衣構文だからです。実際はひとつの型しか返していません。ですが、戻り値の書き味を見ると複数の値を戻しているように見えます

//--- これだとひとつの値を返しているように見えるけれど
ValueTuple<string, int, 性別> GetUserInfo()
{
    var name = "xin9le";
    var age = 31;
    var sex = 性別.男;
    return ValueTuple.Create(name, age, sex);
}

//--- これだと戻り値が複数あるように見える!(∩´∀`)∩
(string, int, 性別) GetUserInfo()
{
    var name = "xin9le";
    var age = 31;
    var sex = 性別.男;
    return (name, age, sex);
]

値へのアクセスとエイリアス

タプル構文の実態は ValueTuple 型なので、格納した値へのアクセスは以下のようになります。

var t = ("xin9le", 31, 性別.男);
Console.WriteLine(t.Item1);  //--- xin9le
Console.WriteLine(t.Item2);  //--- 31
Console.WriteLine(t.Item3);  //--- 男

...なのですが、これはあまりにも無慈悲な状況です。もちろんこれまでも同じような問題はありました。

  1. 複数の値を返すのにイチイチ型を作りたくない!
  2. よし、匿名型だ!
  3. (型推論でしか使えないので) 関数の戻り値にできない!
  4. 仕方ないから Tuple 型だ!
  5. Item1, Item2 だと何を表しているか分からない!
  6. 仕方ないからクラス作るか...(← イマココ

これを解決するため、タプル構文では Item1Item2 などの値へのアクセスに対するエイリアス機能が提供されます

タプル生成時にエイリアスを付与

この場合は以下のように書けます。

//--- エイリアスを付けてアクセス!
var t = (name: "xin9le", age: 31, sex: 性別.男);
Console.WriteLine(t.name);
Console.WriteLine(t.age);
Console.WriteLine(t.sex);

//--- ただのエイリアスなのでもちろん既定のアクセスも可能
Console.WriteLine(t.Item1);
Console.WriteLine(t.Item2);
Console.WriteLine(t.Item3);

関数の戻り値にエイリアスを付与

この場合は以下のように書きます。引数と対になる形で記述できることに注目です!

(int sum, int count) Tally(IEnumerable<int> list)
{
    var s = 0;
    var c = 0;
    foreach (var value in list)
    {
        s += value;
        c++;
    }
    return (s, c);
}

var t = Tally(new []{ 1, 2, 3 });
Console.WriteLine(t.sum);
Console.WriteLine(t.count);

ただし、この機能を使うには TupleElementNamesAttribute が必要です。.NET Core リポジトリの master ブランチにこの属性の実装が追加されています。

戻り値にエイリアスを付けた場合、以下のように属性として展開されます。

//--- こんな風にエイリアスが戻り値についていると
public static (int, int count) Tally(IEnumerable<int> values)
    => (values.Sum(), values.Count());

//--- 戻り値にこんな属性が付与される
[return: TupleElementNames(new []{ null, "count" })]
public static ValueTuple<int, int> Tally(IEnumerable<int> values)
    => new ValueTuple<int, int>(values.Sum(), values.Count<int>());

上記から、エイリアスは第 1 引数から順番に配列として展開されます。名前がない場合は null が埋め込まれます。また、エイリアス名がひとつも付与されていない場合は属性自体展開されません

値を受ける側でエイリアスを設定

ここまで値を返す側でのエイリアス設定について見てきました。このままでも十分便利なのですが、返された値に対して利用側でエイリアスを付けられるともっと使いやすいかもしれません。そんな要望があったからか、受け側でのエイリアス設定機能が追加されています。

//--- 右辺ではエイリアスを設定していないが、受け側である左辺で設定している
(string name, int age) t = ("xin9le", 31);
Console.WriteLine($"{t.name} is {t.age} years old.");

//--- 関数の戻り値でエイリアスが設定されていても変更できる
(int a, int b) t = Tally(new []{ 1, 2, 3 });
Console.WriteLine(t.a);
Console.WriteLine(t.b);

しかし、以下のような型推論を利用した書き方はコンパイルエラーになるので注意が必要です。

//--- こんな風に var を使うのはダメ
(var a, int b) t = Tally(new []{ 1, 2, 3 });

// CS0825 : The contextual keyword 'var' may only appear within a local variable declaration or in script code
// CS0029 : Cannot implicitly convert type '(int sum, int count)' to '(var a, int b)'

また、タプルをネストしていても同様に書くことができます。

(int a, int b, (int c, int d) nested, int e) t = (1, 2, (3, 4), 5); 
Console.WriteLine(t.a);         //--- 1
Console.WriteLine(t.b);         //--- 2
Console.WriteLine(t.nested.c);  //--- 3
Console.WriteLine(t.nested.d);  //--- 4
Console.WriteLine(t.e);         //--- 5

インテリセンスとの連携

現状すでにインテリセンスともシッカリと連携していて、以下のように表示されます。とても分かりやすい :)

f:id:xin9le:20160511020953p:plain

あくまでもエイリアス

ですので、いくらエイリアスを付けてもコンパイル結果は Item1, Item2 のような形に展開されてしまいます。賢いコンパイラが良きに計らってくれているということですね!

//--- これは
var t = (name: "xin9le", age: 31, sex: 性別.男);
Console.WriteLine(t.name);
Console.WriteLine(t.age);
Console.WriteLine(t.sex);

//--- やっぱりこう展開される
var t = new ValueTuple<string, int, 性別>("xin9le", 31, 性別.男);
Console.WriteLine(t.Item1);
Console.WriteLine(t.Item2);
Console.WriteLine(t.Item3);

エイリアス名の制約

大変便利なエイリアス名ですが、付けられる名称にはいくらかの制約があります。まず以下の名称は付けられません (大文字/小文字は完全一致)

  • CompareTo
  • Deconstruct
  • Equals
  • GetHashCode
  • Rest
  • ToString
//--- これはダメ
var t = (Deconstruct: 123, ToString: "abc");

また、エイリアス名でない Item1Item2... などの名称は引数の位置と一致しない限り付けることができません。(こんなコトする人はいないと思いますが...)

//--- これは OK
var t1 = (Item1: 123, "abc");

//--- Item1 は第 1 引数にアクセスするプロパティなので第 2 引数には付けられない
var t2 = (123, Item1: "abc");

エイリアス数の制約

タプルは複数の値をまとめるための機能なのでふたつ以上の値を持つ必要があります。そのため、以下のようにひとつしか値を持たないタプル構文はエラーになります。

//--- これはダメ
var t = (name: "xin9le");

//--- ひとつだけ値を取る ValueTuple 型はないので展開できない
var t = new ValueTuple<string>("xin9le");

エイリアスの有効範囲

タプル構文が実装された当初は、プロジェクトをビルドしてバイナリになった段階でエイリアス名は消失していました。つまり、エイリアスありのタプルを返す便利機能を共通ライブラリとして配布しても利用側ではエイリアスを使用できませんでした。

//--- CoreLib.dll にこんな拡張メソッドがあるとする
public static (int sum, int count) Tally(this IEnumerable<int> list)
{
    var s = 0;
    var c = 0;
    foreach (var value in list)
    {
        s += value;
        c++;
    }
    return (s, c);
}

//--- 当初は App.exe でエイリアスが使えなかった!
var numbers = new []{ 1, 2, 3 };
var t = numbers.Tally();
Console.WriteLine(t.sum);   //--- 当初はコンパイルエラーになっていた!(← 今はできる
Console.WriteLine(t.Item1); //--- これは当然 OK

しかし、TupleElementNamesAttribute が実装されたことによりアセンブリ内にエイリアス名を埋め込むことができるようになったので、そのような制約がなくなりました!(∩´∀`)∩

値の多いタプルへのアクセス

ValueTuple 型の実装を見ると、7 番目までは ItemX プロパティで、8 番目以降は Rest プロパティ (= 残りの部分という意味) でのアクセスを要求されていることが分かります。つまりこんな感じ。

//--- 10 個の値があるとこんな感じ
var t = (1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
Console.WriteLine(t.Item1);  //--- 1
Console.WriteLine(t.Item7);  //--- 7
Console.WriteLine(t.Rest);   //--- (8, 9, 10)

//--- 逆コンパイル結果
//--- 最後の 8 つ目以降は ValueTuple 型のネスト構造になる
var t = new ValueTuple<int, int, int, int, int, int, int, ValueTuple<int, int, int>>(1, 2, 3, 4, 5, 6, 7, new ValueTuple<int>(8, 9, 10));

そして実は値にアクセスする際も Rest プロパティを使わずに Item8Item10 のような書き方ができます!

//--- なんと Item プロパティのままアクセスできる!
var t = (1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
Console.WriteLine(t.Item1);  //--- 1
Console.WriteLine(t.Item7);  //--- 7
Console.WriteLine(t.Item8);  //--- 8
Console.WriteLine(t.Item10); //--- 10

//--- 逆コンパイル結果
//--- ネストしたタプルへのアクセスに書き換えてくれる
var t = new ValueTuple<int, int, int, int, int, int, int, ValueTuple<int, int, int>>(1, 2, 3, 4, 5, 6, 7, new ValueTuple<int>(8, 9, 10));
Console.WriteLine(t.Item1);       //--- 1
Console.WriteLine(t.Item7);       //--- 7
Console.WriteLine(t.Rest.Item1);  //--- 8
Console.WriteLine(t.Rest.Item3);  //--- 10

上記のようにコンパイラが良きに計らって展開してくれていることが分かります。実際の ValueTuple の実装を気にすることなく気軽にタプル構文を書けるのは素晴らしいですね!

最新の C# コンパイラ を利用してビルド/デバッグしてみた

そんな方法はひとつしかない、リポジトリをクローンしてビルドすることだ

ということでやってみましょう!ちなみにこの記事は 5/9 現在の情報を基に書いています。近い将来に変更になる可能性は多分にありますので、その点ご了承ください m( )m

Step.1 - Roslyn リポジトリをクローン

C# コンパイラ (Roslyn) はオープンソースなコンパイラで、GitHub で公開されています。HTTPS なり SSH なりでクローンしましょう。

f:id:xin9le:20160508231240p:plain

Step.2 - future ブランチに切り替え

執筆時点では C# 7 は鋭意開発中というステータスです。開発中の機能は基本的にそれごとにブランチが切られていますが、できたもの/いいところまで行ったものは順次 future ブランチにマージされていっています。なので、future ブランチに切り替えましょう。

f:id:xin9le:20160509031853p:plain

Step.3 - Roslyn のビルド

続いて Roslyn をビルドします。cibuild.cmd というバッチファイルがルートフォルダに置いてあるのでこれを使います。既定では Debug ビルドの設定になっているので、Release ビルドの設定に変更します。/release の引数を与えて実行するだけです。

C:\roslyn> cibuild /release

長時間 CPU を全力でブン回しながらビルド / 単体テストが行われます。無事完了したら roslyn\Binaries\Release フォルダに VBCSCompiler.execsc.exe ができているはずです。これを使って C# コードをビルドすれば OK でしょう。

Step.4 - C# プロジェクトで Roslyn コンパイラを参照する

プロジェクト単位での設定を書いているので何度も Hello World プロジェクトを作る方には若干メンドクサイですが、設定してしまえばオシマイです。まず通常のコンソールアプリのプロジェクトを作成します。ビルドした Roslyn コンパイラを利用するため、.csproj ファイルを開いて、PropertyGroup 配下に CscToolPath のタグ設定を追加します。

<PropertyGroup>
    <!-- ↓↓ C# コンパイラのあるフォルダを絶対パスで追加 ↓↓ -->
    <CscToolPath>C:\roslyn\Binaries\Release</CscToolPath>

    <!-- 以下いろいろ既定の設定 -->
    <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
    <Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
</PropertyGroup>

続いて C# 7 の機能を有効化するため、条件付きコンパイルシンボルとして __DEMO__ を追加します。GUI 上で設定する場合は、以下の記事を参考にしてくださっても OK です。

<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
    <!-- ↓↓ __DEMO__ を追加 ↓↓ -->
    <DefineConstants>TRACE;DEBUG;__DEMO__</DefineConstants>
</PropertyGroup>

何のコンパイルシンボルでどの C# 7 の機能が有効になるかは CSharpParseOptions.IsFeatureEnabled メソッドに定義されています。このあたりを追いかけておけば新しい機能への反応も早くなるのではないかと思います。

//--- こんな実装がある
internal bool IsFeatureEnabled(MessageID feature)
{
    switch (feature)
    {
        case MessageID.IDS_FeatureBinaryLiteral:
        case MessageID.IDS_FeatureDigitSeparator:
        case MessageID.IDS_FeatureLocalFunctions:
        case MessageID.IDS_FeatureRefLocalsReturns:
        case MessageID.IDS_FeaturePatternMatching:
        case MessageID.IDS_FeatureTuples:
            // in "demo" mode enable proposed new C# 7 language features.
            if (PreprocessorSymbols.Contains("__DEMO__"))
            {
                return true;
            }
            break;
        default:
            break;
    }

    //--- 以下略
}

Step.5 - C# 7 の機能を使ってデバッグ実行

ここまで設定すれば Visual Studio 2015 Update 2 上でコンパイルしてデバッグ実行することができます (∩´∀`)∩

f:id:xin9le:20160509015152p:plain

ただしどうやらインテリセンスが参照しているコンパイラはこの設定とは違うところにあるらしく、コード上では赤波線が出てしまいます。どこの設定をイジれば解決できるか分からないので現時点ではここまで。設定の在り処をご存知の方は教えていただけると嬉しいです m( )m

と、記事公開から数分で届く神回答...!

おまけ - Visual Studio Code でビルドする

Visual Sdutio Code + C# on Windows では現状デバッグはできませんが、ビルドして exe ファイルを作成することはできます.vscode\tasks.json に以下のような設定を入れれば OK!

{
    "version": "0.1.0",
    "command": "C:\\roslyn\\Binaries\\Release\\csc.exe",
    "showOutput": "always",
    "isShellCommand": true,
    "isBuildCommand": true,
    "args": [
        "${workspaceRoot}\\Program.cs",
        "/out:${workspaceRoot}\\Program.exe",
        "/features:binaryLiterals,digitSeparators,localFunctions,refLocalsAndReturns,patterns,tuples"
    ]
}

C# コンパイラに C# 7 の機能をひとつひとつ有効にしているのがお分かりいただけるかと思います。