Здравствуйте, tyomchick, Вы писали:
T>В общем есть необходимость выполнять массовое изменение элементов коллекции. T>Ну например элементы байтового массива перегнать в BCD или обратно.
Правильные хелперы всегда начинаются со сценариев использования. В общем случае разница между
for (int i = 2; i < 2 + 3; i++)
{
list[i] = SomeCode(list[i]);
}
// и
list.Update(2, 3, x => SomeCode(x));
очевидна, так что смысл использовать есть. Пара "стартовый индекс + количество" тоже абсолютно правильная, полный аналог Skip+Take. Вариант с стартовым/конечным индексом в API дотнета практически не используется. Неудобно.
Разумеется, если SomeCode раскрывается в длинное выражение или нужен отбор, break или continue — проще не страдать фигнёй и запустить обычный for.
Переписать в любой из вариантов дело 5 минут, так что сильно заморачиваться "правильно/неправильно" тут не стоит.
Пара замечаний:
1. Я бы поменял имя метода на Update. Transform обычно используется как "чистая" функция, которая возвращает новый результат. Вариант с Replace тоже неплох.
2. Неплохо бы добавить дебаг-ассерты для индексов и для проверки на null. Всегда лучше, когда ошибка всплывает сразу, а не в середине перебора.
Ну и на отрицательный count ассерт нужен в любом случае, это баг. И checked() на вычисление endIndex. Я бы добавил ассерт и на count == 0 (или возвращал бы false, если перебор не удался). Если окажется, что большинство случаев с count==0 — не ошибка, всегда можно будет убрать.
Здравствуйте, hardcase, Вы писали:
H>Мне вот очевиднее, что for версия уместнее — она замыкания не будет порождать.
Угу. Два но:
* Как дойдёт до "лямбды тормозят", переписать — 5 минут.
* И до этого ещё дожить надо.
S>Пара замечаний: S>1. Я бы поменял имя метода на Update. Transform обычно используется как "чистая" функция, которая возвращает новый результат. Вариант с Replace тоже неплох.
Да, пожалуй Update более удачное название.
S>2. Неплохо бы добавить дебаг-ассерты для индексов и для проверки на null. Всегда лучше, когда ошибка всплывает сразу, а не в середине перебора.
Это решается атрибутом [NotNull], решарперским анализатором, и административным запретом на комиты с решарперскими варнингами.
S>Ну и на отрицательный count ассерт нужен в любом случае, это баг. И checked() на вычисление endIndex.
Да, вы правы, но наверное я всё таки не дебажные ассерты, а исключения ArgumentOutOfRangeException кидать буду.
S>Я бы добавил ассерт и на count == 0 (или возвращал бы false, если перебор не удался). Если окажется, что большинство случаев с count==0 — не ошибка, всегда можно будет убрать.
Думаю всё же не буду. Вроде бы все функции что я знаю, лояльно относятся к нулевой длине, не хотелось бы рвать шаблон.
Даже самую простую задачу можно сделать невыполнимой, если провести достаточное количество совещаний
Здравствуйте, tyomchick, Вы писали:
T>В общем есть необходимость выполнять массовое изменение элементов коллекции. T>Ну например элементы байтового массива перегнать в BCD или обратно.
T>Породил такие хелперы: T>Но чувствую, что нарушаю какие то концепции, чем могу ввести в заблуждение пользователей библиотеки.
Можно заменить хелперы на врапперы:
public static IReadonlyList<T> Transform<T>([NotNull] this IList<T> self, [NotNull] Func<T, T> transformFunc)
{
return new TransformedList(self, transformFunc);
}
Вызов transform будет выполняться за константное время, не надо думать что произойдет если трасформировать один и тот же список дважды.
Если у Вас нет паранойи, то это еще не значит, что они за Вами не следят.
Здравствуйте, Sinix, Вы писали:
S>Угу. Два но: S>* Как дойдёт до "лямбды тормозят", переписать — 5 минут. S>* И до этого ещё дожить надо.
Против твоего подхода есть одно "но" — а нафига козе баян? Чтобы не писать цикл в три строчки кода? Или есть какие-то более ощутимые бенефиты?
Здравствуйте, tyomchick, Вы писали:
T>Может подскажете?
В C++ STL есть подобный алгоритм std::transform — отличие от твоего в том что ещё есть возможность выбрать куда записывать трансформированные значения (а не только self), работает для итераторов начиная от Input/Output (то есть single pass, а не random access) и возвращает output iterator (если под ним single pass/forward, чтобы знать откуда дальше продолжать).
Плюс в Boost есть обвёртки с меньшим числом параметров (не нужно писать begin/end для типичных случаев).
Здравствуйте, koandrew, Вы писали:
K>Против твоего подхода есть одно "но" — а нафига козе баян? Чтобы не писать цикл в три строчки кода? Или есть какие-то более ощутимые бенефиты?
* Такой же интерфейс имеет parallel_transform (например в Microsoft PPL, libstdc++ parallel) — то есть легко распараллелить при необходимости.
* Внутри могут быть оптимизации которые заняли бы не три строчки, типа loop unroll.
* При чтении кода когда видишь transform — то сразу понятно что там происходит, не нужно вчитываться что же там именно итерируется и нет ли там внутри изменения индекса или какого-нибудь break/return/continue.
* Цикломатическая сложность кода уменьшается.
* Намного проще решать/обсуждать/обдумывать задачу в терминах готовых алгоритмов, а не голых циклов. Sean Parent в своей презентации даже даёт такой guideline — no raw loops: http://www.youtube.com/watch?v=qH6sSOr-yk8
Здравствуйте, koandrew, Вы писали:
K>Против твоего подхода есть одно "но" — а нафига козе баян? Чтобы не писать цикл в три строчки кода? Или есть какие-то более ощутимые бенефиты?
Это топикстартера подход, я только про "дизайн API" отвечал
Если такая штука встречается по коду десятки раз, то это повод выбросить и переписать вытащить однотипный код в хелпер.
С моей точки зрения — тут поиск приключений на ровном месте. Т.к. такие одномоментные хелперы при долгой жизни проекта мутируют в убер-фреймворки, а их лучше убивать, пока они маленькие.