“foreach” или “ForEach”
Многие люди спрашивают меня – почему Microsoft не предоставила для последовательностей метод расширения ForEach. У класса List, конечно, уже есть такой метод, но непонятно, почему бы не создать его как метод расширения для любых последовательностей. Это практически одна строка:
public static void ForEach<T>(this IEnumerable<T> sequence, Action<T> action)
{ // проверка аргумента на null пропущена
foreach(T item in sequence) action(item);
}
Мой обычный ответ на «почему возможность X не реализована» в том, что, конечно же, никакие возможности не реализованы до тех пор, пока кто-то не спроектирует, не реализует, не протестирует, не задокументирует и не выпустит их, и пока что никто не выделил на это денег. Ну да, хотя я, как известно, высказывался насчёт того, что даже маленькие возможности могут дорого стоить, конкретно эта очень проста, очевидно корректна, ее легко протестировать и задокументировать. Затраты всегда важны, но затраты на эту в действительности весьма невелики.
Конечно, это работает в обе стороны. Если это так легко и дёшево, то вы можете сделать всё сами, если нужно.
На самом деле, главное – не низкие затраты, а чистая прибыль. Как мы увидим, преимущества здесь также очень малы, поэтому чистая прибыль может на практике оказаться отрицательной.
Но мы можем копнуть чуть глубже. Я философски против предоставления такого метода по двум причинам.
Во-первых, это нарушило бы принципы функционального программирования, на которых основаны все остальные операторы на последовательностях. Очевидно, что единственная цель вызова этого метода состоит в достижении побочных эффектов.
Цель выражения ( expression ) – вычислить значение, а не произвести побочный эффект. Цель оператора ( statement ) – вызвать побочный эффект. Место вызова этой штуки будет выглядеть весьма похоже на выражение (хотя, признаться, поскольку метод ничего не возвращает, выражение может использоваться только в контексте «операторного выражения» (statement expression)).
Мне как-то не нравится идея сделать единственный оператор для последовательностей, который полезен только для спецэффектов.
Во-вторых, такая возможность ничего не добавляет к выразительности языка. Она всего лишь позволяет вам переписать вот этот идеально понятный код:
foreach(Foo foo in foos){ какой-то оператор с foo; }
в таком виде:
foos.ForEach((Foo foo)=>{ какой-то оператор с foo; });
где использованы почти те же символы в немножко другом порядке. И вторую версию даже труднее понять и отладить, а еще она добавляет семантику замыкания, потенциально внося тонкие изменения в жизненные циклы объектов.
Когда мы предоставляем два почти одинаковых способа сделать в точности одну вещь, мы вносим сумятицу в индустрию, мы затрудняем людям чтение кода друг друга, и так далее. Иногда дополнительные преимущества от наличия двух различных текстовых представления одной операции (вроде query expressions и прямых вызовов методов, или + и String.Concat) настолько огромны, что они стоят потенциальной путаницы. Но решающее преимущество query expressions – их читаемость; новая форма «foreach» читается заведомо не лучше «нормальной» формы, а возможно, и хуже.
Если вы не согласны с этими философскими возражениями и находите в этом приёме практическую ценность, определенно не сдерживайте себя и напишите эту тривиальную строчку сами.