Как выйти из цикла List <string>.ForEach при использовании анонимного делегата? - PullRequest
34 голосов
/ 17 февраля 2009

В нормальном цикле вы можете выйти из цикла, используя break. Можно ли сделать то же самое с помощью анонимного делегата?

Пример inputString и результат оба объявлены вне делегата.

blackList.ForEach(new Action<string>(
    delegate(string item)
    {
        if(inputString.Contains(item)==true)
        {
            result = true;
            // I want to break here
        }
    }
));

Edit: Спасибо за ответы, я на самом деле читаю вашу книгу в тот момент, Джон :) Просто для записи, я затронул эту проблему и переключился на обычный цикл foreach, но я опубликовал этот вопрос, чтобы посмотреть, пропустил ли я что-то.

Ответы [ 11 ]

37 голосов
/ 17 февраля 2009

Как уже сообщали другие, вы не можете выйти из цикла в ForEach.

Вы можете использовать LINQ? Если это так, вы можете легко объединить TakeWhile и пользовательский метод расширения ForEach (который, похоже, есть почти у каждого проекта в наши дни).

В вашем примере, однако, List<T>.FindIndex будет лучшей альтернативой - но если вы на самом деле этого не делаете, опубликуйте пример того, что вы действительно хотите делаем.

27 голосов
/ 17 февраля 2009

Нет петли, к которой можно получить доступ, из которой можно выйти. И каждый вызов (анонимного) делегата является новым вызовом функции, поэтому локальные переменные не помогут. Но так как C # дает вам закрытие, вы можете установить флаг, а затем ничего не делать в следующих вызовах:

bool stop = false;
myList.ForEach((a) => {
  if (stop) {
    return;
  } else if (a.SomeCondition()) {
    stop = true;
  }
});

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

Более продвинутым подходом было бы создание собственного метода расширения, позволяющего делегату возвращать false, чтобы остановить цикл:

static class MyExtensions {
  static void ForEachStoppable<T>(this IEnumerable<T> input, Func<T, bool> action) {
    foreach (T t in input) {
      if (!action(t)) {
        break;
      }
    }
  }
}
15 голосов
/ 17 февраля 2009

У вас есть LINQ? Ваша логика похожа на Any:

bool any = blackList.Any(s=>inputString.Contains(s));

, что совпадает с:

bool any = blackList.Any(inputString.Contains);

Если у вас нет LINQ, то это то же самое, что и:

bool any = blackList.Find(inputString.Contains) != null;

Если вы хотите запустить дополнительную логику, есть вещи, которые вы можете сделать (с помощью LINQ) с помощью TakeWhile etc

6 голосов
/ 17 февраля 2009

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

Что мешает вам делать старомодный foreach?

foreach (string item in blackList)
{
    if (!inputString.Contains(item)) continue;

    result = true;
    break;
}
3 голосов
/ 17 февраля 2009

Если вы хотите цикл, используйте цикл.

Action допускает отсутствие возвращаемого значения, поэтому функция ForEach не может знать, что вы хотите прервать, за исключением исключения. Использование исключения здесь - излишнее.

2 голосов
/ 17 февраля 2009

Метод ForEach не предназначен для этого. Если вы хотите узнать, содержит ли коллекция элемент, вы должны использовать метод Contains. И если вы хотите выполнить проверку всех элементов в коллекции, вы должны попробовать метод Any extention.

2 голосов
/ 17 февраля 2009

Единственный способ «выйти» из цикла - вызвать исключение. Не существует способа выхода из метода .ForEach в стиле "break", как в обычном цикле foreach.

1 голос
/ 19 февраля 2016

если вы действительно хотите существовать цикл foreach в списке, вы можете использовать исключение, как этот код:

public class ExitMyForEachListException : Exception
{
    public ExitMyForEachListException(string message)
        : base(message)
    {
    }
}
class Program
{
    static void Main(string[] args)
    {
        List<string> str = new List<string>() { "Name1", "name2", "name3", "name4", "name5", "name6", "name7" };
        try
        {
            str.ForEach(z =>
            {
                if (z.EndsWith("6"))
                    throw new ExitMyForEachListException("I get Out because I found name number 6!");
                System.Console.WriteLine(z);
            });
        }
        catch (ExitMyForEachListException ex)
        {
            System.Console.WriteLine(ex.Message);
        }

        System.Console.Read();
    }
}

надеюсь, что это поможет получить другую точку зрения.

1 голос
/ 17 февраля 2009
bool @break = false;

blackList.ForEach(item =>
 {  
    if(!@break && inputString.Contains(item))
     { @break = true;
       result = true;
     }

    if (@break) return;
    /* ... */
 });

Обратите внимание, что выше все равно будет проходить через каждый элемент, но сразу же вернуться. Конечно, этот способ, вероятно, не так хорош, как обычный foreach.

1 голос
/ 17 февраля 2009
    class Program
{
    static void Main(string[] args)
    {
        List<string> blackList = new List<string>(new[] { "jaime", "jhon", "febres", "velez" });
        string inputString = "febres";
        bool result = false;
        blackList.ForEach((item) =>
                              {
                                  Console.WriteLine("Executing");
                                  if (inputString.Contains(item))
                                  {
                                      result = true;
                                      Console.WriteLine("Founded!");
                                  }
                              },
                          () => result);
        Console.WriteLine(result);
        Console.ReadLine();
    }


}
public static class MyExtensions
{
    public static void ForEach<T>(this IEnumerable<T> enumerable, Action<T> action, Func<bool> breakOn)
    {
        foreach (var item in enumerable)
        {
            action(item);
            if (breakOn())
            {
                break;
            }
        }
    }
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...