Replacing the for loop, using extensions

ddanbe 0 Tallied Votes 402 Views Share

I'm a great fan of extensions in C#. See also this previous snippet.
Extensions are really simple to use. Start with adding a static class containing static methods.
Note the special use of the keyword this in the Times extension method.
The next code snip is how you could use this extension, eliminating the use of the for.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace ExtensionSpace // use a special namespace for extensions
{
    public static class IntExtensions //extensions for the integer type
    {
        /// <summary>
        /// Repeat an action until EndCount
        /// Counting starts at zero, EndCount not included
        /// </summary>
        /// <param name="EndCount"></param>
        /// <param name="action takes one int and returns void"></param>
        public static void Times(this int EndCount, Action<int> action)
        {
            for (int i = 0; i < EndCount; i++)
            {
                action(i);
            }
        }

        //more extension methods . . .
    }
}

-----------------------------------------------------------------------------------

sing System;
using System.Collections.Generic;
using ExtensionSpace; // notice! 

namespace Extensiontest
{
    class Program
    {
        static void Main(string[] args)
        {
            // Write the square of the numbers 0 to 9 to the console
            10.Times(n => DoTheSquare(n));

            Console.WriteLine("=====================");

            // Equivalent for loop
            for (int i = 0; i < 10; i++)
            {
                DoTheSquare(i);
            }

            Console.ReadKey();
        }

        static void DoTheSquare(int n)
        {
            Console.WriteLine("The square of {0} is {1}", n, n * n);
        }

    }
}

Dani AI

Generated

Nice, compact idea from — a fluent 10.Times(...) can read nicely. A few practical points that expand on the thread (readability, naming, and perf) and that address gaps in the replies.

Extension mechanics and safety — an extension is just a static method the compiler rewrites to a normal call, so the integer literal is not magically converted into an object. Because the extension parameter is this int, there is no boxing of the integer; boxing only happens if the extension took this object. That explains why 10.Times(...) is allowed but keeps the usual value-type semantics.

Readability and naming — is right: terse fluent code can hide the fact that a counter is being incremented. If the index is not needed, an overload that takes Action is clearer and saves callers from an unused parameter:

public static void Times(this int count, Action action)
{
    for (int i = 0; i < count; i++)
        action();
}

Usage: 10.Times(() => DoSomething());

Performance and allocations — called out the indirection and delegate overhead. For small counts the cost is negligible; for hot loops it can matter. Simple strategies: measure (Stopwatch/profiler), prefer a plain for when profiling shows a hotspot, or avoid repeated delegate allocations by caching a noncapturing delegate once:

Action<int> act = DoTheSquare;   // cache method group
1000000.Times(act);

Alternatives — as mentioned, Enumerable.Range + List<T>.ForEach or an IEnumerable<T>.ForEach extension offer different tradeoffs: clearer intent for sequence-based work but still involve delegates.

Recommendation — use the Times form for readability and small tasks, pick explicit for loops for performance-critical code, and choose descriptive names (Repeat/Times/RepeatAction) so intent is clear.

Mike Askew 131 Veteran Poster Featured Poster

I like it!

But from a readability perspective in my opinion the for-loop is better. Can't skim read over the given extension example and know what it does too easily.

ddanbe 2,724 Professional Procrastinator Featured Poster

Of course the for loop still has its merits!
Your remark is correct. With this extension it isn't very obvious that a counter is incremented, like it is clearly visible, when using a for.
I'm in fact not going to use it very much myself I guess and stick with a for loop.
But still, I find it truly astonishing that a mere integer litteral can be used like an object.

Ketsuekiame 860 Master Poster Featured Poster

Actually we use a similar construct here; it's great for doing something X times but be warned that there are three levels of indirection and a delegate object construction plus the delegate invocation.
It has an impact when you get into large numbers.

deceptikon 1,790 Code Sniper Team Colleague Featured Poster

Hmm, I'm not sure I see the benefit in this case. Though variations on ForEach are relatively common. As an example, I have an extension method for ForEach over an IEnumerable<> and another that reports progress over iteration:

/// <summary>
/// Performs an action on each item in an enumerable collection.
/// </summary>
/// <typeparam name="T">The type of items in the collection.</typeparam>
/// <param name="action">The action to perform.</param>
public static void ForEach<T>(this IEnumerable<T> items, Action<T> action)
{
    foreach (var item in items)
    {
        action(item);
    }
}

/// <summary>
/// Performs an action on each item in an enumerable collection with a progress indicator.
/// </summary>
/// <typeparam name="T">The type of items in the collection.</typeparam>
/// <param name="action">The action to perform.</param>
/// <param name="startPercent">Starting progress percentage.</param>
/// <param name="finishPercent">Progress percentage after all items have been traversed.</param>
/// <remarks>
/// The progress percentage is passed to the action for use by the caller.
/// </remarks>
public static void ForEach<T>(this IEnumerable<T> items, Action<T, double> action, double startPercent = 0, double finishPercent = 100)
{
    double progress = startPercent;
    double progressRange = finishPercent - startPercent;
    double progressDelta = progressRange / items.Count();

    foreach (var item in items)
    {
        action(item, progress);
        progress += progressDelta;
    }
}

Replacing a for loop though? I can see it to a certain extent if you want to encourage a more functional style of coding, but use of for should be relatively rare these days anyway. Further, Linq already has Enumerable.Range which somewhat intersects with your extension method's purpose.

ddanbe commented: Thanks for sharing. +15
Ketsuekiame 860 Master Poster Featured Poster

There is no functional benefit, you are correct. But it does mean that some things compress down nicely so instead of being 5 lines they're only 1. That's pretty much it.

Also watch out , there is already a ForEach method on List<T> I don't know what the interaction would be here.

deceptikon 1,790 Code Sniper Team Colleague Featured Poster

Also watch out @deceptikon, there is already a ForEach method on List<T>

Yup, and only List<>, which is why the extension method is beneficial. ;)

I don't know what the interaction would be here.

In method overload resolution, more specific ownership takes precedence. So any locally defined method throughout the inheritance hierarchy will be preferred over an extension method. In this case, when both List<> and the IEnumerable<> extension method are in scope, List<>.ForEach will always be selected for a List<> object.

kplcjl 17 Junior Poster

Note that the name "Times" does not mean multiplication is being done, it means the routine defined is being repeated in a Linq expression, so maybe a better name may be called for. I can't think of a better name myself...

Be a part of the DaniWeb community

We're a friendly, industry-focused community of developers, IT pros, digital marketers, and technology enthusiasts meeting, networking, learning, and sharing knowledge.