Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Other ways to deal with "loop initialization" in C#

Tags:

To start with I'll say that I agree that goto statements are largely made irrelevant by higher level constructs in modern programming languages and shouldn't be used when a suitable substitute is available.

I was re-reading an original edition of Steve McConnell's Code Complete recently and had forgotten about his suggestion for a common coding problem. I had read it years ago when I was first getting started and don't think I realized how useful the recipe would be. The coding problem is the following: when executing a loop you often need to execute part of the loop to initialize state and then execute the loop with some other logic and ending each loop with the same initialization logic. A concrete example is implementing String.Join(delimiter, array) method.

I think everybody's first take on the problem is this. Assume the append method is defined to add the argument to your return value.

bool isFirst = true; foreach (var element in array) {   if (!isFirst)   {      append(delimiter);   }   else   {     isFirst = false;   }    append(element); } 

Note: A slight optimization to this is to remove the else and put it at the end of the loop. An assignment usually being a single instruction and equivalent to an else and decreases the number of basic blocks by 1 and increases the basic block size of the main part. The result being that execute a condition in each loop to determine if you should add the delimiter or not.

I've also seen and used other takes on dealing with this common loop problem. You can execute the initial element code first outside the loop, then perform your loop from the second element to the end. You can also change the logic to always append the element then the delimiter and once the loop is completed you can simply remove the last delimiter you added.

The latter solution tends to be the one that I prefer only because it doesn't duplicate any code. If the logic of the initialization sequence ever changes, you don't have to remember to fix it in two places. It does however require extra "work" to do something and then undo it, causing at least extra cpu cycles and in many cases such as our String.Join example requires extra memory as well.

I was excited then to read this construct

var enumerator = array.GetEnumerator(); if (enumerator.MoveNext()) {   goto start;   do {     append(delimiter);    start:     append(enumerator.Current);   } while (enumerator.MoveNext()); } 

The benefit here being that you get no duplicated code and you get no additional work. You start your loop half way into the execution of your first loop and that is your initialization. You are limited to simulating other loops with the do while construct but the translation is easy and reading it is not difficult.

So, now the question. I happily went to try adding this to some code I was working on and found it didn't work. Works great in C, C++, Basic but it turns out in C# you can't jump to a label inside a different lexical scope that is not a parent scope. I was very disappointed. So I was left wondering, what is the best way to deal with this very common coding problem (I see it mostly in string generation) in C#?

To perhaps be more specific with requirements:

  • Don't duplicate code
  • Don't do unnecessary work
  • Don't be more than 2 or 3 times slower than other code
  • Be readable

I think readability is the only thing that might arguably suffer with the recipe I stated. However it doesn't work in C# so what's the next best thing?

* Edit * I changed my performance criteria because of some of the discussion. Performance is generally not a limiting factor here, so the goal more correctly should be to not be unreasonable, not to be the fastest ever.

The reason I dislike the alternate implementations I suggest is because they either duplicate code which leaves room for changing one part and not the other or for the one I generally choose it requires "undoing" the operation which requires extra thought and time to undo the thing that you just did. With string manipulation in particular this usually leaves you open for off by one errors or failing to account for an empty array and trying to undo something that didn't happen.

like image 737
Peter Oehlert Avatar asked Aug 29 '10 19:08

Peter Oehlert


People also ask

Can FOR loop be used without initialization?

A 'for' loop can be written without initialization. A 'for' statement usually goes like: for (initialization; test-condition; update). We can leave out any or all three of them at a time.

Is initialization necessary in for loop?

Variable initialization is mandatory, everywhere, for every variables. Variable initialization means that you must store a value in the variable before any attempt to use its contain. Here the initialization of i is misplaced because this place is for loop condition and the 0 will prevent from entering the loop.

How many initialization can a for loop have?

No, you can only have one initializing statement.

What is a loop variable initialization?

Variable Initializations. A local loop variable is one that exists only when the Loop Facility is invoked. At that time, the variables are declared and are initialized to some value. These local variables exist until loop iteration terminates, at which point they cease to exist.


2 Answers

Personally I like Mark Byer's option, but you could always write your own generic method for this:

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

That's relatively straightforward... giving a special last action is slightly harder:

public static void IterateWithSpecialLast<T>(this IEnumerable<T> source,     Action<T> allButLastAction,     Action<T> lastAction) {     using (IEnumerator<T> iterator = source.GetEnumerator())     {         if (!iterator.MoveNext())         {             return;         }                     T previous = iterator.Current;         while (iterator.MoveNext())         {             allButLastAction(previous);             previous = iterator.Current;         }         lastAction(previous);     } } 

EDIT: As your comment was concerned with the performance of this, I'll reiterate my comment in this answer: while this general problem is reasonably common, it's not common for it to be such a performance bottleneck that it's worth micro-optimizing around. Indeed, I can't remember ever coming across a situation where the looping machinery became a bottleneck. I'm sure it happens, but that isn't "common". If I ever run into it, I'll special-case that particular code, and the best solution will depend on exactly what the code needs to do.

In general, however, I value readability and reusability much more than micro-optimization.

like image 98
Jon Skeet Avatar answered Sep 29 '22 12:09

Jon Skeet


For your specific example there is a standard solution: string.Join. This handles adding the delimiter correctly so that you don't have to write the loop yourself.

If you really want to write this yourself an approach you can use is the following:

string delimiter = ""; foreach (var element in array) {     append(delimiter);     append(element);     delimiter = ","; } 

This should be reasonably efficient and I think it is reasonable to read. The constant string "," is interned so this won't result in a new string being created on each iteration. Of course if performance is critical for your application you should benchmark rather than guess.

like image 27
Mark Byers Avatar answered Sep 29 '22 12:09

Mark Byers