Подтвердить что ты не робот

Почему List <T>.ForEach() реализует цикл for?

Я не понимаю, почему метод расширения List<T>.ForEach() реализует цикл for под капотом. Это открывает возможность изменения коллекции. Нормальный foreach генерирует исключение в этом случае, так что ForEach() должен реагировать одинаково?

Если вам по какой-то причине вам нужно мутировать коллекцию, то, конечно, вы должны вручную выполнять итерацию через коллекцию в цикле for?

Кажется, что существует немного смысловое противоречие между foreach и List<T>.ForEach().

Я что-то пропустил?

4b9b3361

Ответ 1

Только член команды BCL может сказать нам наверняка, но, вероятно, это был просто недосмотр, что List<T>.ForEach позволяет вам изменять список.

Во-первых, ответ Дэвида Б не имеет смысла для меня. Это List<T>, а не С#, который проверяет, измените ли вы список в цикле foreach и выбрасывает InvalidOperationException, если вы это сделаете. Это не имеет никакого отношения к используемому вами языку.

Во-вторых, это предупреждение в документации:

Изменение базовой коллекции в теле действия <T> делегат не поддерживается и вызывает поведение undefined.

Я считаю маловероятным, чтобы команда BCL хотела, чтобы такой простой метод, как foreach, имел поведение undefined.

В-третьих, с .NET 4.5, List<T>.ForEach выкинет InvalidOperationException, если делегат изменит список. Если программа зависит от старого поведения, перестает работать, когда она перекомпилируется для целевой .NET 4.5. Тот факт, что Microsoft готов принять это нарушение, сильно говорит о том, что первоначальное поведение было непреднамеренным и на него нельзя положиться.

Для справки, здесь, как List<T>.ForEach реализован в .NET 4.0 прямо из исходного источника:

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

    for(int i = 0 ; i < _size; i++) {
        action(_items[i]);
    }
}

И вот как это изменилось в .NET 4.5:

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);
}

Ответ 2

Потому что List.ForEach после определения из MSDN:

Выполняет указанное действие для каждого элемента списка.

Это означает, что Action, выполняемый над элементом, может потенциально изменить элемент или собственную коллекцию. В этом случае нет другого способа (если не создать сборку клонированных клонов, если это возможно), чтобы использовать это, а затем используя простой for.

Если вы изменяете коллекцию во время итерации в foreach, она, естественно, вызывает исключение.

Ответ 3

foreach - это элемент языка С#. Он играет по правилам С#.

List<T>.ForEach - это метод .NET Framework. Он играет по правилам .NET, где foreach не существует.

Это пример путаницы "язык против рамки". Рамочные методы должны работать на многих языках, а языки (обычно) имеют противоречивую семантику.

Другим примером этой путаницы "language vs framework" является нарушение в Enumerable.Cast между .net 3 и .NET 3.5. В .NET 3, Cast используется семантика С#. В .net 3.5 было изменено использование семантики .net.