C#もついに7.0まで来ました。仕様がだいぶ固まってきたのでライブコーディングを見ている風に解説したいと思います。
主な機能は
・リテラルの改良
・タプル
・分解構文(Destruction)
・Deconstructor
・型スイッチ(定数、型、変数とのマッチング)
・ローカル関数
・Out Variables
・戻り値にRef
この記事ではDeconstructorまで解説します。
■using static(C#6.0の機能)によるクラス名の省略
空のProgram.csから始めていきます。started...と表示して止まるだけのコンソールアプリケーションです。
using System;
public class Program
{
public static void Main(string[] args)
{
Console.WriteLine("started...");
Console.ReadLine();
}
}
using staticを使用するとstaticメソッドの呼び出し時にクラス名を省略できるようになります。
using System;
using static System.Console;//←追加したよ
public class Program
{
public static void Main(string[] args)
{
WriteLine("started...");
ReadLine();
}
}
ちょっと楽になりました。メソッドの名前解決に多少注意を払う必要があります。例えば上記のProgramクラスにReadLineメソッドが定義されてるとそっちが優先されたりするので少し注意が必要です。
この機能はRazorとかで利用するとかなり楽になると思われます。今は
<html>
@{ Html.RenderPartial("_HeaderTagPanel");}
<body>
@RenderBody()
@{Html.RenderPartial("_FooterPanel");}
</body>
</html>
みたいに書いてると思いますがこれらのHtmlが全部省略できるようになるとだいぶコードがすっきりしそうです。(できるようになるのかな?ちょっと不明ですが)
■リテラルの改良
次はリテラルの改良についてです。int[]を宣言します。リテラルにアンダーバーを混ぜて見やすく書けるようになりました。
using System;
using static System.Console;
public class Program
{
public static void Main(string[] args)
{
//リテラルの改良↓
int[] numbers = { 0b0000_0001, 0b0000_0010, 0b0000_0011, 0b0000_0100 };//int[] numbers = { 1, 2, 3, 4 };と同じ
WriteLine("started...");
ReadLine();
}
}
どうやらQiitaのエディターが対応していないようです(笑)色がうまくつきません。アンダーバーの位置はどこでも大丈夫です。カラーコードや3桁区切りで
var myColor = 0xAA_CC_88;
var price = 12_000;//12,000円
var x1 = 0b1_0__0_1;
var x2 = 0b00__000_____11001;
上記の感じに書いても大丈夫です。だいぶ見やすくなります。
■タプル
さてコードに戻ります。次はこれらの数字の合計と個数を計算してみます。今までは2つの値を返すにはクラスを作る必要がありました。
using System;
using static System.Console;
public class Program
{
public static void Main(string[] args)
{
int[] numbers = { 0b0000_0001, 0b0000_0010, 0b0000_0011, 0b0000_0100 };//int[] numbers = { 1, 2, 3, 4 };と同じ
同じ
var summary = Calculate(numbers);
WriteLine("Total:" + summary.Total);
WriteLine("Count:" + summary.Count);
ReadLine();
}
private static NumberCalculateResult Calculate(int[] numbers)
{
var result = new NumberCalculateResult();
foreach (var item in numbers)
{
result.Total = result.Total + item;
result.Count = result.Count + 1;
}
return result;
}
}
class NumberCalculateResult
{
public int Total { get; set; }
public int Count { get; set; }
}
こういったシーンでの問題は2つあって
1.クラスの定義の作業コスト
2.クラスの名前を考えるコスト
という問題があります。作業コストはもちろん面倒ですし、いろいろないくつかの値をグルーピングして持つ似て非なるクラスの適切な名前を毎回考えるのはなかなかに面倒です。
タプルの登場で上記の問題が綺麗に解決されます。
using System;
using static System.Console;
public class Program
{
public static void Main(string[] args)
{
int[] numbers = { 0b_0000_0001, 0b_0000_0010, 0b_0000_0011, 0b_0000_0100 };//int[] numbers = { 1, 2, 3, 4 };と同じ
var summary = Calculate(numbers);
WriteLine("Total:" + summary.Item1);
WriteLine("Count:" + summary.Item2);
ReadLine();
}
private static (int, int) Calculate(int[] numbers)
{
//↓タプルの宣言。new ValueTuple(0, 0)と一緒
var result = (Total: 0, Count: 0);
foreach (var item in numbers)
{
result.Total = result.Total + item;
result.Count = result.Count + 1;
}
return (result.Total, result.Count);
}
}
クラスの定義が
var result = (Total: 0, Count: 0);
で済むようになり大幅に簡単になります。この記述でコンパイラが内部的にValueTupleクラスに変換してくれてItem1,Item2という形でアクセス可能になります。
しかしItem1はなんか嫌ですね。やっぱりプロパティに名前を付けたいという話になるのでその場合は以下のように書きます。
using System;
using static System.Console;
public class Program
{
public static void Main(string[] args)
{
int[] numbers = { 0b0000_0001, 0b0000_0010, 0b0000_0011, 0b0000_0100 };//int[] numbers = { 1, 2, 3, 4 };と同じ
var summary = Calculate(numbers);
WriteLine("Total:" + summary.Total);
WriteLine("Count:" + summary.Count);
ReadLine();
}
//↓戻り値に名前を付けたよ
private static (int Total, int Count) Calculate(int[] numbers)
{
var result = (Total: 0, Count: 0);
foreach (var item in numbers)
{
result.Total = result.Total + item;
result.Count = result.Count + 1;
}
return result;
}
}
これでプロパティに自分の好きな名前をつけることができました。タプルは実際にはValueTuple構造体にコンパイラが変換するため、スタックメモリを使用します。ヒープメモリは使用しないためGCの負荷を減らすことができます。これによりゲームなどでマイフレームごとに画面上のオブジェクトの次の場所の計算処理をするとかの場合には劇的なパフォーマンスの向上が見込めるでしょう。
■分解構文(Deconstruction)
タプルの解説で「適切なクラス名が思いつかない」というのがありましたが、適切なクラス名が思いつかないということはその値を代入する適切な変数名を考えるのも面倒だということになります。この例だとsummaryという変数名です。今回は計算処理が1か所しかないですが、これが例えば5か所あると変数名を考えるのが大変です。クラスと同様に変数名も省略可能にできたらいいのにと思うのは必然の流れとも言えます。これを実現するのが分解構文(Deconstruction)という機能です。
using System;
using static System.Console;
public class Program
{
public static void Main(string[] args)
{
int[] numbers = { 0b0000_0001, 0b0000_0010, 0b0000_0011, 0b0000_0100 };//int[] numbers = { 1, 2, 3, 4 };と同じ
(int totalOfNumber, int numberCount) = Calculate(numbers);
WriteLine("Total:" + totalOfNumber);
WriteLine("Count:" + numberCount);
ReadLine();
}
private static (int Total, int Count) Calculate(int[] numbers)
{
var result = (Total: 0, Count: 0);
foreach (var item in numbers)
{
result.Total = result.Total + item;
result.Count = result.Count + 1;
}
return result;
}
}
この書き方でtotalOfNumberに合計値、numberCountに個数が代入されます。summaryという微妙な名前の変数名が消えているのがわかると思います。
代入は引数の順番でマッチングされます。プロパティ名は全く考慮にいれません。この場合は
■戻り値 -> (int Total, int Count)
■宣言 -> (int totalOfNumber, int numberCount)
となっているのでTotalがtotalOfNumberに、CountがnumberCountにそれぞれ代入されます。
順番しか考慮しないということは、もし例えば以下のように
■戻り値 -> (int Total, int Count)
■宣言 -> (int Count, int numberCount)
となった場合、TotalがCountに、CountがnumberCountにそれぞれ代入されます。
■タプルだけじゃなくて既存の型を分解したい→Deconstructor
さてこんな便利な分解構文ができたならば自分のクラスも分解してほしいという話になります。
例えばPersonクラスを作りそのプロパティを分解可能にするには
public class Person
{
public string Name { get; set; }
public int Age { get; set; }
public Person(string name, int age)
{
this.Name = name;
this.Age = age;
}
public static void Deconstruct(this Person person, string out name, int out age)
{
name = person.Name;
age = person.Age;
}
}
class Program
{
public static void Main(string[] args)
{
var p = new Person("Joe", 22);
var (myName, myAge) = p;
WriteLine("My name is " + myName + ".Age is " + myAge + ".");
ReadLine();
}
}
というようにstaticなDeconstructメソッドを定義します。これによりPersonクラスは分解構文で利用可能になります。
何故Deconstructorがタプルによる戻り値ではなくてoutパラメータなのかというと、オーバーロードができるようにとのことです。これによりいろんなパターンでの分解処理を実装することができます。
以上その1でした。検証はしてないので間違いがあったらコメント頂けると助かります。
そのうち気が向いたらその2を書きます。