Я декомпілював деякі бібліотеки 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це не тільки 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.
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 # pre-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");
І повернути два значення з методу:
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 є те, що як тип значення названі аргументи стираються при компіляції в IL, тому вони недоступні для серіалізації під час виконання.
тобто ваші солодкі названі аргументи все ще залишатимуться як "Item1", "Item2" тощо, коли вони будуть серіалізовані через, наприклад, Json.NET.
Пізнє приєднання до швидкого роз'яснення цих двох фактоїдів:
Можна було б подумати, що масове зміна кортежів вартості:
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
Сподіваємось, це допомагає комусь, хто бореться за те, щоб зробити голови хвостів із списків, розміщених у списку.