Здравствуйте, karbofos42, Вы писали:
K>Люди для List вместо проверки Count вызывают метод Any(). Вместо нулевого элемента запрашивают First().
Рекомендую поизучать исходники Linq.
public static bool Any<TSource>(this IEnumerable<TSource> source)
{
if (source == null)
{
ThrowHelper.ThrowArgumentNullException(ExceptionArgument.source);
}
if (source is ICollection<TSource> collectionoft)
{
return collectionoft.Count != 0;
}
else if (source is IIListProvider<TSource> listProv)
{
// Note that this check differs from the corresponding check in
// Count (whereas otherwise this method parallels it). If the count
// can't be retrieved cheaply, that likely means we'd need to iterate
// through the entire sequence in order to get the count, and in that
// case, we'll generally be better off falling through to the logic
// below that only enumerates at most a single element.int count = listProv.GetCount(onlyIfCheap: true);
if (count >= 0)
{
return count != 0;
}
}
else if (source is ICollection collection)
{
return collection.Count != 0;
}
using (IEnumerator<TSource> e = source.GetEnumerator())
{
return e.MoveNext();
}
}
С First примерно та же ситуация.
Если нам не помогут, то мы тоже никого не пощадим.
Здравствуйте, Shmj, Вы писали:
S>Теперь можно писать и так и эдак:
S>Не кажется ли это внедрением бардака? В одном месте будут писать так в другом эдак.
Здравствуйте, IT, Вы писали:
IT>Рекомендую поизучать исходники Linq.
Так я видел и в курсе.
Разве нет разницы между items[0] и items.First() в плане, что во втором случае добавляется проверка типа коллекции?
Вроде мелочь, а вроде лишняя работа и лишнее ветвление непонятно зачем.
Last() хоть короче и понятнее записывается, чем items[items.Count — 1], а с First вообще одни минусы, как по мне.
Здравствуйте, Aquilaware, Вы писали:
A>Можно даже захватить походящее значение в переменную для дальнейшего использования:
A>
A>if (GetIQ() is < 100 and > 50 and var iq)
A> Console.WriteLine("Hoi polloi IQ: {0}", iq);
A>
А что будет, если написать or var iq?
Почему не запись типа: A>
A>if (var iq = GetIQ() is < 100 and > 50)
A> Console.WriteLine("Hoi polloi IQ: {0}", iq);
A>
в язык не вписалось?
Возможность сама вроде норм, но выглядит то убого и чужеродно
A>Теперь вы видите?
Я лично тут вижу, что кто-то написал кривые методы, которые неудобно использовать и в результате вокруг них идут какие-то костыли.
Что мешало написать так:
if (TryGetBicycle(out var bicycle))
...
else if (TryGetMotorcycle(out var motorcycle))
...
else
...
Здравствуйте, Слава, Вы писали:
С>Представьте список транзакций, к каждой из которых нужно добавить набор разрешённых действий. У транзакции есть набор признаков, штук 15. С>Сейчас это сделано в виде 400* строк вложенных if'ов, и одно и то же действие может быть добавлено в нескольких местах этой скомканной простыни. Разбираться в этом очень тяжело.
PM уважаю, как и функциональный подход в принципе, но в данном случае вопрос: а нельзя ли как-то это всё «раскодировать» (перевести из кода во что-то другое)? Или в DSL, или в data-файл. Я бы в эту сторону подумал.
Здравствуйте, Aquilaware, Вы писали:
A>Вместо такого ужаса теперь посмотрим на произведение исскуства, на картину Художника:
A>
A>if (TryGetBicycle() is not null and var bicycle)
A> Console.WriteLine("Bicycle owner is {0}.", bicycle.Owner);
A>else if (TryGetMotorcycle() is not null and var motorcycle)
A> Console.WriteLine("Motorcycle owner is {0}.", motorcycle.Owner);
A>else if (TryGetCar() is not null and var car)
A> Console.WriteLine("Car owner is {0}.", car.Owner);
A>else
A> // ... красота на любом уровне "вложенности"! ...
A>
Для произведения искусства тут слишком много копипасты. Как по мне, я бы сделал список типов (Bicycle, Motorcycle, Car) и проверял соответствие каждому, а при выводе в консоль использовал бы метаданные (имя типа или назначенная ему человекочитаемая строка). А если бы набор TryGetX() был внешним и не поддавался переписыванию, сделал бы список хотя бы методов (TryGetBicycle, TryGetMotorcycle, TryGetCar).
Здравствуйте, Serginio1, Вы писали:
S>Угу если источник будет меняться, то получишь, что угодно. Суть понимать с чем имеешь дело.
Ну, вот и у linq я вижу варианты использования и это нужная вещи, а у этого кривого синтаксического сахара — нет.
Возможно где-то его использование и оправдано и у меня просто таких задач не было.
Предвижу, что раньше люди боялись объёмных ветвлений и начинали задумываться над архитектурой, переписывая нормально, что такие проверки становились в принципе не нужны.
Теперь объёмные проверки можно легко и коротко записывать, поэтому чего бы дублирующих условий везде не напихать и плевать как это скажется на производительности и сколько лишнего будет код делать.
То, что предлагаете вы — это никак не ровные методы, это исторический костыль. Проблема с этим костылем в том, что нельзя сделать вот так:
var vehicle = TryGetBicycle() ?? TryGetMotorcycke() ?? TryGetCar();
А нужно как всегда извращаться в упражнении рука-писать. Это прям как в Java, где нет out и ref параметров функций, и поэтому люди вынуждены придумывать свои исторические костыли.
Зачем, если всё это можно сделать средствами языка? Например, был бы полноценный Optional<T> или уже сам тип данных подразумевает null значение.
Lisp — это святое. Кстати, если бы топик стартер поупражнялся в нем, то он бы не создавал подобных тем, так как Lisp человеку помагает осознать саму материю языка. Прикоснуться к ядру компьютерной и математической вселенной, к тому, с чего начинается само понятие вычисления.
Поэтому задание для топик стартера — это написать своими руками интерпретатор Scheme (нормализованного диалекта Lisp) и в процессе прочувствовать это всё. После такого опыта дороги назад уже не будет, настолько сильным будет его влияение на сознание и на понимание происходящих процессов.
Рекомендуется всем, кто не ещё пробовал. Но конечно же, большая часть людей проигнорирует эту возможность по разным причинам. Кто-то закатит глаза со словами "опять эта функциональщина, а мне тут формы шлёпать надо", у кого-то не будет времени из-за занятости, а кто-то вообще не поймет, что тут такого особенного происходит.
Здравствуйте, Aquilaware, Вы писали:
A>То, что предлагаете вы — это никак не ровные методы, это исторический костыль. Проблема с этим костылем в том, что нельзя сделать вот так:
не костыль, а try-parse паттерн же
A>А нужно как всегда извращаться в упражнении рука-писать.
ну, для предыдущего варианта подвезли pattern-matching, могли и для этого что-то в язык тогда добавить, чтобы удобнее было использовать
Здравствуйте, karbofos42, Вы писали: K>Так я видел и в курсе. K>Разве нет разницы между items[0] и items.First() в плане, что во втором случае добавляется проверка типа коллекции?
Если вас действительно интересует такой вопрос — не поленитесь и посмотрите дизассемблером.
Микроскопические методы типа First прекрасно инлайнятся, а проверки типа в случаях, когда тип известен (то есть как раз там, где можно безопасно написать items[0]), устраняются JIT-ом.
А если этот вопрос вас не беспокоит, то старайтесь писать понятный код, дружественный к модификациям. items.First() точнее выражает намерение разработчика и дружелюбнее к будущей смене типа коллекции. K>Вроде мелочь, а вроде лишняя работа и лишнее ветвление непонятно зачем. K>Last() хоть короче и понятнее записывается, чем items[items.Count — 1], а с First вообще одни минусы, как по мне.
Пока что минусов не видно.
Уйдемте отсюда, Румата! У вас слишком богатые погреба.
Здравствуйте, Shtole, Вы писали:
S>PM уважаю, как и функциональный подход в принципе, но в данном случае вопрос: а нельзя ли как-то это всё «раскодировать» (перевести из кода во что-то другое)? Или в DSL, или в data-файл. Я бы в эту сторону подумал.
Для этого нужен отбеливатель и шуруповёрт с набором саморезов. В отбеливатель засунуть исполнительных копчёных сотрудников, а шуруповёртом закрутить саморезы в головы белым энергичным сотрудникам. А без этого они так и будут тикеты создавать и закрывать.
Здравствуйте, Sinclair, Вы писали: S>Если вас действительно интересует такой вопрос — не поленитесь и посмотрите дизассемблером. S>Микроскопические методы типа First прекрасно инлайнятся, а проверки типа в случаях, когда тип известен (то есть как раз там, где можно безопасно написать items[0]), устраняются JIT-ом.
Посмотрел через ILSpy, посмотрел на sharplab, вызов метода First никуда не делся.
Я не силён в устройстве компилятора и т.п. Метод First точно может инлайниться? Он вроде в другой сборке лежит, а не в моём проекте, такое инлайнится? S>А если этот вопрос вас не беспокоит, то старайтесь писать понятный код, дружественный к модификациям. items.First() точнее выражает намерение разработчика и дружелюбнее к будущей смене типа коллекции.
Ну, да. Мне тут намедни таск под ускорение метода прилетел. Изменил 1 строчку и получил ускорение в десяток раз.
Кто-то взял List и для него активно вызывал методы Remove и Contains. Простая замена на HashSet дала достаточный прирост скорости.
Только пришлось всё равно пробежать по коду и убедиться, что там не важен порядок элементов и я сменой коллекции ничего не нарушу.
Если бы мне пришлось попутно поменять [0] на .First(), то вот вообще не переживал бы.
А я мог и не посмотреть и не думать об особенностях и различиях обеих коллекций.
Засунул бы HashSet бездумно туда, где важен порядок элементов, лови потом весёлый баг. S>Пока что минусов не видно.
Давно хотел и вот решил под такое дело таки попробовать BenchmarkDotNet.
Код теста
public class Benchmark
{
const int retry = 10;
List<int> _items;
public Benchmark()
{
_items = new List<int>(1);
_items.Add(1);
}
[Benchmark]
public bool ByAny()
{
bool result = false;
for (int i = 0; i < retry; ++i)
{
result = _items.Any();
}
return result;
}
[Benchmark]
public bool ByCount()
{
bool result = false;
for (int i = 0; i < retry; ++i)
{
result = _items.Count != 0;
}
return result;
}
[Benchmark]
public int ByIndex()
{
int result = 0;
for (int i = 0; i < retry; ++i)
{
result = _items[0];
}
return result;
}
[Benchmark]
public int ByFirst()
{
int result = 0;
for (int i = 0; i < retry; ++i)
{
result = _items.First();
}
return result;
}
}
Здравствуйте, karbofos42, Вы писали: K>Посмотрел через ILSpy, посмотрел на sharplab, вызов метода First никуда не делся.
Это очень интересно. Действительно, почему-то вызов остаётся. K>Я не силён в устройстве компилятора и т.п. Метод First точно может инлайниться? Он вроде в другой сборке лежит, а не в моём проекте, такое инлайнится?
Да, такое должно инлайниться. Почему этого не происходит — отдельный вопрос.
Будет время — я посмотрю логи джита. K>Ну, да. Мне тут намедни таск под ускорение метода прилетел. Изменил 1 строчку и получил ускорение в десяток раз. K>Кто-то взял List и для него активно вызывал методы Remove и Contains. Простая замена на HashSet дала достаточный прирост скорости.
И это — очень типичная ситуация. Примерно так и выглядит оптимизация шарпа в 99% случаев. K>Только пришлось всё равно пробежать по коду и убедиться, что там не важен порядок элементов и я сменой коллекции ничего не нарушу.
В идеальном случае пробежка выполняется при помощи перепрогона тестов. Если тестов мало — то да, надо смотреть глазами.
Хотя это менее надёжно — мало ли, что где можно посмотреть. K>Если бы мне пришлось попутно поменять [0] на .First(), то вот вообще не переживал бы. K>А я мог и не посмотреть и не думать об особенностях и различиях обеих коллекций. K>Засунул бы HashSet бездумно туда, где важен порядок элементов, лови потом весёлый баг.
K>| Method | Mean | Error | StdDev | K>|-------- |-----------:|----------:|----------:| K>| ByAny | 47.657 ns | 0.3115 ns | 0.2761 ns | K>| ByCount | 3.235 ns | 0.0304 ns | 0.0285 ns | K>| ByIndex | 6.913 ns | 0.0838 ns | 0.0784 ns | K>| ByFirst | 108.889 ns | 1.4484 ns | 1.3549 ns | K>[/code] K>[/cut]
K>Я что-то не так сделал или всё же разница в разы есть?
Вы всё сделали верно. Снимаю шляпу. На досуге поразбираюсь, откуда такой провал в производительности.
Уйдемте отсюда, Румата! У вас слишком богатые погреба.
Здравствуйте, Aquilaware, Вы писали:
A>Поэтому задание для топик стартера — это написать своими руками интерпретатор Scheme (нормализованного диалекта Lisp) и в процессе прочувствовать это всё. После такого опыта дороги назад уже не будет, настолько сильным будет его влияение на сознание и на понимание происходящих процессов.
A>Рекомендуется всем, кто не ещё пробовал. Но конечно же, большая часть людей проигнорирует эту возможность по разным причинам. Кто-то закатит глаза со словами "опять эта функциональщина, а мне тут формы шлёпать надо", у кого-то не будет времени из-за занятости, а кто-то вообще не поймет, что тут такого особенного происходит.
Я писал. Компилятор даже. Нет, не понял, что там особого. Ничего особого в схеме нет. Убогий игрушечный недоязычок, даже до джаваскрипта не дотягивает. А уж интерпретатор — там и вовсе ничего интересного. С компилятором там хоть какие-то вопросы возникают в голове.
Вот хаскель — да. Там ничего писать не надо. Просто сидеть и учить, фича за фичей. Подбирая выпадающий мозг.
Здравствуйте, Sinclair, Вы писали:
K>>Last() хоть короче и понятнее записывается, чем items[items.Count — 1], а с First вообще одни минусы, как по мне. S>Пока что минусов не видно.
А бывает еще смешней: в .net7 linq методы кое-где используют векторизацию и могут работать в 10-ки раз быстрее, чем очевидная реализация руками:
Здравствуйте, Sinclair, Вы писали:
S>Здравствуйте, karbofos42, Вы писали: K>>Посмотрел через ILSpy, посмотрел на sharplab, вызов метода First никуда не делся. S>Это очень интересно. Действительно, почему-то вызов остаётся.
З.Ы. Посмотрел врукопашную, взяв исходник из Core https://github.com/dotnet/runtime/blob/80b7a657ee1471f20fd23f228b2576c0dbe14789/src/libraries/System.Linq/src/System/Linq/First.cs
Применение AggessiveInlining на First и TryGetFirst помогает встроить код, но в итоге почему-то всё равно остаются вызовы.
Похоже, основные оптимизации JIT попали на value-типы, а для референс-типов вроде List<T> устраняется не вся возможная косвенность.
Уйдемте отсюда, Румата! У вас слишком богатые погреба.
A>var bicycle = TryGetBicycle();
A>if (bicycle != null)
A> Console.WriteLine("Bicycle owner is {0}.", bicycle.Owner);
A>
A>это теперь можно делать в одну строку:
A>if (TryGetBicycle() is not null and var bicycle)
A> Console.WriteLine("Bicycle owner is {0}.", bicycle.Owner);
A>
A>Почему такая возможность важна? Потому что иногда это безумно удобно во вложенных if'ах, так как позволяет обойтись одним уровнем вложенности вместо плохо читаемой "ёлочки" c углублениями.