Примесь не обязательно означает плохой код. Многим людям легко и полезно использовать побочные эффекты для решения проблемы. Сначала нужно знать, как сделать это чисто, чтобы вы знали, когда подходит примесь:).
.NET не имеет понятия чистоты в системе типов, поэтому «чистый» метод, который принимает произвольные делегаты, всегда может быть нечистым, в зависимости от того, как он вызывается. Например, «Где», или «фильтр», обычно считается чистой функцией, поскольку она не изменяет свои аргументы и не изменяет глобальное состояние.
Но ничто не мешает вам поместить такой код в аргумент Where. Например:
things.Where(x => { Console.WriteLine("um?");
return true; })
.Count();
Так что это определенно нечистое использование Where. Перечислимые могут делать все, что они хотят, когда они повторяются.
Ваш код плох? Нет. Использование цикла foreach столь же «нечисто» - вы все еще модифицируете исходные объекты. Я пишу такой код все время. Соедините несколько элементов select, filter и т. Д., Затем выполните ForEach для вызова некоторой работы. Вы правы, это чище и проще.
Пример: ObservableCollection. У него нет метода AddRange по какой-то причине. Итак, если я хочу добавить к этому кучу вещей, что мне делать?
foreach(var x in things.Where(y => y.Foo > 0)) { collection.Add(x)); }
или
things.Where(x => x.Foo > 0).ForEach(collection.Add);
Я предпочитаю второй. Как минимум, я не вижу, как это может быть истолковано как худшее, чем первый способ.
Когда это плохой код? Когда это делает побочный эффект кода в месте, которое не ожидается. Это случай для моего первого примера с использованием Где. И даже тогда бывают случаи, когда область действия очень ограничена и использование очевидно.
Цепочка ForEach
Я написал код, который делает подобные вещи. Чтобы избежать путаницы, я бы дал ему другое имя. Основная путаница заключается в том, «это сразу оценивают или лень?». ForEach подразумевает, что он сразу же выполнит цикл. Но что-то, возвращающее IEnumerable, подразумевает, что элементы будут обрабатываться по мере необходимости. Поэтому я бы предложил дать ему другое имя («Process», «ModifySeq», «OnEach» ... как-то так) и сделать его ленивым:
public static IEnumerable<T> OnEach(this IEnumerable<T> src, Action<T> f) {
foreach(var x in src) {
f(x);
yield return x;
}
}