Как вырваться из 2 циклов без переменной флага в C #? - PullRequest
38 голосов
/ 11 июня 2009

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

foreach(DataGridViewRow row in grid.Rows)
{
    foreach(DataGridViewCell cell in row.Cells)
    {
        if(cell.Value == myValue)
        {
            //Do Something useful
            //break out of both foreach loops.
        }
    }
}

Как это делается в C #. В Java я мог бы использовать метку для названия самого внешнего цикла, а затем разорвать этот цикл, но я не могу найти эквивланта в C #.

Какой самый короткий способ сделать это в c #? Я знаю, что могу установить логический флаг и проверить его во внешнем цикле, чтобы он тоже вышел из этого цикла, но он кажется слишком многословным.

Спасибо

Ответы [ 17 ]

65 голосов
/ 11 июня 2009

1

foreach(DataGridViewRow row in grid.Rows)
   foreach(DataGridView cell in row.Cells)
      if (cell.Value == somevalue) {
         // do stuff
         goto End;
      }
End:
   // more stuff

2

void Loop(grid) {
    foreach(row in grid.Rows)
       foreach(cell in row.Cells)
           if (something) {
               // do stuff   
               return;
           }
}

3

var cell = (from row in grid.Rows.OfType<DataGridViewRow>()
            from cell in row.Cells.OfType<DataGridViewCell>()
            where cell.Value == somevalue
            select cell
   ).FirstOrDefault();

if (cell != null) {
   // do stuff
}
57 голосов
/ 11 июня 2009

Хотя многие из приведенных выше решений верны и отвечают на ваш вопрос, я бы сделал шаг назад и спросил бы себя «Есть ли другой способ более четко представить семантику программы?»

Я бы хотел написать такой код:

var query = from row in grid.Rows
            from cell in row.Cells
            where cell.Value == myValue
            select cell;
if (query.Any())
{
  // do something useful;
}

Зачем вообще писать циклы? Вы хотите знать, имеет ли конкретная коллекция определенный член, поэтому напишите запрос, который задает этот вопрос, а затем изучите ответ.

35 голосов
/ 11 июня 2009

Самый приятный способ - разбить второй цикл на функцию, например:

public void DoubleLoop()
{
    for(int i = 0; i < width; i++)
    {
        for(int j = 0; j < height; j++)
        {
            if(whatever[i][j]) break; // let's make this a "double" break
        }
    }
}

переходит к

public bool CheckWhatever(int whateverIndex)
{
    for(int j = 0; j < height; j++)
    {
        if(whatever[whateverIndex][j]) return false;
    }

    return true;
}

public void DoubleLoop()
{
    for(int i = 0; i < width; i++)
    {
        if(!CheckWhatever(i)) break;
    }
}

Конечно, не стесняйтесь упростить это с помощью LINQ или чего-либо еще (вы также можете поместить CheckWhatever в условие цикла.) Это просто многословная демонстрация принципа.

23 голосов
/ 11 июня 2009

Я бы просто заключил циклы в функцию, и функция вернулась бы как способ выхода из циклов для моего решения.

19 голосов
/ 11 июня 2009
        foreach (DataGridViewRow row in grid.Rows)
        {
            foreach (DataGridViewCell cell in row.Cells)
            {
                if (cell.Value == myValue)
                {
                    goto EndOfLoop;
                    //Do Something useful
                    //break out of both foreach loops.
                }
            }

        }
        EndOfLoop: ;

это будет работать, но я бы порекомендовал использовать логический флаг.

EDIT: Просто чтобы добавить немного больше предупреждения здесь; обычно считается плохой практикой использовать goto, поскольку они быстро могут привести к коду спагетти, который (почти) невозможно поддерживать. Тем не менее, он был включен в язык C # и доступен для использования, поэтому очевидно, что есть люди, которые считают, что он имеет действительное использование. Знайте, что эта функция существует, и используйте ее с большой осторожностью.

14 голосов
/ 11 июня 2009

Для полноты, есть и неправильный способ сделать это:

try
{
    foreach(DataGridViewRow row in grid.Rows)
        foreach(DataGridViewCell cell in row.Cells)
            if(cell.Value == myValue)
               throw new FoundItemException(cell);
}
catch (FoundItemException ex)
{
    //Do Something useful with ex.item
}
11 голосов
/ 11 июня 2009

C # имеет оператор goto . На самом деле, пример в MSDN использует его для выхода из дважды вложенного цикла.

8 голосов
/ 11 июня 2009

Лучший способ - не делать этого. Шутки в сторону; если вы хотите найти первое вхождение чего-либо в ваших вложенных циклах, а затем закончить поиск, то вам НЕ нужно проверять каждый элемент, что явно является тем, что делает конструкция foreach. Я бы рекомендовал использовать обычный цикл for с флагом завершения в инварианте цикла.

5 голосов
/ 11 июня 2009

Вы можете написать класс, который реализует IEnumerator в общем случае, и тогда ваш код перечисления будет выглядеть так:

foreach (Foo foo in someClass.Items) {
    foreach (Bar bar in foo.Items) {
        foreach (Baz baz in bar.Items) {
            yield return baz;
        }
    }
}

// and later in client code

MyEnumerator e = new MyEnumerator(someClass);
foreach (Baz baz in e) {
    if (baz == myValue) {
        // do something useful
        break;
    }
 }
2 голосов
/ 11 июня 2009
  //describe how to find interesting cells
var query = from row in grid.Rows.OfType<DataGridViewRow>()
            from cell in row.Cells.OfType<DataGridViewCell>()
            where cell.Value == myValue
            select cell;
  //nab the first cell that matches, if any
DataGridViewCell theCell = query.FirstOrDefault();

  //see if we got one
if (theCell != null)
{
  //Do something with theCell
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...