web-dev-qa-db-ja.com

System.ValueTupleとSystem.Tupleの違いは何ですか?

私はいくつかのC#7ライブラリを逆コンパイルし、ValueTupleジェネリックが使われているのを見ました。 ValueTuplesとは何ですか。なぜTupleではないのですか。

106
Steve Fan

ValueTuplesとは何ですか。なぜTupleではないのですか。

A ValueTuple は、元のSystem.Tupleクラスと同じように、Tupleを反映する構造体です。

TupleValueTupleの主な違いは次のとおりです。

  • System.ValueTupleは値型(struct)ですが、System.Tupleは参照型(class)です。これは、割り当てとGCのプレッシャーについて話すときに意味があります。
  • System.ValueTuplestructだけではなく、可変なので、それらをそのように使うときは注意が必要です。クラスがフィールドとしてSystem.ValueTupleを保持するとどうなるか考えてください。
  • System.ValueTupleは、プロパティではなくフィールドを介してそのアイテムを公開します。

C#7までは、タプルを使うのはあまり便利ではありませんでした。それらのフィールド名はItem1Item2などであり、その言語は他のほとんどの言語がそうであるように(Python、Scala)それらのために構文シュガーを提供していませんでした。

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

さらに、structはランタイムによって自動的な(浅い)等価セマンティクスを取得しますが、classは取得しません。設計チームは、タプルにさらに最適化された同等性があることを確認しましたが、そのためにカスタム同等性を実装しました。

これは Tuplesのデザインノート からの段落です。

構造体またはクラス

先に述べたように、私はTuple型を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を利用しますが、私たちは分解されたTupleを使っているので、これらすべては抽象化されています。名前付き引数を持つTupleは、 TupleElementNamesAttribute のアノテーションが付けられます。分解せずに単一の新しい変数を使用すると、次のようになります。

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));
}

Item1Item2を見るのは奇妙なことなので、アプリケーションをデバッグするときにコンパイラはまだ(属性を介して)何らかの魔法をかける必要があることに注意してください。

165
Yuval Itzchakov

TupleValueTupleの違いは、Tupleは参照型で、ValueTupleは値型です。 C#7の言語を変更するとタプルがより頻繁に使用されるようになるため、後者が望ましいですが、特に不要な場合は、タプルごとにヒープ上に新しいオブジェクトを割り当てることがパフォーマンス上の問題になります。

しかし、C#7では、Tupleで使用するために構文糖が追加されているため、どちらの型も明示的に使用するためにhaveを使用することは絶対にありません。たとえば、C#6で、値を返すためにTupleを使用したい場合は、次のようにする必要があります。

public Tuple<string, int> GetValues()
{
    // ...
    return new Tuple(stringVal, intVal);
}

var value = GetValues();
string s = value.Item1; 

ただし、C#7では、これを使用できます。

public (string, int) GetValues()
{
    // ...
    return (stringVal, intVal);
}

var value = GetValues();
string s = value.Item1; 

さらに一歩進んで値に名前を付けることもできます。

public (string S, int I) GetValues()
{
    // ...
    return (stringVal, intVal);
}

var value = GetValues();
string s = value.S; 

...またはTupleを完全に分解します。

public (string S, int I) GetValues()
{
    // ...
    return (stringVal, intVal);
}

var (S, I) = GetValues();
string s = S;

タプルは、面倒で冗長なためC#7以前ではあまり使用されず、1つの作業のインスタンスに対してデータクラス/構造体を構築することが価値がある場合よりも面倒な場合にのみ実際に使用されます。しかし、C#7では、タプルは現在言語レベルのサポートを持っているので、それらを使うことははるかにクリーンでより便利です。

18
Abion47

TupleValueTupleの両方のソースを調べました。違いは、Tupleclassであり、ValueTuplestructを実装するIEquatableです。

つまり、Tuple == Tupleは、それらが同じインスタンスでない場合はfalseを返しますが、ValueTuple == ValueTupleは同じ型である場合はtrueを返し、Equalsはそれぞれの値についてtrueを返します。

8
Peter Morris

上記のコメントに加えて、ValueTupleの残念な問題の1つは、値型として、名前付き引数がILにコンパイルされると消去されるため、実行時にシリアル化できないことです。

すなわち、あなたの甘い名前付き引数は、例えば "#1"、 "#2"などでシリアライズされたときにはまだ終わるでしょう。 Json.NET.

5
ZenSquirrel

言い換えれば、私は source code からXML文書を参照するつもりです。

ValueTuple型(アリティ0から8)は、C#のタプルとF#の構造体タプルの基礎となるランタイム実装を構成します。

言語構文で作成される以外に、それらはValueTuple.Createファクトリメソッドで最も簡単に作成されます。 System.ValueTuple型は、以下の点でSystem.Tuple型と異なります。

  • それらはクラスではなく構造体です。
  • それらはreadonlyではなく可変です、そして
  • そのメンバー(Item1、Item2など)はプロパティではなくフィールドです。

このタイプとC#7.0コンパイラの紹介で、あなたは簡単に書くことができます

(int, string) idAndName = (1, "John");

メソッドから2つの値を返します。

private (int, string) GetIdAndName()
{
   //.....
   return (id, name);
}

System.Tupleとは反対に、メンバーは更新可能です(Mutable)。これは意味のある名前を付けることができるパブリックな読み書きフィールドです。

(int id, string name) idAndName = (1, "John");
idAndName.name = "New Name";
5
Zein Makki

これらの2つのファクトイドに関する明確な説明を追加するためのレイトジョイン:

  • クラスではなく構造体
  • それらは読み取り専用ではなく可変です

一括して値タプルを変更するのは簡単だと思うでしょう:

 foreach (var x in listOfValueTuples) { x.Foo = 103; } // wont even compile because x is a value (struct) not a variable

 var d = listOfValueTuples[0].Foo;

誰かがこれを次のように回避しようとするかもしれません:

 // initially *.Foo = 10 for all items
 listOfValueTuples.Select(x => x.Foo = 103);

 var d = listOfValueTuples[0].Foo; // 'd' should be 103 right? wrong! it is '10'

この風変わりな動作の理由は、値タプルが値ベース(構造体)であるため、.Select(...)呼び出しが元の構造体ではなくクローン構造体で機能するためです。これを解決するには、次の手段を使用する必要があります。

 // initially *.Foo = 10 for all items
 listOfValueTuples = listOfValueTuples
     .Select(x => {
         x.Foo = 103;
         return x;
     })
     .ToList();

 var d = listOfValueTuples[0].Foo; // 'd' is now 103 indeed

もちろん、簡単なアプローチを試すこともできます:

   for (var i = 0; i < listOfValueTuples.Length; i++) {
        listOfValueTuples[i].Foo = 103; //this works just fine

        // another alternative approach:
        //
        // var x = listOfValueTuples[i];
        // x.Foo = 103;
        // listOfValueTuples[i] = x; //<-- vital for this alternative approach to work   if you omit this changes wont be saved to the original list
   }

   var d = listOfValueTuples[0].Foo; // 'd' is now 103 indeed

これがリストにホストされた値タプルからテールのヘッドを作るのに苦労している誰かを助けることを願っています。

2
XDS

これはそれが何であるか、そしてその限界のかなり良い要約です。

https://josephwoodward.co.uk/2017/04/csharp-7-valuetuple-types-and-their-limitations

0
nick-s