代码之家  ›  专栏  ›  技术社区  ›  onof

像这样优雅地重构代码(避免标记)

  •  2
  • onof  · 技术社区  · 14 年前

    void start() { 
        List<string> a = ...
        a.ForEach(DoWork);
    }
    
    bool isFirst = true;
    
    private void DoWork(string s) {
       // do something
    
       if(isFirst)
         isFirst = false;
       else
         print("first stuff");
    
       // do something
    }
    

    您将如何重构它以避免那个丑陋的标志?

    8 回复  |  直到 8 年前
        1
  •  0
  •   Dr. Wily's Apprentice    14 年前

    编辑:添加了用法示例,添加了ForFirst方法,对我的段落重新排序。

    下面是一个完整的解决方案。

            list.ForFirst(DoWorkForFirst).ForRemainder(DoWork);
            // or 
            list.ForNext(1, DoWorkForFirst).ForRemainder(DoWork);
    

    关键是 ForNext 方法,该方法对集合中指定的下一组项执行操作并返回其余项。我还实现了一个 ForFirst 方法调用count为1的ForNext。

    class Program
    {
        static void Main(string[] args)
        {
            List<string> list = new List<string>();
            // ...
    
            list.ForNext(1, DoWorkForFirst).ForRemainder(DoWork);
        }
    
        static void DoWorkForFirst(string s)
        {
            // do work for first item
        }
    
        static void DoWork(string s)
        {
            // do work for remaining items
        }
    }
    
    public static class EnumerableExtensions
    {
        public static IEnumerable<T> ForFirst<T>(this IEnumerable<T> enumerable, Action<T> action)
        {
            return enumerable.ForNext(1, action);
        }
    
        public static IEnumerable<T> ForNext<T>(this IEnumerable<T> enumerable, int count, Action<T> action)
        {
            if (enumerable == null)
                throw new ArgumentNullException("enumerable");
    
            using (var enumerator = enumerable.GetEnumerator())
            {
                // perform the action for the first <count> items of the collection
                while (count > 0)
                {
                    if (!enumerator.MoveNext())
                        throw new ArgumentOutOfRangeException(string.Format(System.Globalization.CultureInfo.InvariantCulture, "Unexpected end of collection reached.  Expected {0} more items in the collection.", count));
    
                    action(enumerator.Current);
    
                    count--;
                }
    
                // return the remainder of the collection via an iterator
                while (enumerator.MoveNext())
                {
                    yield return enumerator.Current;
                }
            }
        }
    
        public static void ForRemainder<T>(this IEnumerable<T> enumerable, Action<T> action)
        {
            if (enumerable == null)
                throw new ArgumentNullException("enumerable");
    
            foreach (var item in enumerable)
            {
                action(item);
            }
        }
    }
    

    ForRemainder 方法;我可以发誓我正在用它重新实现一个内置函数,但是我没有想到它,而且我环顾了一下之后也找不到一个等价的方法。更新:在阅读了其他答案之后,我发现Linq中显然没有一个等价的内置版本。我现在不觉得很难过。

        2
  •  9
  •   msarchet    14 年前

    解释吉米·霍法的答案如果你真的想对第一项做点什么,你可以这样做。

    DoFirstWork(a[0])

    a.Skip(1).ForEach(DoWork)

    如果关键是它在逻辑上与列表的其余部分是分开的,那么您应该使用一个单独的函数。

        3
  •  2
  •   Andy_Vulhop    14 年前

    public static void IterateWithSpecialFirst<T>(this IEnumerable<T> source,
        Action<T> firstAction,
        Action<T> subsequentActions)
    {
        using (IEnumerator<T> iterator = source.GetEnumerator())
        {
            if (iterator.MoveNext())
            {
                firstAction(iterator.Current);
            }
            while (iterator.MoveNext())
            {
                subsequentActions(iterator.Current);
            }
        }
    }
    
        4
  •  1
  •   Rune    14 年前
        5
  •  0
  •   abatishchev Karl Johan    14 年前
    using System.Linq; // reference to System.Core.dll
    
    List<string> list = ..
    list.Skip(1).ForEach(DoWork) // if you use List<T>.ForEeach()
    

    但我建议你写一个:

    public static void ForEach(this IEnumerable<T> collection, Action<T> action)
    {
        foreach(T item in collection)
            action(item);
    }
    

    所以你可以做下一步:

    list.Skip(1).ForEach(DoWork)
    
        6
  •  0
  •   Patrick    14 年前

    在不知道为什么需要对第一个元素进行不同处理的情况下,很难说什么是“最好的”处理方法。

    如果要将序列的元素输入到框架的ForEach方法中,就不能优雅地为操作委托提供确定元素参数在源序列中的位置所需的信息,因此我认为需要额外的步骤。如果在循环遍历该序列后不需要对其执行任何操作,则始终可以使用队列(或堆栈),通过Dequeue()(或Pop())方法调用将第一个元素传递给正在使用的任何处理程序,然后就可以得到剩余的“同构”序列。

        7
  •  0
  •   Andy_Vulhop    14 年前

    所有闪亮的Linq产品看起来都很简陋,但是loop总是有旧的流行方式。

    var yourList = new List<int>{1,1,2,3,5,8,13,21};
    for(int i = 0; i < yourList.Count; i++)
    {
        if(i == 0)
            DoFirstElementStuff(yourList[i]);
        else
            DoNonFirstElementStuff(yourList[i]);
    }
    

        8
  •  0
  •   TMN    14 年前

    取决于你如何“以不同的方式处理”。如果需要做一些完全不同的事情,那么我建议在循环之外处理第一个元素。如果你需要做点什么 除了 常规元素处理,然后考虑检查附加处理的结果。在代码中可能更容易理解,下面是一些:

    string randomState = null; // My alma mater!
    foreach(var ele in someEnumerable) {
        if(randomState == null) randomState = setState(ele);
        // handle additional processing here.
    }
    

    这样,您的“flag”实际上是一个您(大概)需要的外部变量,所以您没有创建专用变量。你也可以把它包起来 if/else 如果您不想像枚举的其余部分那样处理第一个元素。