В чем разница между System.ValueTuple и System.Tuple?
Я декомпилировал некоторые библиотеки C# 7 и увидел ValueTuple
генерики используются. Что ValueTuples
и почему бы нет Tuple
вместо?
6 ответов
Что
ValuedTuples
и почему бы нетTuple
вместо?
ValueTuple
это структура, которая отражает кортеж, так же, как оригинал System.Tuple
учебный класс.
Основное различие между Tuple
а также ValueTuple
являются:
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> ints)
{
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
,
Разница между 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# pre-7, потому что они были громоздкими и многословными, и действительно использовались только в тех случаях, когда создание класса / структуры данных только для одного экземпляра работы было бы большим трудом, чем оно того стоило. Но в C# 7 кортежи теперь имеют поддержку на уровне языка, поэтому их использование намного чище и полезнее.
Я посмотрел на источник для обоих Tuple
а также ValueTuple
, Разница в том, что Tuple
это class
а также ValueTuple
это struct
который реализует IEquatable
,
Это означает, что Tuple == Tuple
вернусь false
если они не один и тот же экземпляр, но ValueTuple == ValueTuple
вернусь true
если они одного типа и Equals
возвращается true
для каждого из значений, которые они содержат.
В дополнение к комментариям, приведенным выше, одна неприятная особенность ValueTuple заключается в том, что именованные аргументы стираются как тип значения при компиляции в IL, поэтому они не доступны для сериализации во время выполнения.
т.е. ваши аргументы с именами по-прежнему будут заканчиваться как "Item1", "Item2" и т. д. при сериализации через, например, Json.NET.
Другие ответы забыли упомянуть важные моменты. Вместо того, чтобы перефразировать, я буду ссылаться на документацию XML из исходного кода:
Типы ValueTuple (от 0 до 8) включают реализацию времени выполнения, которая лежит в основе кортежей в C# и структурных кортежей в F#.
Помимо созданного с помощью синтаксиса языка, они легче всего создаются с помощью ValueTuple.Create
заводские методы. System.ValueTuple
типы отличаются от System.Tuple
Типы в этом:
- они скорее структуры, чем классы,
- они изменчивы, а не только для чтения, и
- их члены (такие как Item1, Item2 и т. д.) являются полями, а не свойствами.
С введением этого типа и компилятора C# 7.0 вы можете легко написать
(int, string) idAndName = (1, "John");
И вернуть два значения из метода:
private (int, string) GetIdAndName()
{
//.....
return (id, name);
}
Вопреки System.Tuple
вы можете обновить его члены (изменяемые), потому что они являются открытыми полями для чтения и записи, которым могут быть даны значимые имена:
(int id, string name) idAndName = (1, "John");
idAndName.name = "New Name";
Позднее присоединение для краткого пояснения этих двух фактоидов:
- это структуры, а не классы
- они изменяемы, а не только для чтения
Можно было бы подумать, что массовое изменение кортежей значений будет простым:
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