2008-09-19 5 views
296

Вдохновленные еще один вопрос с просьбой о пропавшем Zip функции:Почему в IEnumerable нет метода расширения ForEach?

Почему не существует никакого метода ForEach расширения в Enumerable классе? Или где угодно? Единственным классом, который получает метод ForEach, является List<>. Есть ли причина, по которой она отсутствует (производительность)?

ответ

162

Существует уже предложение foreach, включенное в язык, который выполняет большую часть работы.

Я не хотел бы видеть следующее:

list.ForEach(item => 
{ 
    item.DoSomething(); 
}); 

Вместо:

foreach(Item item in list) 
{ 
    item.DoSomething(); 
} 

Последнее понятнее и легче читать в большинстве ситуации, хотя, возможно, немного больше времени, тип.

Однако я должен признать, что я изменил свою позицию по этой проблеме; метод расширения ForEach() действительно будет полезен в некоторых ситуациях.

Вот основные различия между утверждением и метода:

  • Тип проверки: (! Big Plus) Еогеасп делается во время выполнения, ForEach() является время компиляции
  • Синтаксис для вызова делегат действительно намного проще: objects.ForEach (DoSomething);
  • ForEach() может быть прикован цепью: хотя злобность и полезность такой функции открыты для обсуждения.

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

+0

Хмм, хорошая точка. Единственное, что я могу думать об этом, это тот факт, что List <> также имеет метод ForEach. К сожалению, я не могу придумать пример, когда использование ForEach было бы полезно синтаксически. – 2008-09-19 12:07:52

+0

Но что произойдет, если у вас уже есть делегат? :) – leppie 2008-09-19 12:08:11

+1

leppie: вы вызываете делегата в foreach :) – Coincoin 2008-09-19 12:29:07

2

Большинство методов расширения LINQ возвращают результаты. ForEach не вписывается в этот шаблон, поскольку он ничего не возвращает.

+2

ForEach использует Action , что является еще одной формой делегата – 2008-09-19 11:59:55

+0

За исключением права leppie, все методы Enumerable extension возвращают значение, поэтому ForEach не соответствует шаблону. Делегаты этого не делают. – 2008-09-19 12:02:01

+0

Просто, чтобы метод расширения не возвращал результат, он служит для добавления способа вызова часто используемой операции – 2008-09-19 12:39:48

62

Метод ForEach был добавлен до LINQ. Если вы добавите расширение ForEach, он никогда не будет вызван для экземпляров List из-за ограничений методов расширения. Я думаю, что причина, по которой он не был добавлен, - это не вмешательство в существующее.

Однако, если вы действительно пропустить эту маленькую удобную функцию, вы можете раскрутить свой вариант

public static void ForEach<T>(
    this IEnumerable<T> source, 
    Action<T> action) 
{ 
    foreach (T element in source) 
     action(element); 
} 
+8

Это позволяет использовать методы расширения. Если уже существует реализация экземпляра данного имени метода, то этот метод используется вместо метода расширения. Таким образом, вы можете реализовать метод расширения ForEach и не сталкиваться с ним с методом List <>. ForEach. – 2008-09-19 11:57:23

+1

Камерон, поверь мне, я знаю, как работают методы расширения :) Список наследует IEnumerable, поэтому это будет неочевидная вещь. – aku 2008-09-19 12:02:07

+6

В Руководстве по программированию расширения (http://msdn.microsoft.com/en-us/library/bb383977.aspx): Метод расширения с тем же именем и сигнатурой, что и интерфейс или метод класса, никогда не будет вызван. Кажется довольно очевидным для меня. – 2008-09-19 12:11:39

10

Я всегда задавался вопросом, что я, именно поэтому я всегда ношу это со мной:

public static void ForEach<T>(this IEnumerable<T> col, Action<T> action) 
{ 
    if (action == null) 
    { 
     throw new ArgumentNullException("action"); 
    } 
    foreach (var item in col) 
    { 
     action(item); 
    } 
} 

Nice little extension способ.

+1

col может быть пустым ... вы также можете это проверить. – 2008-09-19 12:24:59

2

Если у вас есть F # (который будет в следующей версии .NET), вы можете использовать

Seq.iter doSomething myIEnumerable

2

Является ли это мне или это список <T> .Foreach в значительной степени устарели по Linq. Первоначально был

foreach(X x in Y) 

где Y просто должны были быть IEnumerable (Pre 2,0), и реализовать GetEnumerator(). Если вы посмотрите на MSIL генерироваться вы можете увидеть, что это точно так же, как и

IEnumerator<int> enumerator = list.GetEnumerator(); 
while (enumerator.MoveNext()) 
{ 
    int i = enumerator.Current; 

    Console.WriteLine(i); 
} 

(см http://alski.net/post/0a-for-foreach-forFirst-forLast0a-0a-.aspx для MSIL)

Тогда в DotNet2.0 Обобщения пришел и список. Foreach всегда считал меня реализацией шаблона Vistor (см. «Образцы дизайна» Гамма, Хелм, Джонсон, Влиссидес).

Теперь, конечно, в 3.5 мы можем использовать вместо лямбда к тому же эффекту, для примера попробуйте http://dotnet-developments.blogs.techtarget.com/2008/09/02/iterators-lambda-and-linq-oh-my/

6

Так было много комментариев по поводу того, что метод расширения ForEach не подходит потому что он не возвращает значение, подобное методам расширения LINQ. Хотя это фактическое утверждение, это не совсем так.

Методы расширения LINQ делать все возвращают значение, так что они могут быть соединены друг с другом:

collection.Where(i => i.Name = "hello").Select(i => i.FullName); 

Однако, только потому, что LINQ реализован с использованием методов расширения не означает, что методы расширения должны быть использованы в то же самое и вернуть значение. Написание метода расширения для раскрытия общей функциональности, которая не возвращает значение, является вполне допустимым использованием.

Удельная arguement о ForEach является то, что, на основе ограничений на методах расширения (а именно, что метод расширения не будет никогда переопределять наследуемый метод с же подписью), может возникнуть ситуация, когда пользовательские расширения метод доступен для всех классов, которые impelement IEnumerable <T> кроме списка <T>. Это может вызвать путаницу, когда методы начинают вести себя по-разному в зависимости от того, вызван ли метод расширения или метод наследования.

2

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

15

Хотя я согласен, что лучше использовать встроенный в foreach конструкции в большинстве случаев, я считаю использование этой вариации на ForEach <> расширение, чтобы быть немного лучше, чем того, чтобы управлять индексом в регулярных foreach себе:

public static int ForEach<T>(this IEnumerable<T> list, Action<int, T> action) 
{ 
    if (action == null) throw new ArgumentNullException("action"); 

    var index = 0; 

    foreach (var elem in list) 
     action(index++, elem); 

    return index; 
} 
Пример
var people = new[] { "Moe", "Curly", "Larry" }; 
people.ForEach((i, p) => Console.WriteLine("Person #{0} is {1}", i, p)); 

даст вам:

Person #0 is Moe 
Person #1 is Curly 
Person #2 is Larry 
25

The discussion here дает ответ:

На самом деле, конкретное обсуждению я видел в самом деле шарнир над функциональной чистотой. В выражении часто высказываются предположения о отсутствии побочных эффектов.Наличие ForEach специально вызывает побочные эффекты, а не просто смириться с ними. - Keith Farmer (Partner)

В основном принято решение поддерживать функциональность «чистых» методов расширения. ForEach будет поощрять побочные эффекты при использовании методов расширения Enumerable, что не было целью.

13

Одним из обходных путей является написать .ToList().ForEach(x => ...).

профи

Легко понять - читатель должен знать только то, что корабли с C#, а не какие-либо дополнительные методы расширения.

Синтаксический шум очень мягкий (только добавляет немного экстраординарного кода).

Обычно не стоит дополнительной памяти, так как родной .ForEach() должен был бы реализовать всю коллекцию, во всяком случае.

против

Порядок операций не является идеальным. Я предпочел бы реализовать один элемент, затем действовать на нем, а затем повторить. Этот код сначала реализует все элементы, а затем последовательно действует на них.

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

Если перечисление бесконечно (как натуральные числа), вам не повезло.

43

Вы могли бы написать этот метод расширения:

// Possibly call this "Do" 
IEnumerable<T> Apply<T> (this IEnumerable<T> source, Action<T> action) 
{ 
    foreach (var e in source) 
    { 
     action(e); 
     yield return e; 
    } 
} 

Pros

Позволяет цепочки:

MySequence 
    .Apply(...) 
    .Apply(...) 
    .Apply(...); 

Против

Это не будет на самом деле anyt пока вы не сделаете что-то, чтобы заставить итерацию. По этой причине его не следует называть .ForEach(). Вы можете написать .ToList() в конце, или вы могли бы написать этот метод расширения, тоже:

// possibly call this "Realize" 
IEnumerable<T> Done<T> (this IEnumerable<T> source) 
{ 
    foreach (var e in source) 
    { 
     // do nothing 
     ; 
    } 

    return source; 
} 

Это может быть слишком значительным отступлением от библиотек груза C#; читатели, которые не знакомы с вашими методами расширения, не будут знать, что делать с вашим кодом.

4

@Coincoin

Реальной сила методы расширения Еогеаспа предполагает повторное использование Action<> без добавления ненужных методов коды. Скажем, что у вас есть 10 списков, и вы хотите выполнить с ними одну и ту же логику, и соответствующая функция не вписывается в ваш класс и не используется повторно. Вместо того, чтобы иметь десять для циклов или общую функцию, которая, очевидно, является помощником, который не принадлежит, вы можете сохранить всю свою логику в одном месте (Action<>.Так, десятки строк заменяются с

Action<blah,blah> f = { foo }; 

List1.ForEach(p => f(p)) 
List2.ForEach(p => f(p)) 

и т.д ...

Логика в одном месте, и вы не загрязняется свой класс.

1

Никто еще не указал, что ForEach <T> приводит к тому, что тип времени компиляции проверяет, где проверено время выполнения foreach.

Проделав некоторый рефакторинг, в котором оба метода были использованы в коде, я пользуюсь .ForEach, поскольку мне приходилось выискивать сбои тестирования/сбои в работе, чтобы найти проблемы с foreach.

3

В 3.5, добавлены все методы расширения to IEnumerable существуют для поддержки LINQ (обратите внимание, что они определены в классе System.Linq.Enumerable). В этой статье я объясню, почему Еогеасп не принадлежит в LINQ: Existing LINQ extension method similar to Parallel.For?

5

Вы можете использовать (змеевидные, но лениво оценивал) Select, первый делать свою работу, а затем возвращая личность (или что-то другое, если вы предпочитаете)

IEnumerable<string> people = new List<string>(){"alica", "bob", "john", "pete"}; 
people.Select(p => { Console.WriteLine(p); return p}); 

Вам нужно будет убедиться, что он по-прежнему оценивается, либо с Count() (самыми дешевыми операциями для перечисления AFAIK) или другой операция, вам необходимо в любом случае.

Я хотел бы, чтобы он принес в стандартной библиотеке, хотя:

static IEnumerable<T> WithLazySideEffect(this IEnumerable<T> src, Action<T> action) { 
    return src.Select(i => { action(i); return i}); 
} 

Приведенный выше код становится people.WithLazySideEffect(p => Console.WriteLine(p)), которое эффективно эквивалентно Еогеасп, но ленивым и цепной.

1

Я хотел бы расширить Aku's answer.

Если вы хотите вызвать метод с единственной целью, это побочный эффект без перебора всех перечислимого первым вы можете использовать это:

private static IEnumerable<T> ForEach<T>(IEnumerable<T> xs, Action<T> f) { 
    foreach (var x in xs) { 
     f(x); yield return x; 
    } 
} 
0

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

Однако при желании загрузить (используя ToList()), вы можете загрузить свой список в память и воспользоваться функцией ForEach.

Смежные вопросы