やってみる

アウトプットすべく己を導くためのブログ。その試行錯誤すらたれ流す。

C#の概念 タプル

 新しくできたタプルについて。

成果物

学習方針

 上記を流し読みした。そのうち、これまで学習していない気になったところだけをピックアップすることにする。今回はタプル。

コード

代入と参照

 名前なし。ItemNという名前が自動割当される。Nは1から始まる整数。

var t = (1, 'a');
Console.WriteLine($"{t.Item1}, {t.Item2}");

 名前あり。

var t = (id:1, cls:'a');
Console.WriteLine($"{t.id}, {t.cls}");

 プロジェクション初期化子。

private void ProjectionInitializer() {
    var id = 1;
    var cls = 'a';
    var t = (id, cls);
    Console.WriteLine($"{t.id}, {t.cls}");
}

2. 比較

(1, 'a') == (1, 'a')

 変数に代入してもいい。

var a = (1, 'a');
var b = (1, 'b');
a == b

 名前つきでもいい。

var a = (id:1, cls:'a');
var b = (id:1, cls:'a');
a == b

 名前が違ってもいい。

var a = (i1:1, i2:'a');
var b = (k1:1, k2:'a');
a == b

 ネストしていてもOK。

var a = (id:1, cls:(1, 'a'));
var b = (id:1, cls:(1, 'a'));
Console.WriteLine(a == b);

3. 割当

 タプルへの代入もできる。

var a = (1, 'a');
var b = (1, 'b');
a = b;
Console.WriteLine(a);

 名前は変わらない。名前つきを名前なしに代入しても、新しい名前は無視される。

var a = (1, 'a');
var b = (id: 1, cls: 'b');
a = b;
Console.WriteLine(a);
Console.WriteLine(a.Item1);
Console.WriteLine(a.id); // error CS1061

 数や型が異なるとエラー。同じ数であり、かつ同じ位置にある同じ型のときだけOK。

var a = (1, 'a');
var b = (1, 2);
a = b; // error CS0266

4. 戻り値

(int,    char)     Ret()   { return (1, 'a'); }
(int id, char cls) Named() { return (1, 'a'); }

var t = Ret();
Console.WriteLine($"{t.Item1}, {t.Item2}");

var u = Named();
Console.WriteLine($"{u.id}, {u.cls}");

5. 分解

var t = (1, 'a');
(int i, char c) = t;
Console.WriteLine($"{i}, {c}");

 varで型を省略。

var t = (1, 'a');
var (i, c) = t;
Console.WriteLine($"{i}, {c}");

 一部だけvar

var t = (1, 'a');
(int i, var c) = t;
Console.WriteLine($"{i}, {c}");

 定義済み識別子を使う。

class Point {
    public int X { get; }
    public int Y { get; }
    public Point(int x, int y) => (X,Y) = (x,y);
}
private void Decompose4() {
    var p = new Point(1,2);
    Console.WriteLine($"{p.X}, {p.Y}");
}

 Deconstructメソッド実装して独自に分解する。

public class Name {
    public string First { get; }
    public string Last { get; }
    public Name(string first, string last) => (First, Last) = (first, last);
    public void Deconstruct(out string first, out string last) => (first, last) = (First, Last);
}
var n = new Name("A", "B");
var (f, l) = n;
Console.WriteLine($"{f}, {l}");

 しかしDeconstructメソッド実装したとき、比較できなくなってしまう。

var n = new Name("A", "B");
var m = new Name("A", "B");
Console.WriteLine($"{n == m}"); // False

 Deconstructメソッドは拡張メソッドとして実装することもできる。

public class Name3 : Name {
    public string Middle { get; }
    public Name3(string first, string middle, string last) : base(first, last) => Middle = middle;
}
public static class Extensions
{
    public static void Deconstruct(this Name3 n, out string first, out string middle, out string last)
    {
        first = n.First;
        middle = n.Middle;
        last = n.Last;
    }
}
var n = new Name3("A", "B", "C");
var (f, m, l) = n;
Console.WriteLine($"{f}, {m}, {l}");

6. out

 outで出力させるときは以下のように書く。

dict.TryGetValue(2, out (int num, string place) pair);

 名前なしでもいい。

dict.TryGetValue(2, out (int, string) pair);

 以下のように使う。

private Dictionary<int, (int, string)> GetDict() => new Dictionary<int, (int, string)>() {
    [1] = (234, "First!"),
    [2] = (345, "Second"),
    [3] = (456, "Last"),
};
private void Out1() {
    var dict = GetDict();
    dict.TryGetValue(2, out (int num, string place) pair);
    Console.WriteLine($"{pair.num}: {pair.place}");
    Console.WriteLine($"{pair.Item1}: {pair.Item2}");
}
private void Out2() {
    var dict = GetDict();
    dict.TryGetValue(2, out (int, string) pair);
    Console.WriteLine($"{pair.Item1}: {pair.Item2}");
}

7. 破棄

 分解するとき不要な位置の要素名を_にすることで破棄できる。

(int, char) T() => (1, 'a');

var (i, _) = T(); // 2番目の要素は破棄する
Console.WriteLine($"{i}");

所感

 匿名型との微妙な違いがある。使い分けが面倒。タプルは引数や戻り値に使えるのがポイントか。

対象環境

$ uname -a
Linux raspberrypi 4.19.42-v7+ #1218 SMP Tue May 14 00:48:17 BST 2019 armv7l GNU/Linux