Хороший способ написать цикл, который имеет специальную логику для первого элемента в коллекции - PullRequest
12 голосов
/ 08 декабря 2010

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

// this is more code to read then I would like for such a common concept
// and it is to easy to forget to update "firstItem"
foreach(x in yyy)
{
  if (firstItem)
  {
     firstItem = false;
     // other code when first item
  }
  // normal processing code
}

// this code is even harder to understand
if (yyy.Length > 0)
{
   //Process first item;
   For(int I = 1; I <yyy.Length; i++)
   {  
      // process the other items.
   }
}

Ответы [ 11 ]

13 голосов
/ 08 декабря 2010

Как насчет:

using (var erator = enumerable.GetEnumerator())
{
    if (erator.MoveNext())
    {
        ProcessFirst(erator.Current);
        //ProcessOther(erator.Current); // Include if appropriate.

        while (erator.MoveNext())
            ProcessOther(erator.Current);
    }
}

Вы можете превратить это в расширение, если хотите:

public static void Do<T>(this IEnumerable<T> source, 
                         Action<T> firstItemAction,
                         Action<T> otherItemAction)
{
   // null-checks omitted

    using (var erator = source.GetEnumerator())
    {
        if (!erator.MoveNext())
            return;

        firstItemAction(erator.Current);

        while (erator.MoveNext())
           otherItemAction(erator.Current);            
    }
}
5 голосов
/ 08 декабря 2010

Вы можете попробовать:

collection.first(x=>
{
    //...
}).rest(x=>
{
    //...
}).run();

first / rest будет выглядеть следующим образом:

FirstPart<T> first<T>(this IEnumerable<T> c, Action<T> a)
{
    return new FirstPart<T>(c, a);
}

FirstRest rest<T>(this FirstPart<T> fp, Action<T> a)
{
    return new FirstRest(fp.Collection, fp.Action, a);
}

Вам необходимо определить классифицированные FirstPart и FirstRest.FirstRest потребуется такой метод запуска (свойства Collection, FirstAction и RestAction являются):

void run()
{
    bool first = true;
    foreach (var x in Collection)
    {
        if (first) {
            FirstAction(x);
            first = false;
        }
        else {
             RestAction(x);
        }
    }
}
5 голосов
/ 08 декабря 2010

Я хотел бы использовать немного linq

using System.Linq;

var theCollectionImWorkingOn = ...

var firstItem = theCollectionImWorkingOn.First();
firstItem.DoSomeWork();

foreach(var item in theCollectionImWorkingOn.Skip(1))
{
    item.DoSomeOtherWork();
}
4 голосов
/ 08 декабря 2010

Я все время использую переменный метод first, и он кажется мне совершенно нормальным.Если вам это нравится больше, вы можете использовать LINQ First() и Skip(1)

var firstItem = yyy.First();
// do the whatever on first item

foreach (var y in yyy.Skip(1))
{
// process the rest of the collection
}
2 голосов
/ 08 декабря 2010

ИМХО самым чистым способом является: старайтесь избегать особых случаев для первого предмета. Конечно, это может работать не в каждой ситуации, но «особые случаи» могут указывать на то, что логика вашей программы более сложна, чем должна быть.

Кстати, я бы не кодировал

if (yyy.Length > 0)
{
   for(int i = 1; i <yyy.Length; i++)
   {  
      // ...
   }
}

но вместо

   for(int i = 1; i <yyy.Length; i++)
   {  
      // ...
   }

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

2 голосов
/ 08 декабря 2010

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

1 голос
/ 08 декабря 2010

Вот немного более простой метод расширения, который делает эту работу.Это сочетание решения KeithS и моего ответа на связанный вопрос Java :

public static void ForEach<T>(this IEnumerable<T> elements,
                              Action<T> firstElementAction,
                              Action<T> standardAction)
{
    var currentAction = firstElementAction;
    foreach(T element in elements)
    {
        currentAction(element);
        currentAction = standardAction;
    }
}
1 голос
/ 08 декабря 2010

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

for(int i = 0;  i < yyy.Count; i++){
      if(i == 0){
          //special logic here
      }
}

Использование цикла for также позволит вам сделать что-то особенное в других случаях, например, для последнего элемента, даже для элементовв последовательности, ..etc.

0 голосов
/ 09 декабря 2010

Другой вариант, который я придумал, это

enum ItemType
{
  First,
  Last,
  Normal
}

list.Foreach(T item, ItemType itemType) =>
{
   if (itemType == ItemType.First)
   {
   }

   // rest of code
};

Написание метода расширения оставлено читателю в качестве упражнения… Также следует ли использовать два булевых флага «IsFirst» и «IsLast» вместо перечисления ItemType, или ItemType должен быть объектом со свойствами «IsFirst» и «IsLast»?

0 голосов
/ 08 декабря 2010

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

void Main()
{
    var numbers = Enumerable.Range(1, 5);
    IEnumerator num = numbers.GetEnumerator();

    num.MoveNext();
    ProcessFirstItem(num.Current); // First item

    while(num.MoveNext()) // Iterate rest
    {
        Console.WriteLine(num.Current);
    }

}

    void ProcessFirstItem(object first)
    {
        Console.WriteLine("First is: " + first);
    }

Пример вывода будет:

First is: 1
2
3
4
5
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...