C#代码简化问题:顺序Foreach循环

6
假设我有一些代码,看起来像这样:
foreach(type x in list y)
{
   //dostuff1(x)
}

foreach(type x in list y)
{
   //dostuff2(x)
}

foreach(type x in list y)
{
   //dostuff3(x)
}

foreach(type x in list y)
{
   //dostuff4(x)
}

foreach(type x in list y)
{
   //dostuff5(x)
}

我无法将事物组合成一个大的for循环,就像这样:

foreach (type x in list y)
{
    //dostuff1(x)
    //dostuff2(x)
    //dostuff3(x)
    //dostuff4(x)
    //dostuff5(x)
}

这样做会改变顺序。对于如何使C#代码更简单的最佳方法有什么评论吗?

我想我可以通过创建像这样的函数来解决这个问题,虽然我宁愿让它保持原样,而不是强迫未来读者理解 yield

void func(type x)
{
    dostuff1(x)
    yield 0;
    dostuff2(x)
    yield 0;
    dostuff3(x)
    yield 0;
    dostuff4(x)
    yield 0;
    dostuff5(x)
    yield break;
}

for (int i = 0; i<5; ++i)
{
   foreach (type x in list y)
   {
       //Call func(x) using yield semantics, which I'm not going to look up right now
   }
}
6个回答

30

另一个选择:

List<Action<Foo>> actions = new List<Action<Foo>> { 
    doStuff1, doStuff2, doStuff3, doStuff4, doStuff5
};

foreach (Action<Foo> action in actions)
{
    foreach (Foo x in list)
    {
        action(x);
    }
}

我刚刚检查了一下,它是可以正常工作的。例如:

using System;
using System.Collections.Generic;

public class Test
{
    static void Main(string[] args)
    {
        var actions = new List<Action<string>> {
            First, Second
        };

        foreach (var action in actions)
        {
            foreach (string arg in args)
            {
                action(arg);
            }
        }
    }

    static void First(string x)
    {
        Console.WriteLine("First: " + x);
    }

    static void Second(string x)
    {
        Console.WriteLine("Second: " + x);
    }
}

运行Test.exe a b c的结果

First: a
First: b
First: c
Second: a
Second: b
Second: c

5
如果您有一个非常固定的操作列表,您可以避免使用 foreach 循环,而是显式地执行这些操作(代码未经测试):
list.ForEach(action1);
list.ForEach(action2);
list.ForEach(action3);
list.ForEach(action4);

5

Jon Skeet的回答很好(我刚投了赞成票)。这里有一个可以更进一步的想法:

如果你经常这样做,你可以编写一个扩展方法叫做“DoActionsInOrder”(或者也许你可以想到一个更好的名字)来完成这个任务。以下是思路:

public static void DoActionsInOrder<T>(this IEnumerable<T> stream, params Action<T> actionList)
{
     foreach(var action in actionList)
     {
          foreach(var item in stream)
          {
               action(item);
          }
     }
}

然后,您可以这样调用它:
myList.DoActionsInOrder(doStuff1, doStuff2, doStuff3, doStuff4, doStuff5);

该死的同步性!我刚刚完成了完全相同的扩展方法。我唯一能看到的区别是你使用var而不是T :) +1,然后! - flq
真是太神奇了,这个网站让我们急切地回答其他人的问题!我都要上瘾了! - Charlie Flowers

2
如何考虑以下内容:
interface IDoStuff
{
     void DoStuff(x);
}

List<IDoStuff> listOfActions = ...
foreach (IDoStuff iDoStuff in listOfActions)
{
    foreach (type x in list y)
    {
         iDoStuff(x);
    }
} 

[编辑] 是的,正如J.Skeet所说,你应该选择通用解决方案(尽管你也可以使用通用接口而不是委托)。


为什么要使用接口,当委托更加方便呢? :) - Jon Skeet
你是正确的。但有时候我更喜欢用委托来将一些状态与动作关联起来,这样每个动作+数据就可以封装在一个单独的类中(当然你也可以从另一个类中传递公共委托)...或者你认为在这些情况下也应该使用委托? - vgru
这里委托有几个好处:1)您可以使用Lambda表达式和匿名方法(这提供闭包支持-关联状态!)2)您可以从同一类中拥有多个委托实例-比每个接口实现一个类更清晰。 - Jon Skeet

0
如果你必须保留顺序性,那么你能做的不多。你可以使用一些扩展方法的快捷方式,但在我看来,这会导致代码可读性降低。此外,根据你的方法签名,你可能会遇到问题。
你可以重构代码,将迭代移到单独的函数中。
// Method 1
DoStuff1ToList(y);
DoStuff2ToList(y);
DoStuff3ToList(y);
DoStuff4ToList(y);
DoStuff5ToList(y);

// Do Stuff 1
foreach (var x in y)
{ 
    // do actual stuff
}

0

我认为这与 testing.ForEach(action) 相同,如果你要走这种路线,就使用它。

private static void DoDifferentStuffToThings()
    {
        List<string> testing = new List<string>() { "hello", "world" };

        Action<string> action1 = (a) =>
        {
            Console.WriteLine("Action 1 {0}", a);
        };

        Action<string> action2 = (a) =>
        {
            Console.WriteLine("Action 2 {0}", a);
        };

        DoStuffToThings<string>(testing, action1);
        DoStuffToThings<string>(testing, action2);
    }

    private static void DoStuffToThings<T>(IEnumerable<T> list, Action<T> dothing)
        where T : class
    {
        foreach (var item in list)
        {
            dothing(item);
        }
    }

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接