連載
» 2018年01月17日 05時00分 公開

.NET TIPS:構文:複数のオブジェクトを一時的に1つにまとめるには?[C#/VB、.NET Framework 4.7以降]

.NET Framework 4.7で追加されたValueTuple構造体とタプル構文を使うと、複数のオブジェクトをひとまとめにしてメソッドの返値などで使える。

[山本康彦,BluewaterSoft/Microsoft MVP for Windows Development]
「.NET TIPS」のインデックス

連載「.NET TIPS」

 専用のクラスや構造体を定義するほどのことでもないが、複数のオブジェクトを一時的に1つにまとめたいときがある。代表的な場面は、メソッドの返値だ。メソッドは1つのオブジェクトしか返せない。これまでは複数の結果を返そうと思ったら、複数の結果を格納するためだけに型を定義したり、あるいは、out引数(C#)/ByRef引数(VB)を使ったりしていた。

 .NET Framework 4.7で導入されたValueTuple構造体(System名前空間)と、Visual Studio 2017(C# 7/VB 15)で導入されたタプル構文を使えば、簡単に複数の結果を一時的に1つにまとめられるのだ。それらの新しい機能のうち、本稿ではメソッドから複数の結果を返して使う方法について解説する。

POINT タプル構文の使い方

タプル構文の使い方まとめ タプル構文の使い方まとめ


 特定のトピックをすぐに知りたいという方は以下のリンクを活用してほしい。

 なお、本稿に掲載したサンプルコードをそのまま試すにはVisual Studio 2017以降が必要である。サンプルコードはコンソールアプリの一部であり、コードの冒頭に以下の宣言が必要となる。

using System;
using static System.Console;

Imports System.Console

本稿のサンプルコードに必要な宣言(上:C#、下:VB)

TupleクラスとValueTuple構造体

 Tupleクラス(System名前空間)は.NET Fremework 4.0からあり、ValueTuple構造体は.NET Fremework 4.7で導入された*1。これら2つはよく似ており、例えば2つの値をまとめて返すメソッドを次のコードのように記述できる。

// Tupleクラス(.NET Fremework 4.0以降)
static Tuple<int, int> OldExchange(int x, int y) => Tuple.Create(y, x);

// ValueTuple構造体(.NET Fremework 4.7以降)
static ValueTuple<int, int> Exchange(int x, int y) => ValueTuple.Create(y, x);

' Tupleクラス(.NET Fremework 4.0以降)
Function OldExchange(x As Integer, y As Integer) As Tuple(Of Integer, Integer)
  Return Tuple.Create(y, x)
End Function

' ValueTuple構造体(.NET Fremework 4.7以降)
Function Exchange(x As Integer, y As Integer) As ValueTuple(Of Integer, Integer)
  Return ValueTuple.Create(y, x)
End Function

TupleとValueTupleの例(上:C#、下:VB)
このようなコーディングでは、TupleクラスとValueTuple構造体は同じように見える。ValueTuple構造体は、以降で解説するタプル構文で利用することで、その威力を発揮する。
なお、どちらもインスタンスを作ってから初期化してもよいが、このようにCreate静的メソッドを使った方が簡潔に書ける。

 よく似ているTupleクラスとValueTuple構造体だが、以下のような違いがある。

  • ValueTupleは軽量

 ValueTupleは構造体なので、クラスであるTupleよりも生成にかかるコストが小さく軽量である。

  • ValueTupleはタプル構文がサポートされる

 Visual Studio 2017(C# 7/VB 15)では、ValueTuple構造体を使ったタプル構文が導入された。タプル構文を使うと、複数のオブジェクトを1つにまとめたり分解したりするコードをシンプルに書ける。また、タプルに格納する個々の要素に名前(以降、タプル名)を付けることもできる(ValueTuple構造体を直接使った場合はタプル名を付けられない)。

 以降では、タプル構文による書き方を解説していく。

*1 .NET Fremework 4.7以前でも、NuGetからSystem.ValueTupleパッケージをプロジェクトに導入すれば、ValueTuple構造体を利用できる。


複数の値をまとめてメソッドから返すには?

 前述したValueTuple構造体を返すメソッドは、タプル構文を使うと簡潔に書ける(次のコード)。「ValueTuple<int, int>」(C#)という型の宣言は、タプル構文では「(int, int)」(C#)となる。タプルを生成する「ValueTuple.Create(y, x)」式は、タプル構文では「(y, x)」と書くだけでよいのだ。

// ValueTuple構造体を直接使う書き方(前述)
static ValueTuple<int, int> Exchange(int x, int y) => ValueTuple.Create(y, x);

// タプル構文を使って上と同じ意味のコードを書く
static (int, int) Exchange1(int x, int y) => (y, x);

' ValueTuple構造体を直接使う書き方(前述)
Function Exchange(x As Integer, y As Integer) As ValueTuple(Of Integer, Integer)
  Return ValueTuple.Create(y, x)
End Function

' タプル構文を使って上と同じ意味のコードを書く
Function Exchange1(x As Integer, y As Integer) As (Integer, Integer)
  Return (y, x)
End Function

タプル構文でタプル名を付けずに2つの値を返す例(上:C#、下:VB)

 上のように記述した場合、返値を受け取った側では、タプル内の要素をItem1/Item2という既定の名前で参照することになる。

 次のコードのようにしてタプル名を付けると、返値を受け取った側ではタプル内の要素をタプル名で参照できる。なお、タプル名はTupleElementNames属性(System.Runtime.CompilerServices名前空間)によって実現されているのだが、この属性を直接コーディングすることはできない(タプル構文が強制される)。

static (int x1, int y1) Exchange2(int x, int y) => (y, x);

Function Exchange2(x As Integer, y As Integer) As (x1 As Integer, y1 As Integer)
  Return (y, x)
End Function

タプル構文でタプル名を付けて2つの値を返す例(上:C#、下:VB)

 上のように記述した場合、返値を受け取った側では、タプル内の要素をx1/y1というタプル名で参照することになる(後述)。適切なタプル名を付けておくことで、コードの見通しがよくなる。

複数の値をメソッドから受け取るには?

 ValueTuple構造体を返してくるメソッドの基本的な呼び出し方は、次のコードのようになる。

// タプル名なし(既定の名前Item1/Item2……でアクセスする)
var result1a = Exchange1(2, 3);
WriteLine($"result1a.Item1={result1a.Item1}, result1a.Item2={result1a.Item2}");
// 出力:result1a.Item1=3, result1a.Item2=2

// タプル名付き
var result2a = Exchange2(2, 3);
WriteLine($"result2a.x1={result2a.x1}, result2a.y1={result2a.y1}");
// 出力:result2a.x1=3, result2a.y1=2

' タプル名なし(既定の名前Item1/Item2……でアクセスする)
Dim result1a = Exchange1(2, 3)
WriteLine($"result1a.Item1={result1a.Item1}, result1a.Item2={result1a.Item2}")
' 出力:result1a.Item1=3, result1a.Item2=2

' タプル名付き
Dim result2a = Exchange2(2, 3)
WriteLine($"result2a.x1={result2a.x1}, result2a.y1={result2a.y1}")
' 出力:result2a.x1=3, result2a.y1=2

2つの値を返すメソッドを呼び出す例(上:C#、下:VB)
呼び出しているメソッドExchange1/Exchange2は、前述のもの。
なお、既定の名前(Item1/Item2……)しか持っていないタプルなのか、名前付きタプルなのか、コーディング時に迷うことはない。Visual StudioのIntelliSenseが提示してくれる。

 あるいは、受け取るときに別のタプル名を付けることもできる(次のコード)。

// タプル名なしで返してくるメソッド
(int X, int Y) result1b = Exchange1(2, 3);
WriteLine($"result1b.X={result1b.X}, result1b.Y={result1b.Y}");
// 出力:result1b.X=3, result1b.Y=2

// タプル名付きで返してくるメソッド
(int M, int N) result2b = Exchange2(2, 3);
WriteLine($"result2b.M={result2b.M}, result2b.N={result2b.N}");
// 出力:result2b.M=3, result2b.N=2

' タプル名なしで返してくるメソッド
Dim result1b As (X As Integer, Y As Integer) = Exchange1(2, 3)
WriteLine($"result1b.X={result1b.X}, result1b.Y={result1b.Y}")
' 出力:result1b.X=3, result1b.Y=2

' タプル名付きで返してくるメソッド
Dim result2b As (M As Integer, N As Integer) = Exchange2(2, 3)
WriteLine($"result2b.M={result2b.M}, result2b.N={result2b.N}")
' 出力:result2b.M=3, result2b.N=2

2つの値を返すメソッドを呼び出すときにタプル構文を使って別のタプル名を付ける例(上:C#、下:VB)
呼び出しているメソッドExchange1/Exchange2は、前述のもの。
メソッドから返ってくるタプルにタプル名が付いていようといまいと、別のタプル名を付けたタプルとして受け取ることができる。

 さらにC# 7では、タプルを受け取るときにいきなり分解する(タプルの要素を個別の変数に代入する)ことも可能だ(次のコード)。受け取ったタプルをそのまま利用することがないのであれば、このように簡潔に書ける。

var (p, q) = Exchange1(2, 3);
WriteLine($"p={p}, q={q}");
// 出力:p=3, q=2

受け取ったタプルをいきなり分解するタプル構文の例(C#のみ)
呼び出しているメソッドExchange1は前述。上の例にはないが、「(p, _)」のようにして、タプルとして返された値で使わない要素を「_」で示すこともできる(「_」は書き込み専用の変数)。
この機能はVB 15にはない。将来のバージョンでサポートされるものと期待される。

まとめ

 C# 7/VB 15で導入されたタプル構文では、複数の変数やリテラルをカンマで区切って並べて丸かっこで囲むだけでタプルになる。タプルの各要素には(従来のTupleクラスとは異なり)タプル名を付けられる。タプルを受け取った側では、従来のTupleクラスと同様にItem1/Item2……という名前でもアクセスできるし、タプル名が付けられていればそれでもアクセスできる。あるいは、受け取るときに異なるタプル名を付けてもよい。C# 7では、タプルを受け取ると同時に分解することもできる。

 なお、タプル構文は.NET Framework 4.7で導入されたValueTuple構造体を利用している。それ以前の.NET Frameworkでも、NuGetから導入すれば利用可能だ。

利用可能バージョン:Visual Studio 2017以降
カテゴリ:Visual Studio 2017 処理対象:言語構文
カテゴリ:C# 処理対象:言語構文
カテゴリ:Visual Basic 処理対象:言語構文
使用ライブラリ:ValueTuple構造体(System名前空間)
使用ライブラリ:TupleElementNameAttribute属性(System.Runtime.CompilerServices名前空間)
関連TIPS:複数のオブジェクトを一時的に1つにまとめるには?[4以降、C#、VB]
関連TIPS:構文:メソッドやプロパティをラムダ式で簡潔に実装するには?[C# 6.0/7.0]
関連TIPS:構文:クラス名を書かずに静的メソッドを呼び出すには?[C# 6.0]
関連TIPS:VB.NETでクラス名を省略してメソッドや定数を利用するには?


「.NET TIPS」のインデックス

.NET TIPS

Copyright© 1999-2018 Digital Advantage Corp. All Rights Reserved.

RSSについて

アイティメディアIDについて

メールマガジン登録

@ITのメールマガジンは、 もちろん、すべて無料です。ぜひメールマガジンをご購読ください。