【问题标题】:Is there an elegant way to repeat an action?有没有一种优雅的方式来重复一个动作?
【发布时间】:2011-09-18 09:39:47
【问题描述】:

在使用 .NET Framework 4 的 C# 中,是否有一种优雅的方法可以将相同的操作重复一定次数?例如,而不是:

int repeat = 10;
for (int i = 0; i < repeat; i++)
{
    Console.WriteLine("Hello World.");
    this.DoSomeStuff();
}

我想写这样的东西:

Action toRepeat = () =>
{
    Console.WriteLine("Hello World.");
    this.DoSomeStuff();
};

toRepeat.Repeat(10);

或:

Enumerable.Repeat(10, () =>
{
    Console.WriteLine("Hello World.");
    this.DoSomeStuff();
});

我知道我可以为第一个示例创建我自己的扩展方法,但是是否有一个现有的功能可以做到这一点?

【问题讨论】:

    标签: c# .net linq .net-4.0


    【解决方案1】:

    像这样?

    using System.Linq;
    
    Enumerable.Range(0, 10).ForEach(arg => toRepeat());
    

    这将执行你的方法 10 次。

    [编辑]

    我已经习惯了在 Enumerable 上使用 ForEach 扩展方法,以至于我忘记了它不是 FCL 的一部分。

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

    如果没有ForEach 扩展方法,您可以执行以下操作:

    Enumerable.Range(0, 10).ToList().ForEach(arg => toRepeat());
    

    [编辑]

    我认为最优雅的解决方案是实现可重用的方法:

    public static void RepeatAction(int repeatCount, Action action)
    {
        for (int i = 0; i < repeatCount; i++)
            action();
    }
    

    用法:

    RepeatAction(10, () => { Console.WriteLine("Hello World."); });
    

    【讨论】:

    • 一个旧答案,但你也可以这样做Enumerable.Range(0, 10).Select(o =&gt; /* return or do something */)
    • @will-hart 这很脏,乍一看你在做什么不是很清楚。编写扩展方法比滥用 select 执行操作要好。
    • “我已经习惯了在 Enumerable 上使用 ForEach 扩展方法”我在尝试发布代码时遇到了同样的问题;太多有用的方法通常不存在。
    【解决方案2】:

    没有内置的方法可以做到这一点。

    原因是 C# 试图在语言的函数式和命令式两面之间进行区分。 C# 仅在不会产生副作用的情况下使函数式编程变得容易。因此,您将获得像 LINQ 的 WhereSelect 等这样的集合操作方法,但 you do not get ForEach1

    以类似的方式,您在这里尝试做的是找到某种功能性的方式来表达本质上是命令性的动作。尽管 C# 为您提供了执行此操作的工具,但它并没有尝试让您轻松,因为这样做会使您的代码不清楚且不习惯。

    1List&lt;T&gt;.ForEach,但没有IEnumerable&lt;T&gt;.ForEach。我想说List&lt;T&gt;.ForEach 的存在是一个历史产物,源于框架设计者在 .NET 2.0 前后没有考虑过这些问题;明确划分的需要在 3.0 中才变得明显。

    【讨论】:

    • 当然,没有什么能阻止你在 IEnumerable 上编写你自己的带有副作用的 RepeatForEach 方法 - 语言设计者到那时已经洗手了。
    • 我能问一下为什么以ForEach 为例,这是一件坏事吗?
    • @CodeBlend,这是一个很好的问题。
    • 我相信有时你最好写一个for循环性能明智。 Enumerable 应该做它所暗示的;枚举。设计师明确区分了可操作集合和可迭代集合以避免混淆
    • @Domenic - 我认为你的解释是正确的,为什么 LINQ 对此没有帮助。但是,它不适用于更广泛的问题,即为什么 C# 缺少一种命令式 方式来表示“重复循环 n 次”。其他语言也这样做。或者至少更接近于这样做,例如 VB 的For .. To ..,它避免了 C# 的测试和递增或递减循环变量的语义过度杀伤,在单步执行一系列值的常见情况下。我们坚持使用 1970 年代为 C 发明的语法,因为它在具有递增和递减指令的 DEC PDP-11 上非常有效。
    【解决方案3】:

    如果不推出你自己的扩展,我想你可以做这样的事情

        Action toRepeat = () => {
            Console.WriteLine("Hello World.");
             this.DoSomeStuff();
        };
    
        int repeat = 10;
        Enumerable.Range(0, repeat).ToList().ForEach(i => toRepeat());
    

    【讨论】:

      【解决方案4】:

      为了简洁起见,您可以这样做。不知道你怎么想...

      Enumerable.Repeat<Action>(() => 
      {
          Console.WriteLine("Hello World.");
          this.DoSomeStuff();
      }, 10).ToList().ForEach(x => x());
      

      【讨论】:

      • o 认为这并不优雅。这比简单的 for 语句可读性差,也更难理解。
      • @Ewerton 也许这就是他们忽略此功能的原因;比使用简单的 foreach 语法更混乱。
      【解决方案5】:
      Table table = frame.AddTable();
      int columnsCount = 7;
      
      Enumerable.Repeat<Func<Column>>(table.AddColumn, columnsCount)
                .ToList()
                .ForEach(addColumn => addColumn());
      //or
      Enumerable.Range(0, columnsCount)
                .ToList()
                .ForEach(iteration => table.AddColumn());
      

      由于 ToList(),这些选项并不优雅,但都适用于我的情况

      【讨论】:

        【解决方案6】:

        声明一个扩展:

        public static void Repeat(this Action action, int times){
            while (times-- > 0)
                action.Invoke();
        }
        

        您可以将扩展方法用作:

                new Action(() =>
                           {
                               Console.WriteLine("Hello World.");
                               this.DoSomeStuff();
                           }).Repeat(10);
        

        【讨论】:

        • 如果它是一个函数,你可以用 return (times == 0) 递归调用它? func() : 重复(func, --times)
        【解决方案7】:
        Enumerable.Repeat<Action>(() => { Console.WriteLine("Hello World"); this.DoSomething(); },10).ToList().ForEach(f => f.Invoke());
        

        优雅是不是?

        【讨论】:

        • 似乎不适合我!你检查它有效吗?也许添加一个要点。
        【解决方案8】:

        我把它写成Int32 扩展方法。起初我觉得这听起来可能不是一个好主意,但当你使用它时,它实际上看起来真的很棒。

        public static void Repeat(
           this int count,
           Action action
        ) {
           for (int x = 0; x < count; x += 1)
              action();
        }
        

        用法:

        5.Repeat(DoThing);
        
        value.Repeat(() => queue.Enqueue(someItem));
        
        (value1 - value2).Repeat(() => {
           // complex implementation
        });
        

        请注意,它不会对值 &lt;= 0 执行任何操作。起初我打算否定,但在比较两个时我总是必须检查哪个数字更大。这允许差异在积极的情况下发挥作用,否则什么也不做。

        如果你想重复而不考虑符号,你可以编写一个Abs 扩展方法(value.Abs().Repeat(() =&gt; { });-5.RepeatAbs(() =&gt; { });),然后两个数字的差的顺序就无关紧要了。

        其他变体也是可能的,例如传递索引,或投影到类似于Select 的值。

        我知道有Enumerable.Range,但这更简洁。

        【讨论】:

          【解决方案9】:

          我觉得最方便的是:

          Enumerable.Range(0, 10).Select(_ => action());
          

          【讨论】:

            【解决方案10】:

            如果你的操作是线程安全的,你可以使用

            Parallel.For(0, 10, i => myAction(schema));
            

            【讨论】:

              猜你喜欢
              • 1970-01-01
              • 2010-09-07
              • 1970-01-01
              • 2014-05-13
              • 1970-01-01
              • 1970-01-01
              相关资源
              最近更新 更多