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

时间:2010-08-31 14:29:07

标签: c# .net .net-4.0

我有一个可枚举的函数,但第一项的函数应该有点不同,例如:

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 个答案:

答案 0 :(得分:9)

如果您真的想要对第一项做某事,可以解释Jimmy Hoffa的回答。

DoFirstWork(a[0])

a.Skip(1).ForEach(DoWork)

如果重点是逻辑与列表的其余部分是分开的,那么你应该使用一个单独的函数。

答案 1 :(得分:2)

这可能有点沉重,但我暂时从另一个SO问题中解决了这个问题。

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

答案 2 :(得分:1)

查看Jon Skeet的smart enumerations

他们是Miscellaneous Utility Library

的一部分

答案 3 :(得分:0)

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)

答案 4 :(得分:0)

很难说以不同方式处理第一个元素的“最佳”方式是不知道为什么需要以不同方式处理它。

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

答案 5 :(得分:0)

对于所有可用的Linq材料来说,它看起来似乎很简陋,但总是存在旧时尚的循环。

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

如果你不想在循环中改变你的List,那就没问题了。否则,您可能需要显式使用迭代器。那时,你不得不想知道是否真的值得它去除IsFirst标志。

答案 6 :(得分:0)

编辑:添加了用法示例,添加了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中显然没有相应的内容。我现在感觉不太好。

答案 7 :(得分:0)

取决于你如何“以不同方式处理”。如果你需要做一些完全不同的事情,那么我建议处理循环外的第一个元素。如果除了常规元素处理之外还需要执行某些操作,请考虑检查其他处理的结果。在代码中理解起来可能更容易,所以这里有一些:

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

这样,你的“旗帜”实际上是你(大概)需要的外部变量,所以你不是在创建专用变量。如果您不想像处理其余枚举那样处理第一个元素,也可以将其包装在if/else中。