Каков наилучший способ перебора строго типизированного универсального списка ?

Каков наилучший способ перебора строго типизированного универсального списка в C # .NET и VB.NET?

18.08.2008 20:49:22
7 ОТВЕТОВ
РЕШЕНИЕ

Для C #:

foreach(ObjectType objectItem in objectTypeList)
{
    // ...do some stuff
}

Ответ для VB.NET от Purple Ant :

For Each objectItem as ObjectType in objectTypeList
    'Do some stuff '
Next
30
21.04.2010 18:07:30
Так как это сильно типизированных, вы также можете использовать: foreach(var item in itemlist).
Steven Sudit 21.04.2010 18:05:50

Возможно, я что-то упускаю, но итерация общего списка должна быть довольно простой, если вы используете мои примеры ниже. Класс List <> реализует интерфейсы IList и IEnumerable, так что вы можете легко перебирать их практически любым способом.

Наиболее эффективным способом было бы использовать цикл for:

for(int i = 0; i < genericList.Count; ++i) 
{
     // Loop body
}

Вы также можете использовать цикл foreach:

foreach(<insertTypeHere> o in genericList)
{
    // Loop body
}
1
18.08.2008 20:54:56
Нет необходимости <insertTypeHere>. Компилятор сделает это за вас var.
Steven Sudit 21.04.2010 18:14:36

Для VB.NET:

For Each tmpObject as ObjectType in ObjectTypeList
    'Do some stuff '
Next

4
18.08.2008 21:01:07

Не зная внутренней реализации списка, я думаю, что в общем случае наилучшим способом его перебора будет цикл foreach. Поскольку foreach использует IEnumerator для обхода списка, сам список определяет, как перемещаться от объекта к объекту.

Если бы внутренняя реализация была, скажем, связанным списком, то простой цикл for был бы немного медленнее, чем foreach.

Имеет ли это смысл?

2
18.08.2008 21:06:49
Да, потому что связанный список будет нуждаться в линейном поиске для каждого нового элемента, в то время как итератор над ним займет всего один проход.
Steven Sudit 21.04.2010 18:13:56

Для любой общей реализации IEnumerable лучшим способом является:

//C#
foreach( var item in listVariable) {
    //do stuff
}

Однако есть важное исключение. IEnumerable включает в себя служебные данные Current () и MoveNext (), в которых фактически скомпилирован цикл foreach.

Когда у вас есть простой массив структур:

//C#
int[] valueTypeArray;
for(int i=0; i < valueTypeArray.Length; ++i) {
     int item = valueTypeArray[i];
     //do stuff
}

Это быстрее.


Обновить

После обсуждения с @Steven Sudit (см. Комментарии) я думаю, что мой первоначальный совет может быть устаревшим или ошибочным, поэтому я провел несколько тестов:

// create a list to test with
var theList = Enumerable.Range(0, 100000000).ToList();

// time foreach
var sw = Stopwatch.StartNew();
foreach (var item in theList)
{
    int inLoop = item;
}
Console.WriteLine("list  foreach: " + sw.Elapsed.ToString());

sw.Reset();
sw.Start();

// time for
int cnt = theList.Count;
for (int i = 0; i < cnt; i++)
{
    int inLoop = theList[i];
}
Console.WriteLine("list  for    : " + sw.Elapsed.ToString());

// now run the same tests, but with an array
var theArray = theList.ToArray();

sw.Reset();
sw.Start();

foreach (var item in theArray)
{
    int inLoop = item;
}
Console.WriteLine("array foreach: " + sw.Elapsed.ToString());

sw.Reset();
sw.Start();

// time for
cnt = theArray.Length;
for (int i = 0; i < cnt; i++)
{
    int inLoop = theArray[i];
}
Console.WriteLine("array for    : " + sw.Elapsed.ToString());

Console.ReadKey();

Итак, я запустил это в релизе со всеми оптимизациями:

list  foreach: 00:00:00.5137506
list  for    : 00:00:00.2417709
array foreach: 00:00:00.1085653
array for    : 00:00:00.0954890

И тогда отладка без оптимизаций:

list  foreach: 00:00:01.1289015
list  for    : 00:00:00.9945345
array foreach: 00:00:00.6405422
array for    : 00:00:00.4913245

Таким образом, это выглядит довольно непротиворечивым, forбыстрее чем foreachи массивы быстрее чем общие списки.

Однако это составляет 100 000 000 итераций, и разница между самым быстрым и самым медленным методами составляет около 0,4 секунды. Если вы не делаете огромные циклы, критичные к производительности, об этом не стоит беспокоиться.

20
23.04.2010 14:36:23
Вопрос был о List<T>, а не массивах. Несмотря на это, я не верю, что foreachв нативном массиве фактически используется IEnumerable, по крайней мере, не в оптимизированном коде, поэтому реального ускорения не будет. Фактически, мои тестовые шоу foreachзанимают всего три четверти времени for.
Steven Sudit 21.04.2010 18:04:41
@Steven Sudit - foreachциклы действительно страдают от forциклов после применения оптимизации компилятора. Однако я немного покопался - в случае с массивом foreachоптимизирован точно такой же IL, как и у for. Для List<T>компилятора не может использовать эту оптимизацию , и полученный IL немного медленнее. Похоже, мы оба не правы (._.)
Keith 22.04.2010 08:20:29
Я не просто говорил из теории. Я написал короткий тест и запустил его. Если вы хотите, я был бы рад выкопать и опубликовать его, чтобы вы могли увидеть, как это работает для вас.
Steven Sudit 23.04.2010 13:37:15
@ Стивен Судит - да, я тоже. Я обновил ответ с результатами.
Keith 23.04.2010 14:37:12
Позвольте мне поблагодарить вас за тяжелую работу, но я должен отметить, что в моих тестах было одно ключевое отличие: я убедился, что циклы передают каждый элемент внешнему методу. Без этого компилятор, скорее всего, полностью оптимизирует назначение. Я не знаю, объясняет ли это крошечное увеличение скорости, но это легко могло бы произойти.
Steven Sudit 26.04.2010 07:51:10

C #

myList<string>().ForEach(
    delegate(string name)
    {
        Console.WriteLine(name);
    });

В настоящее время анонимные делегаты не реализованы в VB.Net, но и C #, и VB.Net должны иметь возможность выполнять лямбды:

C #

myList<string>().ForEach(name => Console.WriteLine(name));

VB.Net

myList(Of String)().ForEach(Function(name) Console.WriteLine(name))

Как указал Грауэнольф, вышеприведенный VB не будет компилироваться, поскольку лямбда не возвращает значение. Обычный цикл ForEach, как предлагали другие, на данный момент является, пожалуй, самым простым, но, как обычно, для выполнения того, что C # может сделать в одной строке, требуется блок кода.


Вот банальный пример того, почему это может быть полезно: это дает вам возможность передавать логику цикла из другой области, отличной от той, в которой существует IEnumerable, так что вам даже не нужно показывать ее, если вы этого не хотите.

Допустим, у вас есть список относительных путей URL, которые вы хотите сделать абсолютными:

public IEnumerable<String> Paths(Func<String> formatter) {
    List<String> paths = new List<String>()
    {
        "/about", "/contact", "/services"
    };

    return paths.ForEach(formatter);
}

Тогда вы могли бы вызвать функцию следующим образом:

var hostname = "myhost.com";
var formatter = f => String.Format("http://{0}{1}", hostname, f);
IEnumerable<String> absolutePaths = Paths(formatter);

Предоставление вам "http://myhost.com/about", "http://myhost.com/contact"и т. Д. Очевидно, что в этом конкретном примере есть лучшие способы сделать это, я просто пытаюсь продемонстрировать основной принцип.

4
22.04.2010 02:08:53
Ваш код VB не будет работать. В этой версии VB поддерживает только анонимные функции, вам нужно подождать, пока в VB10 не появятся анонимные подпрограммы.
Jonathan Allen 9.09.2008 08:21:03
Вы правы, я не проверял это перед публикацией. Неудивительно, что документации для лямбда-выражений в VB так мало; они не так полезны.
Adam Lassek 18.09.2008 19:15:33
Очевидный вопрос: зачем возиться с обратными вызовами, когда вы можете просто сделать foreach?
Steven Sudit 21.04.2010 18:06:42
Вы предполагаете, что IEnumerable и делегат принадлежат к одной и той же области, но, несмотря на мой простой пример, они не должны быть такими. Возможность передавать замыкание в функцию из другой области может быть очень мощным шаблоном проектирования.
Adam Lassek 21.04.2010 18:18:26
Может быть, пример может помочь. И, пожалуйста, начните писать мне сообщения с @Steve, чтобы я получал уведомления.
Steven Sudit 21.04.2010 18:51:39

Это зависит от вашего приложения:

  • для цикла, если эффективность является приоритетом
  • цикл foreach или метод ForEach, в зависимости от того, что ваше намерение яснее
2
19.08.2008 02:01:14
foreach транслируется в вызовы GetEnumerator (), MoveNext () и Current (), что, очевидно, медленнее, чем приращение индексации и выбор из массива. Мы говорим о наносекундах, и, как правило, это не имеет значения.
Pauli Østerø 11.12.2010 04:13:27
"for (index = objectListType.Count - 1; count> = 0; --count) {/ * ... * /}" - лучший способ, по моему мнению, по одной причине. Это позволяет вам удалять элементы во время итерации. И если вы решите удалить элемент из objectListType "objectListType.RemoveAt (index)" во время зацикливания, вы не получите никаких странностей или исключений Modification-While-Enumeration. Приятно, что это качественно.
TamusJRoyce 27.10.2011 19:15:56