第2回 簡潔なコーディングのために:特集:C# 7の新機能詳説(2/2 ページ)
ラムダ式で記述できるメンバの増加、throw式、out変数、タプルなど、C# 7には以前よりもコードを簡潔に記述できるような機能が導入されている。
out変数
「out変数」とは、out引数を要求するメソッドを呼び出すとき、引数リストの中で変数宣言も行えるものだ。つまり、事前に変数を宣言しておかなくてよいのである。
例えば、文字列をDateTime型(System名前空間)に変換するTryParseメソッドは、その第2引数がout引数になっている。文字列をDateTime型に変換する(ただし、変換できないときは今日の日付を返す)メソッドは、従来なら次のコードのように書いていた。
public static DateTime ReadDate(string date)
{
// 文字列をDateTime型に変換する。変換できないときは、今日の日付を返す。
DateTime dt;
return DateTime.TryParse(date, out dt) ? dt : DateTime.Today;
}
ただし、変換できないときは今日の日付を返す。
TryParseメソッドの第2引数には、あらかじめ宣言しておいた変数にoutキーワードを付けて渡さねばならない。
C# 7では、TryParseメソッドを呼び出す式の中でout変数を宣言できる(次のコード)。そのため、事前の変数宣言が不要になるだけでなく、このような簡単なメソッドの場合は、ラムダ式での記述も可能になるのだ。
public static DateTime ReadDate(string date)
=> DateTime.TryParse(date, out var dt) ? dt : DateTime.Today;
TryParseメソッドにout引数を与えるところで、同時に変数宣言もできる。
ここではvarキーワードを使っているが、厳密な型を書いてもよい。
なお、out変数のスコープは、その直前で変数宣言をしたときと同じになる。
タブルの生成と分解
C# 7の新しいタプルの機能を使うと、メソッドから複数の値を返せる。ただし、.NET Frameworkの追加機能を使っているため、.NET Framework 4.7以前の場合はコードを書いただけでは動かないので注意してほしい(後述)。
複数の値を返すメソッドとその使用例
新しいタプルの機能を使うと、例えば次のコードのように合計と平均値を返すメソッドが作れる。
public static (int sum, double ave) Calc(IList<int> numbers)
=> (numbers.Sum(), numbers.Average());
タプルを使って複数の値を返すメソッドを作るには、このように複数の返値それぞれに名前を付けてカンマで区切って並べ、かっこでくくる。返値の名前は付けなくてもよいが、使うときに分かりにくくなる。
かっこでくくった返値の組がタプルを表している。
メソッド本体では、2つの計算結果(numbers.Sum()とnumbers.Average())をカンマで区切って並べ、かっこでくくってある。これでタプルが生成される。
上のメソッドは、次のコードのようにして使える。
(var sm, var av) = Calc(new int[]{1,3,5,7,9 });
Console.WriteLine($"合計={sm}, 平均={av:0.000}");
// 出力:合計=25, 平均=5.000
// varを使うときは、次のようにも書ける
var (s, v) = Calc(new int[] { 1, 3, 5, 7, 9 });
メソッドからの返値を受け取るには、ローカル変数宣言をカンマで区切って並べてかっこでくくったものに代入する。出力結果を見ると、ローカル変数smには合計値が、ローカル変数avには平均値が、確かにそれぞれ代入されたことが分かるだろう。
ローカル変数sm/avはvarで宣言しているが、ちゃんと型推論が働いて、smはint型に、avはdouble型になる。また、varで宣言するときは、2つ目の例に示した書き方もできる。
このメソッドはタプルを返してくるのであるが、上の例ではいずれも、タプルを受け取るときにその要素を個別の変数に代入している。これを「タプルの分解」と呼ぶ。後述するが、タプルを分解せずに1つの変数に受け取って、後から個々の要素にアクセスすることもできる。
複数の値を返すメソッドなんて何が返ってくるか分からないという不安を抱くかもしれないが、心配はいらない。Visual StudioのIntelliSenseが、返値の名前もちゃんと教えてくれる(次の画像)。
メソッド名をキー入力したところでIntelliSenseが複数の返値の名前も表示してくれる
先のコード(Calcメソッドの定義)で返値それぞれに名前を付けたので、それがIntelliSenseで表示されている。適切な名前が付けてあれば、使うときに迷うこともないだろう。
新しいタプルを使うには?
C# 7の新しいタプルの機能は、ValueTuple構造体(System名前空間)に基づいている。ValueTuple構造体を作ったり、そこから値を取り出したりするコードの糖衣構文が、上で紹介したコードになるというわけだ。
このValueTuple構造体は、.NET Framework 4.7には組み込まれている。.NET Framework 4.7(またはそれ以降)を使っているときは、C# 7で上記のようなコードを書くだけでよい。
バージョン4.7よりも前の.NET FrameworkにはValueTuple構造体が組み込まれていない。そこでNuGetから「System.ValueTuple」パッケージをプロジェクトごとに導入する必要がある(.NET Core+Visual Studio Codeを使っている場合も同様)。忘れていると、タプルを使ったコードを書き始めた途端にエラーになってしまう。気を付けてほしい。
タプルのまま受け取る
先の例では、メソッドから返値を受け取るところでタプルをいきなり分解していた。複数の値を受け取るだけなら、その方法で良いだろう。
メソッドから返された複数の値を、タプルのまま扱いたいときもある。タプルのまま他のメソッドに渡したいときなどだ。そのようなときは、分解せずにタプルとして受け取ることももちろん可能だ。また、タプルを構成する要素の名前が違うタプルに代入することもできる(次のコード)。
// 型と名前を指定してタプルの変数t1を宣言
(int sum, double ave) t1 = Calc(new int[] { 1, 3, 5, 7, 9 });
Console.WriteLine($"合計={t1.sum}, 平均={t1.ave:0.000}");
// varでタプルの変数t2を宣言(上と同じになる)
var t2 = Calc(new int[] { 1, 3, 5, 7, 9 });
Console.WriteLine($"合計={t2.sum}, 平均={t2.ave:0.000}");
// 名前が違うタプルの変数t3に代入する(それぞれの型が合っていればよい)
(int 合計, double 平均) t3 = t2;
Console.WriteLine($"合計={t3.合計}, 平均={t3.平均:0.000}");
タプルのまま受け取るには、タプルのローカル変数が必要だ。それを宣言するには、1番目のようにメソッドの返値と同じ型/同じ名前を指定してもよいのだが、面倒なだけなので、通常は2番目のようにvarで済ませてしまう。それぞれの値にアクセスするには、タプルのローカル変数の後ろにピリオドを付け、その後ろに値の名前を指定する(名前がないときはItem1/Item2/……というプロパティでアクセス可能だが、これは分かりにくい)。
また、タプルはその型の並びさえ合っていれば(厳密に言えば、それぞれの型が暗黙に型変換可能なら)、名前が違っていても3番目のように代入できる。代入後に値にアクセスするときには、新しい名前だけが使える。
まとめ
今回は、C# 7の新機能の中から、簡潔なコーディングに役立つ4つの新機能の使い方を紹介した。ラムダ式でプロパティのsetterなども記述できるようになり、また、条件演算子などいくつかの式の中で例外を投げられるようになった。out引数を渡すところで同時に変数宣言が可能になった。新しいタプルの糖衣構文を使うと、複数の値を返すメソッドを簡潔に書ける。
Copyright© Digital Advantage Corp. All Rights Reserved.