День двести шестьдесят пятый. #ЗаметкиНаПолях

Заметки по использованию кортежей

Замечание: что здесь приведены умозаключения о вариантах использования кортежей, поскольку реальной практики их применения ещё не так много.

1. Непубличные API и легко изменяемый код

Пока сообщество не имеет достаточно опыта использования кортежей в публичных API, не набило шишек и не выработало лучших практик использования, лучше будет с ними не экспериментировать. Если же вы контролируете (и можете произвольно изменять) весь код, который взаимодействует с вашим API, можно попробовать. Но не стоит ставить себя в ситуацию, когда вы возвращаете кортеж из открытого метода только потому, что это легко сделать, чтобы потом оказалось, что значения придётся инкапсулировать. Именованный тип требует больше усилий по проектированию и реализации, но результат вряд ли будет более сложным для использования вызывающей стороной. Кортежи в основном удобны для автора, но не для пользователя.



2. Локальные переменные

Кортежи в первую очередь предназначены для того, чтобы из метода можно было возвращать несколько значений без использования параметров или специально созданного типа. Но это не значит, что это единственное место, где вы можете их использовать. Внутри метода нет ничего необычного в том, чтобы иметь естественные группы переменных. Вы можете часто замечать, что переменные имеют общий префикс. Поэтому следующий код:

string bestPlayer;

int bestScore;

foreach (var game in games)

{

if (game.Score > bestScore)

{

bestPlayer = game.Player;

bestScore = game.Score;

}

}

Можно переписать так:

(string player, int score) best = (null, -1);

foreach (var game in games)

{

if (game.Score > best.score)

{

best = (game.Player, game.Score);

}

}



3. Поля

Сказанное выше про локальные переменные можно применить и к полям класса. Однако существует несколько ограничений и предостережений:

- Элементы кортежа могут по-прежнему назначаться в конструкторе индивидуально, но, если вы инициализируете не все элементы, никакого предупреждения не будет.

- Либо все поле кортежа будут доступны только для чтения, либо нет. Если у вас есть группа связанных полей, некоторые из которых доступны только для чтения, а некоторые нет, вам придётся либо отказаться от использования модификатора readonly, либо не использовать кортежи.

- Если некоторые из полей являются автоматически сгенерированными, поддерживающими автоматически реализованные свойства, вам потребуется написать полные свойства, чтобы использовать кортеж.



4. Кортежи и dynamic не дружат

Есть подозрение, что кортежи и тип dynamic и так не будут особо пересекаться. Но всё же стоит помнить о двух проблемах, связанных с доступом к элементам:

- Связыватель типа dynamic не знает об именах элементов

Имена элементов кортежа в основном касаются времени компиляции. Так как динамическое связывание происходит только во время выполнения, следующий код (несмотря на то, что выглядит безобидно):

dynamic tuple = (x: 10, y: 20);

Console.WriteLine(tuple.x);

приведёт к ошибке времени выполнения:

'System.ValueTuple<int,int>' не содержит определения для 'x'

Если изменить код на вывод tuple.Item1, он сработает (по крайней мере, для первых 8 элементов).

- Связыватель типа dynamic (пока) не знает об элементах кортежей далее 8-го

Для кортежей длиннее 8 элементов компилятор использует ValueTuple<…>, где восьмым элементом является ещё один кортеж. Таким образом 9й элемент – это первый элемент кортежа, находящегося в 8-м элементе исходного кортежа. Таким образом, следующий код скомпилируется:

var tuple = (1, 2, 3, 4, 5, 6, 7, 8, 9);

Console.WriteLine(tuple.Item9);

dynamic d = tuple;

Console.WriteLine(d.Item9);

Но на настоящий момент приводит к ошибке времени выполнения:

'System.ValueTuple<int,int,int,int,int,int,int,System.ValueTuple<int,int>>' не содержит определения для 'Item9'

Возможно, это будет исправлено в новых версиях компилятора.



Источник: Jon Skeet “C# In Depth”. 4th ed – Manning Publications Co, 2019. Глава 11.