私はいくつかのC#7ライブラリを逆コンパイルし、ValueTuple
ジェネリックが使われているのを見ました。 ValueTuples
とは何ですか。なぜTuple
ではないのですか。
ValueTuples
とは何ですか。なぜTuple
ではないのですか。
A ValueTuple
は、元のSystem.Tuple
クラスと同じように、Tupleを反映する構造体です。
Tuple
とValueTuple
の主な違いは次のとおりです。
System.ValueTuple
は値型(struct)ですが、System.Tuple
は参照型(class
)です。これは、割り当てとGCのプレッシャーについて話すときに意味があります。System.ValueTuple
はstruct
だけではなく、可変なので、それらをそのように使うときは注意が必要です。クラスがフィールドとしてSystem.ValueTuple
を保持するとどうなるか考えてください。System.ValueTuple
は、プロパティではなくフィールドを介してそのアイテムを公開します。C#7までは、タプルを使うのはあまり便利ではありませんでした。それらのフィールド名はItem1
、Item2
などであり、その言語は他のほとんどの言語がそうであるように(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;
}
内部的には、コンパイルされたコードはItem1
とItem2
を利用しますが、私たちは分解された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));
}
Item1
、Item2
を見るのは奇妙なことなので、アプリケーションをデバッグするときにコンパイラはまだ(属性を介して)何らかの魔法をかける必要があることに注意してください。
Tuple
とValueTuple
の違いは、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では、タプルは現在言語レベルのサポートを持っているので、それらを使うことははるかにクリーンでより便利です。
Tuple
とValueTuple
の両方のソースを調べました。違いは、Tuple
はclass
であり、ValueTuple
はstruct
を実装するIEquatable
です。
つまり、Tuple == Tuple
は、それらが同じインスタンスでない場合はfalse
を返しますが、ValueTuple == ValueTuple
は同じ型である場合はtrue
を返し、Equals
はそれぞれの値についてtrue
を返します。
上記のコメントに加えて、ValueTupleの残念な問題の1つは、値型として、名前付き引数がILにコンパイルされると消去されるため、実行時にシリアル化できないことです。
すなわち、あなたの甘い名前付き引数は、例えば "#1"、 "#2"などでシリアライズされたときにはまだ終わるでしょう。 Json.NET.
言い換えれば、私は source code からXML文書を参照するつもりです。
ValueTuple型(アリティ0から8)は、C#のタプルとF#の構造体タプルの基礎となるランタイム実装を構成します。
言語構文で作成される以外に、それらはValueTuple.Create
ファクトリメソッドで最も簡単に作成されます。 System.ValueTuple
型は、以下の点でSystem.Tuple
型と異なります。
このタイプと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";
これらの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
これがリストにホストされた値タプルからテールのヘッドを作るのに苦労している誰かを助けることを願っています。
これはそれが何であるか、そしてその限界のかなり良い要約です。
https://josephwoodward.co.uk/2017/04/csharp-7-valuetuple-types-and-their-limitations