1. ホーム
  2. c#

[解決済み] System.ValueTupleとSystem.Tupleの違いは何ですか?

2022-05-02 10:27:23

質問

C# 7 のライブラリをデコンパイルしてみたところ ValueTuple ジェネリックが使用されています。とは何ですか? ValueTuples で、なぜ Tuple の代わりに?

解決方法は?

<ブロッククオート

何が ValueTuples で、なぜ Tuple の代わりに?

A ValueTuple は、タプルを反映した構造体であり、元の System.Tuple クラスがあります。

との主な違いは TupleValueTuple があります。

  • System.ValueTuple は値型(構造体)であるのに対し System.Tuple は参照型 ( class ). これは、アロケーションとGCプレッシャーについて話すときに意味があります。
  • System.ValueTuple だけではありません。 struct であり、それは ミュータブル として使用する場合は、注意が必要です。あるクラスが System.ValueTuple をフィールドとして使用します。
  • System.ValueTuple は、プロパティではなくフィールドを介してその項目を公開します。

C# 7までは、タプルを使うのはあまり便利ではありませんでした。そのフィールド名は Item1 , Item2 他の多くの言語(PythonやScala)がそうであるように、言語がこれらのためのシンタックスシュガーを提供していなかったのです。

.NET言語設計チームがタプルを組み込み、言語レベルでタプルにシンタックスシュガーを追加することを決定したとき、重要な要素はパフォーマンスでした。そのため ValueTuple 値型であるため、(実装の詳細として)スタックに割り当てられるので、使用時のGCプレッシャーを避けることができます。

さらに struct はランタイムによって自動的に (浅い) 等号の意味を持つようになります。 class はない。設計チームは、タプルに対してさらに最適化された等式があることを確認し、そのためにカスタム等式を実装しました。

以下は のデザインノートです。 Tuples :

構造体かクラスか

前述の通り、私はタプル型の structs よりも classes そのため、割り当てのペナルティはありません。これらの は、できるだけ軽量にする必要があります。

論外です。 structs の方がコスト高になる可能性があります。 はより大きな値をコピーします。そのため、もしこれらの値が割り当てられる回数が が作成されると structs は悪い選択だと思います。

しかし、タプルはその動機からして、刹那的なものです。そのため 全体よりも部分が重要な場合です。だから、一般的な を構築し、返し、すぐに分解するパターンです。 である。このような状況では、明らかに構造体が望ましいと言えます。

構造体には他にも多くの利点がありますが、それは今後明らかになっていくでしょう。 で明らかになります。

を使えば、簡単にわかると思います。 System.Tuple はすぐに曖昧になります。例えば、あるメソッドで List<Int> :

public Tuple<int, int> DoStuff(IEnumerable<int> values)
{
    var sum = 0;
    var count = 0;

    foreach (var value in values) { sum += value; count++; }

    return new Tuple(sum, count);
}

受信側では、次のようになります。

Tuple<int, int> result = DoStuff(Enumerable.Range(0, 10));

// What is Item1 and what is Item2?
// Which one is the sum and which is the count?
Console.WriteLine(result.Item1);
Console.WriteLine(result.Item2);

値のタプルを名前付き引数に分解する方法は、この機能の真価を発揮します。

public (int sum, int count) DoStuff(IEnumerable<int> values) 
{
    var res = (sum: 0, count: 0);
    foreach (var value in values) { res.sum += value; res.count++; }
    return res;
}

そして受信側。

var result = DoStuff(Enumerable.Range(0, 10));
Console.WriteLine($"Sum: {result.Sum}, Count: {result.Count}");

または

var (sum, count) = DoStuff(Enumerable.Range(0, 10));
Console.WriteLine($"Sum: {sum}, Count: {count}");

コンパイラーグッズです。

先ほどの例の裏側を見ると、コンパイラがどのように ValueTuple 分解を依頼したとき

[return: TupleElementNames(new string[] {
    "sum",
    "count"
})]
public ValueTuple<int, int> DoStuff(IEnumerable<int> values)
{
    ValueTuple<int, int> result;
    result..ctor(0, 0);
    foreach (int current in values)
    {
        result.Item1 += current;
        result.Item2++;
    }
    return result;
}

public void Foo()
{
    ValueTuple<int, int> expr_0E = this.DoStuff(Enumerable.Range(0, 10));
    int item = expr_0E.Item1;
    int arg_1A_0 = expr_0E.Item2;
}

内部的には、コンパイルされたコードは Item1Item2 しかし、分解されたタプルを扱うので、これらはすべて抽象化されています。名前付き引数を持つタプルには TupleElementNamesAttribute . 分解する代わりに、1つの新鮮な変数を使用すると、次のようになります。

public void Foo()
{
    ValueTuple<int, int> valueTuple = this.DoStuff(Enumerable.Range(0, 10));
    Console.WriteLine(string.Format("Sum: {0}, Count: {1})", valueTuple.Item1, valueTuple.Item2));
}

アプリケーションをデバッグするときに、コンパイラが (属性を使って) 何か魔法をかけなければならないことに注意してください。 Item1 , Item2 .