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

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


person billmaya    schedule 18.08.2008    source источник


Ответы (7)


Для С#:

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

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

For Each objectItem as ObjectType in objectTypeList
    'Do some stuff '
Next
person mbillard    schedule 18.08.2008
comment
Поскольку он строго типизирован, вы также можете использовать: foreach(var item in itemlist). - person Steven Sudit; 21.04.2010

С любой общей реализацией 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 секунды между самым быстрым и самым медленным методами. Если вы не выполняете массивные критические циклы производительности, об этом просто не стоит беспокоиться.

person Keith    schedule 18.08.2008
comment
Вопрос был о List<T>, а не о массивах. Несмотря на это, я не верю, что foreach в собственном массиве на самом деле использует IEnumerable, по крайней мере, не в оптимизированном коде, поэтому реального ускорения не будет. На самом деле, мой тест показывает, что foreach занимает всего три четверти времени, чем for. - person Steven Sudit; 21.04.2010
comment
@Steven Sudit - циклы foreach на самом деле страдают от циклов for после применения оптимизации компилятора. Однако я немного покопался - в случае массива foreach оптимизирован точно для того же IL, что и for. Для List<T> компилятор не может использовать эту оптимизацию, и результирующий IL немного медленнее. Похоже, мы оба ошибаемся (._.) - person Keith; 22.04.2010
comment
Я говорил не только из теории. Я написал небольшой тест и запустил его. Если хотите, я был бы рад выкопать его и опубликовать, чтобы вы могли увидеть, как это работает для вас. - person Steven Sudit; 23.04.2010
comment
@ Стивен Судит - да, я тоже. Я обновил ответ результатами. - person Keith; 23.04.2010
comment
Позвольте мне поблагодарить вас за тяжелую работу, но я должен упомянуть, что мои тесты имели одно ключевое отличие: я убедился, что циклы передают каждый элемент внешнему методу. Без этого компилятор, скорее всего, полностью откажется от присваивания. Я не знаю, объясняет ли это крошечный прирост скорости, но вполне может. - person Steven Sudit; 26.04.2010

C#

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

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

C#

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

ВБ.Нет

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

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


Вот банальный пример того, почему это может быть полезно: это дает вам возможность передавать логику цикла из другой области, отличной от той, где существует 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" и т. д. Очевидно, что есть лучшие способы сделать это в этом конкретном примере, я просто пытаюсь продемонстрировать основной принцип.

person Adam Lassek    schedule 18.08.2008
comment
Ваш код VB не будет работать. В этой версии VB поддерживает только анонимные функции, вам придется подождать, пока в VB10 не появятся анонимные подпрограммы. - person Jonathan Allen; 09.09.2008
comment
Вы правы, я не проверял это перед публикацией. Неудивительно, что документации по лямбда-выражениям в VB так мало; они не так полезны. - person Adam Lassek; 18.09.2008
comment
Очевидный вопрос: зачем возиться с обратными вызовами, когда можно просто сделать foreach? - person Steven Sudit; 21.04.2010
comment
Вы предполагаете, что IEnumerable и делегат находятся в одной области видимости, но, несмотря на мой простой пример, это не обязательно. Возможность передать замыкание в функцию из другой области видимости может быть очень мощным шаблоном проектирования. - person Adam Lassek; 21.04.2010
comment
Может быть, пример может помочь. И, пожалуйста, начинайте свои сообщения со мной с @Steve, чтобы я получал уведомления. - person Steven Sudit; 21.04.2010
comment
@Steve Хорошо, я добавил пример, чтобы показать вам, о чем я говорю. Кодировал Ruby в течение прошлого года, так что простите меня, если мой C # немного заржавел. - person Adam Lassek; 22.04.2010
comment
Спасибо, Адам. На примере я могу понять, о чем вы говорите. Я понимаю, что пример кода обязательно немного банален, но в общем случае этот метод имеет ценность, особенно из-за гибкости, которую он предлагает, если метод, принимающий делегат, более сложен. Меня беспокоит только то, что я не уверен, что это ответ на вопрос ОП, поскольку он сложнее (и, вероятно, немного медленнее), чем базовый foreach. Я бы сказал, что это не лучший способ в целом, но это, безусловно, один из наиболее гибких способов, и поэтому о нем стоит знать на этой основе. - person Steven Sudit; 23.04.2010

Для VB.NET:

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

person Brian G Swanson    schedule 18.08.2008

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

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

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

person Matt Hamilton    schedule 18.08.2008
comment
Да, потому что связанный список должен будет искать каждый новый элемент линейно, тогда как итератор по нему займет всего один проход. - person Steven Sudit; 21.04.2010

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

  • цикл for, если эффективность является приоритетом
  • цикл foreach или метод ForEach, в зависимости от того, что более четко передает ваши намерения.
person jfs    schedule 19.08.2008
comment
foreach преобразуется в вызовы GetEnumerator(), MoveNext() и Current(), что явно медленнее, чем увеличение индексатора и выборка из массива. Мы говорим о наносекундах, и обычно это не вызывает никаких опасений. - person Pauli Østerø; 11.12.2010
comment
for (index = objectListType.Count - 1; count ›= 0; --count) { /* ... */ } - лучший способ, на мой взгляд, по одной причине. Это позволяет вам удалять элементы во время итерации. И если вы решите удалить элемент из objectListType objectListType.RemoveAt(index) во время цикла, вы не получите никаких странностей или исключений Modification-While-Enumeration. Приятно, что он исполнительный. - person TamusJRoyce; 27.10.2011

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

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

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

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

foreach(<insertTypeHere> o in genericList)
{
    // Loop body
}
person Dan Herbert    schedule 18.08.2008
comment
Не нужно <insertTypeHere>. Компилятор сделает это за вас с помощью var. - person Steven Sudit; 21.04.2010