Neat way to write loop that has special logic for the first item in a collection

后端 未结 12 2602
忘了有多久
忘了有多久 2021-02-13 17:31

Often I have to code a loop that needs a special case for the first item, the code never seems as clear as it should ideally be.

Short of a redesign of the C# language,

相关标签:
12条回答
  • 2021-02-13 17:43

    You could try:

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

    first / rest would look like:

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

    You would need to define classed FirstPart and FirstRest. FirstRest would need a run method like so (Collection, FirstAction, and RestAction are properties):

    void run()
    {
        bool first = true;
        foreach (var x in Collection)
        {
            if (first) {
                FirstAction(x);
                first = false;
            }
            else {
                 RestAction(x);
            }
        }
    }
    
    0 讨论(0)
  • 2021-02-13 17:45

    Whilst I wouldn't personally do this, there is another way using enumerators, which alleviates the need for conditional logic. Something like this:

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

    Sample output would be:

    First is: 1
    2
    3
    4
    5
    
    0 讨论(0)
  • 2021-02-13 17:56

    IMHO the most cleanest way is: try to avoid special cases for the first item. That may not work in every situation, of course, but "special cases" may indicate that your program logic is more complex than it needs to be.

    By the way, I would not code

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

    but instead

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

    (which is itself a simple example of how to avoid unnecessary dealing with a special case.)

    0 讨论(0)
  • 2021-02-13 17:57

    I use the first variable method all the time and it seems totally normal to me. If you like that better you can use LINQ First() and 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
    }
    
    0 讨论(0)
  • 2021-02-13 17:58

    The way you wrote it is probably the cleanest way it can be written. After all, there is logic specific to the first element, so it has to be represented somehow.

    0 讨论(0)
  • 2021-02-13 18:01

    How about:

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

    You could turn that into an extension if you want:

    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);            
        }
    }
    
    0 讨论(0)
提交回复
热议问题