понедельник, 24 ноября 2014 г.

List.ForEach в .NET 4.5

Блог переехал. Актуальная версия поста находится по адресу: http://aakinshin.net/ru/blog/dotnet/listforeach-net-45/.


Продолжим обсуждать тему изменения коллекции внутри цикла foreach. Следующий код

var list = new List<int> { 1, 2 };
foreach (var i in list)
{
    if (i == 1)
        list.Add(3);
    Console.WriteLine(i);
}
выбросит InvalidOperationException. А как думаете, что случится при выполнении цикла через List<T>.ForEach?

var list = new List<int> { 1, 2 };
list.ForEach(i =>
{
    if (i == 1)
        list.Add(3);
    Console.WriteLine(i);
});

Правильный ответ: зависит. Ранее (.NET 4.0) данный код замечательно отрабатывал и выводил 1 2 3. Это было не очень хорошо. Поэтому в .NET 4.5 поведение поменялось, ForEach начал бросать InvalidOperationException для случая, если кто-то внутри цикла менял коллекцию:

public void ForEach(Action<T> action) {
  if( action == null) { 
    ThrowHelper.ThrowArgumentNullException(ExceptionArgument.match);
  } 
  Contract.EndContractBlock();

  int version = _version;

  for(int i = 0 ; i < _size; i++) {
    if (version != _version && BinaryCompatibility.TargetsAtLeast_Desktop_V4_5) { 
      break; 
    }
    action(_items[i]); 
  }

  if (version != _version && BinaryCompatibility.TargetsAtLeast_Desktop_V4_5)
    ThrowHelper.ThrowInvalidOperationException(
        ExceptionResource.InvalidOperation_EnumFailedVersion); 
}

Что касается Mono, то в нём никаких исключение не бросается (даже в последнем стабильном на текущий момент Mono 3.10). Когда я узнал, что в Mono 3.10 всё плохо, то очень расстроился. А потом пошёл и завёл баг-репорт. Вскоре баг был исправлен.

А в старых версий (например, 2.10) был баг, в результате которого исключение не происходило даже внутри обычного foreach, если коллекцию менять через индексатор (ideone):

using System;
using System.Collections.Generic;

namespace test
{
    public class test
    {
        public static void Main()
        {
            List x = new List();
            x.Add(1);
            x.Add(4);
            x.Add(9);
            foreach(int i in x){
                x[2] = 3;
            }
            foreach(int i in x){
                System.Console.WriteLine(i);
            }            
        }
    }
}

Actual Results (Mono 2.10):
1
4
3

Ссылки по теме