У чому різниця між System.ValueTuple і System.Tuple?


139

Я декомпілював деякі бібліотеки C # 7 і побачив, ValueTupleяк використовуються дженерики. Що таке, ValueTuplesа чому не Tupleзамість цього?


Я думаю, що це стосується класу Dot NEt Tuple. Чи можете ви поділіться зразком коду. Так що це було б легко зрозуміти.
Ранадіп Дутта

14
@Ranadip Dutta: Якщо ви знаєте, що таке кортеж, вам не знадобиться зразок коду, щоб зрозуміти питання. Питання саме по собі просте: що таке ValueTuple і чим він відрізняється від Tuple?
BoltClock

1
@BoltClock: Тому я нічого не відповів у цьому контексті. Я знаю, що в c # є клас Tuple, який я досить часто використовую і той самий клас, колись я його також називаю в powerhell. Його еталонний тип. Тепер, побачивши інші відповіді, я зрозумів, що існує також тип значення, який відомий як Valuetuple. Якщо є зразок, я хотів би дізнатися про використання для того ж.
Ранадіп Дутта

2
Чому ви декомпілюєте їх, коли вихідний код для Roslyn доступний на github?
Зейн Макі

@ user3185569, мабуть, тому, що F12 автоматично декомпілює речі і це простіше, ніж стрибати на GitHub
Джон Заброський

Відповіді:


203

Що таке, ValueTuplesа чому не Tupleзамість цього?

A ValueTuple- це структура, яка відображає кортеж, такий же, як і оригінальний System.Tupleклас.

Основна відмінність між Tupleі ValueTuple:

  • System.ValueTupleє типом значення (структура), а System.Tupleтип посилань ( class). Це має сенс, якщо говорити про виділення та тиску GC.
  • System.ValueTupleце не тільки a struct, це мутаційний , і потрібно бути обережним при використанні їх як таких. Подумайте, що станеться, коли клас містить System.ValueTupleполе як поле.
  • System.ValueTuple виставляє свої елементи через поля замість властивостей.

До C # 7 користуватися кортежами було не дуже зручно. Їх імена полів Item1, Item2і т.д., і мова не поставляється синтаксис для них , як і більшості інших мов робити (Python, Scala).

Коли команда дизайнерів мови .NET вирішила включити кортежі та додати до них синтаксичний цукор на мовному рівні, важливим фактором була продуктивність. З ValueTupleтого типу значення, ви можете уникнути тиску ГХ при їх використанні , тому що (як деталь реалізації) вони будуть виділені в стеці.

Додатково, програма structотримує автоматичну (неглибоку) семантику рівності за часом виконання, де classні. Хоча команда дизайнерів переконалася, що буде ще більш оптимізована рівність для кортежів, отже, вона застосувала для нього рівність на замовлення.

Ось абзац із примітокTuples до дизайну :

Структура або клас:

Як вже було сказано, я пропоную зробити типи кортежів, 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, але все це абстрагується від нас, оскільки ми працюємо з розкладеним кортежем. Корпорація з названими аргументами отримує коментарі до 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.


1
Зауважте, що ви також можете використовувати простіший (і на мій погляд, кращий) синтаксисvar (sum, count) = DoStuff(Enumerable.Range(0, 10));
Abion47

@ Abion47 Що буде, якщо обидва типи відрізняються?
Юваль Ітчаков

Крім того, як згодні ваші пункти "це змінна структура" і "вона відкриває поля, які читаються тільки "?
CodesInChaos

@CodesInChaos Це не так. Я бачив [це] ( github.com/dotnet/corefx/blob/master/src/Common/src/System/… ), але я не думаю, що це в кінцевому підсумку випускається компілятором, оскільки локальні поля не можуть бути тільки в будь-якому випадку. Я думаю, що пропозиція означала "ви можете зробити їх заново, якщо хочете, але це залежить від вас" , що я неправильно трактував.
Юваль Ітчаков

1
Деякі гниди: "вони будуть виділені в стеку" - справедливо лише для локальних змінних. Без сумніву, ви це знаєте, але, на жаль, те, як ви це висловили, може, увічнити міф про те, що типи значень завжди живуть у стеці.
Пітер Дуніхо

26

Різниця між 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 кортежі мають підтримку на рівні мови, тому їх використання набагато чистіше і корисніше.


10

Я подивився на джерело і для, Tupleі для ValueTuple. Різниця полягає в тому, що Tupleце classта ValueTupleє, structщо реалізується IEquatable.

Це означає, що Tuple == Tupleвони повернуться, falseякщо вони не є тим самим екземпляром, але ValueTuple == ValueTupleповернуться, trueякщо вони одного типу і Equalsповернуть trueдля кожного зі значень, які вони містять.


Це більше, ніж це.
BoltClock

2
@BoltClock Ваш коментар був би конструктивним, якби ви допрацювали
Пітер Морріс

3
Також типи значень не обов'язково переходять у стек. Різниця полягає в тому, що при збереженні цієї змінної семантично представляють значення, а не посилання, яке може бути, а може і не бути.
Сервіс

6

Інші відповіді забули згадати важливі моменти. Замість перефразовування я посилаюся на 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ви можете оновити його членів (Mutable), оскільки вони є загальнодоступними полями для читання-запису, яким можна надати значущі імена:

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

"Ариті від 0 до 8". Ах, мені подобається те, що вони включають 0-кортеж. Він може використовуватися як різновид порожнього типу, і він буде дозволений у дженеріках, коли якийсь параметр типу не потрібен, як class MyNonGenericType : MyGenericType<string, ValueTuple, int>і т. Д.
Jeppe Stig Nielsen

6

На додаток до вищезазначених коментарів, однією невдалою дією ValueTuple є те, що як тип значення названі аргументи стираються при компіляції в IL, тому вони недоступні для серіалізації під час виконання.

тобто ваші солодкі названі аргументи все ще залишатимуться як "Item1", "Item2" тощо, коли вони будуть серіалізовані через, наприклад, Json.NET.


2
Тож технічно це схожість, а не різниця;)
JAD,

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

Сподіваємось, це допомагає комусь, хто бореться за те, щоб зробити голови хвостів із списків, розміщених у списку.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.