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


139

いくつかのC#7ライブラリを逆コンパイルし、ValueTupleジェネリックが使用されているのを見ました。代わりに何でValueTuples、なぜそうしないのTupleですか?


Dot NEt Tupleクラスを参照していると思います。サンプルコードを教えてください。分かりやすいように。
Ranadip Dutta 2016

14
@Ranadip Dutta:タプルが何であるかわかっている場合、質問を理解するためにサンプルコードは必要ありません。質問自体は簡単です:ValueTupleとは何ですか?Tupleとどう違うのですか?
BoltClock

1
@BoltClock:それが私がその文脈で何も答えなかった理由です。私はc#で知っていますが、かなり頻繁に使用するタプルクラスがあり、Powershellでも同じクラスを呼び出すことがあります。その参照型。他の回答を見ると、Valuetupleとしても知られる値タイプがあることがわかりました。サンプルがある場合、その使い方を知りたいのですが。
Ranadip Dutta 2016

2
Roslynのソースコードがgithubで入手できるのに、なぜこれらを逆コンパイルするのですか?
Zein Makki

@ user3185569おそらくF12が物事を自動逆コンパイルするので、GitHubにジャンプするよりも簡単です
John Zabroski

回答:


203

代わりに何でValueTuples、なぜそうしないのTupleですか?

A ValueTupleは、元のSystem.Tupleクラスと同じ、タプルを反映した構造体です。

主な違いTupleとはValueTuple、次のとおりです。

  • System.ValueTupleは値の型(構造体)System.Tupleですが、参照型(class)です。これは、割り当てとGC圧力について話すときに意味があります。
  • System.ValueTuple だけではありません struct変更可能なものであり、そのように使用する場合は注意が必要です。クラスが持っているときに何が起こるか考えてくださいSystem.ValueTupleフィールドとしてaをと。
  • System.ValueTuple プロパティの代わりにフィールドを介してアイテムを公開します。

C#7までは、タプルの使用はあまり便利ではありませんでした。それらのフィールド名はItem1Item2など、他のほとんどの言語は(Pythonの、スカラ座)やるような言語は、彼らのためにシンタックスシュガーを供給していませんでした。

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

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

以下のデザインノートのTuples段落です

構造またはクラス:

言及したように、私はタプルタイプを作るstructsよりも classes割り当てのペナルティが関連付けられないように、します。それらはできるだけ軽量でなければなりません。

structs割り当てはより大きな値をコピーするため、間違いなく、結果的にコストが高くなる可能性があります。したがって、作成された数よりもはるかに多く割り当てられている場合はstructs、不適切な選択になります。

しかし、彼らの非常に動機において、タプルは短命です。全体よりもパーツが重要な場合に使用します。したがって、一般的なパターンは、それらを構築し、返し、すぐに分解することです。この状況では、構造体が明らかに望ましいです。

構造体には他にも多くの利点がありますが、これは以下で明らかになります。

例:

での作業がSystem.Tupleあっという間にあいまいになることが簡単にわかります。たとえば、aの合計とカウントを計算するメソッドがあるとします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;
}

内部的には、コンパイルされたコードはItem1およびを利用しますItem2が、分解されたタプルを使用するため、これらはすべて抽象化されています。名前付き引数を持つタプルは、で注釈が付けられます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


1
より単純な(そして私の意見では、好ましい)構文を使用することもできますvar (sum, count) = DoStuff(Enumerable.Range(0, 10));
Abion47

@ Abion47両方のタイプが異なる場合はどうなりますか?
Yuval Itzchakov 16

また、「可変構造です」と「読み取り専用フィールドを公開する」という点はどのように一致しますか?
CodesInChaos 16

@CodesInChaosありません。[this](github.com/dotnet/corefx/blob/master/src/Common/src/System/…)を見ましたが、ローカルフィールドはとにかく読み取り専用。この提案は「必要に応じて読み取り専用にすることもできますが、それはあなた次第」という意味であると私は誤解しました。
Yuval Itzchakov 16

1
一部のnit:「それらはスタックに割り当てられます」 -ローカル変数に対してのみtrue。これを知っていることは間違いありませんが、残念ながら、これを表現した方法は、値型が常にスタックに存在するという神話を永続させる可能性があります。
Peter Duniho

26

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

ただし、C#7では、 持っていないために明示的タプルの使用のために追加されたシンタックスシュガーのいずれかのタイプを使用します。たとえば、C#6では、タプルを使用して値を返す場合、次のことを行う必要があります。

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; 

...またはタプルを完全に分解します。

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

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

タプルは面倒で冗長なため、C#7より前のバージョンではあまり使用されませんでした。実際に使用されるのは、単一の作業インスタンスのデータクラス/構造体を構築するだけの場合よりも問題が多い場合のみです。しかし、C#7では、タプルが言語レベルでサポートされるようになったため、タプルを使用する方がよりクリーンで便利です。


10

との両方のソースを確認TupleしましたValueTuple。違いはTupleclassValueTuplestructを実装することIEquatableです。

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


それだけではありません。
BoltClock

2
@BoltClock詳しく説明すると、コメントは建設的になります
Peter Morris

3
また、値型は必ずしもスタックに入れられるわけではありません。違いは、変数が格納されるときは常に、参照ではなく意味的に値を表すことです。これは、スタックである場合とそうでない場合があります。
サービー16

6

他の回答は重要なポイントを言及するのを忘れていました。言い換えるのではなく、ソースコードからXMLドキュメントを参照します

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

言語構文を介して作成される以外に、これらはValueTuple.Createファクトリメソッドを介して最も簡単に作成され ます。System.ValueTupleタイプは以下のタイプと異なりますSystem.Tuple

  • クラスではなく構造体です。
  • 読み取り専用はなく変更可能であり、
  • それらのメンバー(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";

「アリティ0から8」。ああ、私はそれらが0タプルを含むという事実が好きです。それは空のタイプの一種として使用することができ、いくつかのタイプのパラメータが必要とされていない場合には同様に、ジェネリック医薬品に許可されるclass MyNonGenericType : MyGenericType<string, ValueTuple, int>など
ジャップスティグ・ニールセン

6

上記のコメントに加えて、ValueTupleの不幸な欠点の1つは、値の型として、名前付き引数がILにコンパイルされるときに消去されるため、ランタイムでのシリアル化に使用できないことです。

つまり、スイートの名前付き引数は、Json.NETなどを介してシリアル化すると、「Item1」、「Item2」などになります。


2
技術的には、これは類似点で違いはありません;)
JAD

2

これらの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

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

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.