xin9le.net

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

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;
            }
        }
    }
}

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

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

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

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

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

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

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

f:id:xin9le:20160714055622p:plain

基本的な記法

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

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

//--- Let's 分解
(int x, string 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)