Коли використовувати: Кортеж проти класу в C # 7.0


79

До Кортежів я створював клас та його змінні, потім створював об’єкт із цього класу та робив цей об’єкт типом повернення для деяких функцій.

Тепер, з кортежами, я можу зробити те ж саме, і в C # 7.0 ми можемо привласнити зрозумілі імена властивостей кортежу (до цього він був item1, item2і т.д ..)

Тому зараз мені цікаво, коли слід використовувати кортеж і коли створювати клас на C # 7.0?


2
Зверніть увагу, що назви елементів кортежу в основному є властивістю часу проектування і не існують у складеному ІЛ. Тож якщо ви піклуєтесь про це, у вас є ще одна причина для належних занять.
тикати

3
@OrkhanAlikhanov Незважаючи на те, що це було б правильною дублікатною метою, я погоджуюсь з OP, що зараз змінився час із C # 7, тому відповіді 2012 та 2010 років застосовуються лише частково.
тикати

2
@poke , що не є допустимим дублікатом мета, що мова йшла про System.Tuple , це один про новий System.ValueTuple , який має спеціальний синтаксичний цукор в C # 7.
Theraot

@ Theraot Ви розумієте, що я критикував пропозицію закрити це питання, чи не так? Я знаю, про що йдеться в цьому питанні, саме тому я запропонував залишити це відкритим.
тикати

@Theraot О, я щойно зробив ту ж помилку, інтерпретуючи це питання як посилання на System.Tuple, оскільки там написано ... "Кортеж". Якщо ви впевнені, що це насправді стосується чогось іншого, то вам слід відредагувати це, щоб зробити це більш чітким. Я скасував своє закриття, припустивши, що ви праві.
Коді Грей

Відповіді:


59

Оскільки ця відповідь викликає певну плутанину серед деяких людей, я маю пояснити, що - відповідно до питання - всі посилання на "кортеж" тут стосуються ValueTupleтипу та нових синтаксичних характеристик цукру C # 7 і жодним чином не стосуються старого System.Tupleеталонні типи.

Отже, зараз мені цікаво, коли слід використовувати кортежі і коли слід створювати клас в c # 7.0?

Тільки ви можете відповісти на це запитання, оскільки це насправді залежить від вашого коду.

Однак є вказівки та правила, якими ви можете керуватися, вибираючи між ними:

Кортежі є значеннями, тому копіюються за значенням, а не за посиланням.

Здебільшого це не повинно бути проблемою. Однак якщо ви проходите навколо кортежів великих конструкцій, це може вплинути на продуктивність. Місцеві жителі / повернення можуть бути використані для вирішення цих проблем із продуктивністю.

Крім того, оскільки вони є значеннями, віддалене змінення копії не змінить оригінальну копію. Це гарна річ, але може спіймати когось із людей.

Назви елементів кортежу не зберігаються

Імена, надані елементам, використовуються компілятором і (у більшості випадків) недоступні під час виконання. Це означає, що для розкриття їх імен не можна використовувати рефлексію; до них неможливо отримати динамічний доступ і їх не можна використовувати в режимах бритви.

Також це є важливим фактором, що стосується API. Кортеж, повернутий із методу, є винятком із правила щодо виявлення імені після компіляції. Компілятор додає до методу атрибути, які містять інформацію про назви кортежів. Це означає, що ви можете безпечно повернути кортеж із загальнодоступного методу в одній збірці та отримати доступ до його назв в іншій.

Кортежі легкі

Кортежі набагато простіші для написання, ніж типи, оскільки вони менш детальні, і декларація може бути "вбудованою" (тобто оголошена в точці використання). Це добре працює, наприклад, при оголошенні методу, який повертає кілька значень.

Однак, оскільки вони оголошені в момент використання, якщо у вас є MethodAвиклики, MethodBякі викликають, MethodCі кожен повертає кортеж, вам доведеться перевизначити кортеж на кожному етапі. Не існує ( поки ) способу створити псевдонім кортежу та повторно використати його за допомогою кількох методів.

Просто використовуйте здоровий глузд

Для будь-якої ситуації, коли ви можете розглянути можливість використання кортежу: просто задайте собі питання: "чи буде кортеж спрощувати код тут". Якщо відповідь "так", то скористайтеся одним. І це, зрештою, є головним питанням щодо того, використовувати кортеж або спеціальний клас.


1
ValueTuple- тип значення. Tupleє еталонним типом. Так просто Tupleне можна скопіювати посилання. Ви повинні згадати це напівжирним шрифтом, оскільки це зараз бентежить
Szer

2
@Szer, питання стосується кортежів C # 7, тому я не розумію, чому виникає плутанина. Але я оновив питання, щоб пояснити цей момент.
Девід Арно,

@DavidArno, коли я вперше почав використовувати C # 7, мені було незрозуміло, чи новий синтаксис Tuple був новим типом чи просто мовною підтримкою.
Gusdor

Я здивований, що назви елементів кортежу не можуть бути використані в поданнях Razor. Чи не файли Razor перетворюються на C #, а потім компілюються? Якщо так, вони повинні бачити назви елементів кортежу з метаданих / IL, як і будь-яка компіляція клієнта.
Julien Couvreur

1
@JulienCouvreur, наскільки мені відомо, це обмеження інструментів для бритви. Однак, на вигляд github.com/aspnet/Razor/issues/1046 , це зараз або виправлено, або виправлення буде випущено найближчим часом.
Девід Арно

28

Взагалі кажучи, іменовані класи мають певне значення у розробці вашої системи. Вони також більш багатослівно пишуть. Наприклад, у вас може бути клас, який називається MediaFileOpener. Для дизайну важливо, щоб ми знали, чим займається цей клас - ми працюємо з медіафайлами!

Анонімні типи та кортежі використовуються тоді, коли конструктивне значення не має значення, і все, що вам потрібно - це полегшений об’єкт передачі даних (DTO) для переміщення інформації.

Як правило, якщо ваш клас вимагає якоїсь документації, щоб описати, для чого він призначений, або якщо існує така поведінка, використовуйте повний клас. Якщо все, що вам потрібно, це тимчасове сховище або якась групування, використовуйте Tuple. Розглянемо ситуацію, коли ви хочете повернути більше одного значення з асинхронного методу. Кортеж призначений для вирішення цієї проблеми.


12

Використовуйте клас

Якщо ваші об'єкти - це сутності, які широко використовуються у вашій програмі, а також зберігаються в якомусь постійному сховищі, як-от реляційна база даних (SQL Server, MySQL, SQLite), база даних NoSQL або кеш-пам'ять (Redis, Azure DocumentDB) або навіть на простих текстові файли або файли CSV.

Так що, будь-що наполегливе повинно мати свій клас.

Використовуйте кортеж

Якщо ваші об’єкти недовговічні, без особливого значення для вашої програми. Наприклад, якщо вам потрібно швидко повернути пару координат, краще мати щось подібне:

(double Latitude, double Longitude) getCoordinates()
{
    return (144.93525, -98.356346);
}

ніж визначити окремий клас

class Coordinates
{
    public double Latitude { get; set; }
    public double Longitude { get; set; }
}

Кортеж заощадить ваш час від необхідності розподіляти пам'ять у купі за допомогою newтакої простої операції.

Інший раз, коли я вважаю кортежі корисними, це виконуючи кілька математичних операцій над деякими операндами

(double Result1, double Result2, double Result3) performCalculations(int operand1, int operand 2)

У цьому випадку немає сенсу визначати клас. Незалежно від обчислень результати не належать до класу. Тож альтернативою було б використання outзмінних, але я вважаю, що кортежі є більш виразними та призводять до поліпшення читабельності.


2
Я б визначив Coordinatesяк structзамість class.
Орхан Аліханов

7
Координати можуть бути хорошим прикладом для того, щоб не використовувати кортеж, оскільки це досить загальна річ, яка вам, ймовірно, потрібна весь час . Крім того, ви можете перевантажити, ToStringщоб забезпечити читабельний результат (до речі, за замовчуванням ToString цього кортежу вже добре працює тут), а також перевантаження Equals/ GetHashCodeдля порівняння рівності.
тикати

ІМХО, це видається дуже самовпевненою відповіддю, з неясними рекомендаціями щодо невідповідності. Я не думаю, що oop має до цього щось спільне (анонімні класи можна віднести до обох категорій). Крім того, додавання стійкості на рівні бази даних не має нічого спільного з цими міркуваннями.
Даніель Дубовський

4

Як правило, ви хочете мати клас, коли ваш об'єкт буде використовуватися десь ще або якщо він представляє реальний об'єкт або концепцію у вашому домені. Напевно, ви збираєтеся створити клас, який представляв би автомобіль чи автомагазин, а не кортежі.

З іншого боку, іноді потрібно просто повернути пару об’єктів із методу. Можливо, вони не представляють нічого особливого, просто вам потрібно повернути їх разом саме цим методом. Іноді, навіть якщо вони представляють концепцію з вашого домену (скажімо, ви повертаєтесь (Car, Store), що може бути представлено як Saleоб’єкт), ви насправді нікуди не збираєтесь їх використовувати - ви просто переміщуєте дані. У цих випадках добре використовувати кортежі.

Тепер, говорячи про C #, є ще одна річ, яку ви повинні знати. Тип кортежу C # 7 насправді є тим ValueTuple, що є структурою. На відміну від класів, які є еталонними типами, структури є типами цінностей. Ви можете прочитати більше про це на msdn . Найголовніше, знайте, що вони можуть вимагати багато копіювання, тому будьте обережні.


4

Я думаю, що це стане питанням, яке часто задають. В даний час не існує "найкращої практики" щодо того, коли використовувати нові кортежі значень проти класу.

Однак варто прочитати, що з’явилося під час попередніх бесід про попередні версії кортежів проти класу .

На мій погляд, кортеж значень слід використовувати лише мінімально і не більше ніж максимум з трьох значень. Я думаю, що це забезпечує хороший баланс між "поверненням деяких цінностей без необхідності класу" та "жахливим безладом цінностей". Якщо у вас є більше трьох значень для повернення, створіть клас.

Я також ніколи не використовував би кортеж для повернення із загальнодоступного API, який споживачам доведеться використовувати. Знову ж таки, просто використовуйте клас.

Ось деякий реальний код, який я використовував:

public async Task<(double temperature, double humidity, string description)> DownloadTodaysForecast()

Як тільки я хочу повернути більш складні дані, я роблю клас.


3

На початку я хочу згадати, що C # вже підтримував анонімні типи . Які є еталонними типами. Таким чином, ви вже мали відповідну альтернативу створенню іменованого класу.

Однією з переваг іменованого класу є легше повторне використання (наприклад, якщо вам потрібен однаковий тип у кількох місцях) та документування. Оскільки анонімний тип є анонімним, ви можете отримати змінні, набрані до нього, лише якщо ви можете використовувати це var, що обмежує контексти, в яких корисні анонімні типи (наприклад, ви не можете використовувати їх як типи полів, типи повернення або типи параметрів) .

Звичайно, ви можете перейти до деяких обмежень анонімних типів, використовуючи System.Tuple. Це також посилальний тип, і ви можете використовувати його явно. Недоліком є ​​те, що в ньому відсутні спеціальні імена для членів.


Кортежі C # 7 ( ValueTuple) можна вважати подібними до анонімних типів. Перша відмінність полягає в тому, що вони є типами цінностей. Це означає, що ці кортежі матимуть переваги в продуктивності до тих пір, поки вони залишаться в локальному масштабі або переміщаються по стеку (що є загальним використанням анонімних типів через його обмеження).

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

Третя відмінність полягає в тому, що ValueTupleпідтримується деконструкція з коробки. Процитувавши Нове у C # 7.0 :

Інший спосіб споживання кортежів - це їх деконструкція. Декларація деконструкції - це синтаксис для розділення кортежу (або іншого значення) на його частини та присвоєння цих частин окремо змінним:

(string first, string middle, string last) = LookupName(id1); // deconstructing declaration
WriteLine($"found {first} {last}.");

Що ви також можете зробити із користувацькими типами, додавши метод Deconstruct .


Для реферату:

  • ValueTuple має синтаксичний цукор в C # 7.0, що слід враховувати для читабельності.
  • ValueTupleє типом значення. Усі плюси і мінуси між використанням a classта structapply.
  • ValueTupleможе використовуватися просто (з синтаксичним цукром або без нього), що дозволяє йому мати універсальність System.Tuple, зберігаючи іменовані члени.
  • ValueTuple підтримує деконструкцію.

Враховуючи, що суттєвим елементом є синтаксичний цукор, я б сказав, що сильнішим аргументом для вибору ValueTupleє той самий аргумент для вибору a struct. Що було б ідеально для невеликих, незмінних типів, які живуть здебільшого на стеці (так що у вас не так багато боксу та розпакування).

Порівнюючи ValueTupleз повномасштабною структурою, враховуючи синтаксичний цукор, я б запропонував використовувати ValueTupleза замовчуванням, якщо вам не потрібен явний макет або вам потрібно додати до нього методи.

Я також хочу сказати, що синтаксичний цукор не обов'язково покращує читабельність. Основною причиною є те, що ви не називаєте тип, а ім'я типу надає значення коду. На додаток до цього, ви можете додати документацію до декларації structабо classдекларації, що полегшує розуміння.

Загалом, ситуація, коли по- ValueTupleсправжньому блищить, повертає безліч значень з методу. У цьому випадку відпадає необхідність створення нових outпараметрів. А документація ValueTupleвикористовуваного може жити в документації методу. Якщо ви виявите, що вам потрібно зробити щось інше за допомогою ValueTuple(наприклад, визначення методів розширення), я пропоную замість цього створити іменований тип.


2

Кортежі призначені для представлення декількох значень, наприклад, коли метод має намір повернути кілька значень. Підтримка кортежу в C # 7 використовує System.ValueTuple<...>екземпляри для представлення цього набору значень. Імена цих значень є дійсними лише в тому контексті, де вони використовуються і не застосовуються.

Класи призначені для представлення одного значення з кількома атрибутами.


2

Я б уникав використання кортежів як публічних методів повернення. У таких випадках я вважаю за краще визначити клас або структуру.


2
Не могли б ви пояснити, чому?
Фютеміре

@ Fütemire Google "відсутність абстракції" або "примітивна одержимість" дизайнерський запах.
Мілош Мрдович,

@MilosMrdovic дякую, я розгляну це.
Фютеміре

1

Кортеж - чудовий варіант, коли ви хочете об'єднати кілька значень (можуть бути різних типів) в один об'єкт без створення власного класу. У цьому випадку Tuple буде швидким і ідеальним варіантом.

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