Reputation: 147
I recently implemented this Linq-extension for usage in automated tests. I know that I can just use a for
-loop; however, also working in iOS, I like if you can read your method code almost like an English sentence when using them.
/// <summary>
/// Repeats an action x times
/// </summary>
/// <param name="times">The number of times an action should be repeated</param>
/// <param name="action">The action that should be repeated</param>
public static void DoIt(Int32 times, Action<Int32> action)
{
for (var i = 0; i < times; i++)
action(i);
}
This works very well and you can use it like this:
DoIt(5, i => {
// Here goes the code that should be executed multiple times
});
It is clear to read as "Do it 5 times with current index i". However, now that I wanted to use this call also in my regular code, I noticed that I can not use break;
or continue;
inside the DoIt
-Block.
Is there any possibility to escape the Linq expression using the existing language supported techniques?
Upvotes: 1
Views: 302
Reputation: 117144
I would be inclined to make the DoIt
method more powerful by modifying it in this way:
public static void DoIt<T>(
T initial, Func<T, bool> condition, Func<T, T> iterate, Action<T> action)
{
var t = initial;
while (condition(t))
{
action(t);
t = iterate(t);
}
}
In this way break
is handled in the condition
predicate - return false
and you will break
. And continue
is handled by calling return
in the iterate
function.
This allows your DoIt
method to iterate on any type - including regular classes or even anonymous types.
You could do this:
DoIt("X", t => t.Length <= 3, t => t + "!", t => Console.WriteLine(t));
Or even this:
DoIt(
new { counter = 0, sum = 0 },
t => t.sum < 50,
t => new { counter = t.counter + 1, sum = t.sum + t.counter },
t =>
{
Console.WriteLine(t.counter);
Console.WriteLine(t.sum);
});
Upvotes: 2
Reputation: 477230
You could slightly modify the Action<T>
into a Predicate<T>
:
public static void DoIt(Int32 times, Predicate<Int32> action) {
for (var i = 0; i < times; i++)
if(action(i)) {
break;
}
}
Now you can use a return
in your block. If you return false;
it will emulate a continue;
, if you use return true;
, it will emulate a break:
DoIt(5, i => {
if(i == 1) {
return false;//here we continue
} else if(i == 3) {
return true;//we break after the third time
}
Console.WriteLine("We run for the "+i+"th time!");
return false;
});
The downside is that the last line of your block must always contain a return false;
. You could see it as the predicate asking "Stop now?".
So to summarize:
return true; -> break
return false; -> continue
The solution is not very clean I admit: first of all, it is a bit confusing to work with booleans. You can solve this problem however by introducing an enum
:
public enum LoopResult : byte {
Break = 1,
Continue = 0,
End = 0
}
The End
is in fact an alias for Continue
, but can be seen as a more "natural" way to express it, in that case one can rewrite the DoIt
with a Function<Int32,Result>
:
public static void DoIt(Int32 times, Function<Int32,LoopResult> action) {
for (var i = 0; i < times; i++)
if(action(i) == LoopResult.Break) {
break;
}
}
And rewrite it as:
DoIt(5, i => {
if(i == 1) {
return LoopResult.Continue;//here we continue
} else if(i == 3) {
return LoopResult.Break;//we break after the third time
}
Console.WriteLine("We run for the "+i+"th time!");
return LoopResult.End;
});
But furthermore it doesn't make good reading to place return
parts into your code. Since C# has no (standard) precompiler I think there is however no way to automatically rewrite code such that one can use a break
and continue
expression. On the other hand a precompiler introduces an additional level of trouble and many programs can get ruined simply by starting to do a bit too much magic with precompilers.
Upvotes: 4
Reputation:
This would work. Return false to exit the loop prematurely.
public static void DoIt(Int32 times, Func<int, bool> action)
{
for (var i = 0; i < times; i++)
{
if (!action(i))
{
break;
}
}
Console.WriteLine("Done");
Console.ReadLine();
}
static void Main(string[] args)
{
DoIt(5, i =>
{
Console.WriteLine(i);
return i != 3; // returning false exits the loop
});
}
Upvotes: 2