いくつかのC#7ライブラリを逆コンパイルし、ValueTuple
ジェネリックが使用されているのを見ました。代わりに何でValueTuples
、なぜそうしないのTuple
ですか?
いくつかのC#7ライブラリを逆コンパイルし、ValueTuple
ジェネリックが使用されているのを見ました。代わりに何でValueTuples
、なぜそうしないのTuple
ですか?
回答:
代わりに何で
ValueTuples
、なぜそうしないのTuple
ですか?
A ValueTuple
は、元のSystem.Tuple
クラスと同じ、タプルを反映した構造体です。
主な違いTuple
とはValueTuple
、次のとおりです。
System.ValueTuple
は値の型(構造体)System.Tuple
ですが、参照型(class
)です。これは、割り当てとGC圧力について話すときに意味があります。System.ValueTuple
だけではありません struct
、変更可能なものであり、そのように使用する場合は注意が必要です。クラスが持っているときに何が起こるか考えてくださいSystem.ValueTuple
フィールドとしてaをと。System.ValueTuple
プロパティの代わりにフィールドを介してアイテムを公開します。C#7までは、タプルの使用はあまり便利ではありませんでした。それらのフィールド名はItem1
、Item2
など、他のほとんどの言語は(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));
}
我々は我々のアプリケーションをデバッグするとき、見て奇妙なように、コンパイラはまだ、(属性を経由して)いくつかの魔法を実現するために持っていることに注意してくださいItem1
、Item2
。
var (sum, count) = DoStuff(Enumerable.Range(0, 10));
との違いはTuple
、ValueTuple
それ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では、タプルが言語レベルでサポートされるようになったため、タプルを使用する方がよりクリーンで便利です。
との両方のソースを確認Tuple
しましたValueTuple
。違いはTuple
、class
とValueTuple
はstruct
を実装することIEquatable
です。
つまり、Tuple == Tuple
返されますfalse
、彼らは同じインスタンスでない場合は、しかし、ValueTuple == ValueTuple
返されますtrue
、彼らは同じ型とである場合にEquals
戻ってtrue
、彼らが含まれている値のそれぞれについて。
他の回答は重要なポイントを言及するのを忘れていました。言い換えるのではなく、ソースコードから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";
class MyNonGenericType : MyGenericType<string, ValueTuple, int>
など
上記のコメントに加えて、ValueTupleの不幸な欠点の1つは、値の型として、名前付き引数がILにコンパイルされるときに消去されるため、ランタイムでのシリアル化に使用できないことです。
つまり、スイートの名前付き引数は、Json.NETなどを介してシリアル化すると、「Item1」、「Item2」などになります。
これらの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
これが、リストがホストする値タプルからテールのヘッドを作成するのに苦労している人を助けることを願っています。