Concatenation and the StringBuilder Class

Many decisions in programming are based on efficiency – the code that will accomplish the most in the least time or with fewer resources. That’s especially true when working with processes that repeat again and again where a small difference can be multiplied thousands of times.

Now that we have the beginnings of a game map, it’s time to see the output. In fact, the next step of generating the hallways would have been impossible without being able to debug them as they were being completed. In this chapter, we’ll look at how to output our ASCII character array to a working game map, the use of escape characters in C# and a comparison of different ways of combining strings, including the StringBuilder class.

This post is part of a series on creating a roguelike game in C#. For the latest chapters and more information, please visit the Official Project Page on ComeauSoftware.com. The current code for this project is also available on Github.

Going Back to the Form

Back when we were designing the Windows Form for the game itself, we put two buttons at the bottom of the form, Next and Generate. Now, we’re going to use these to quickly test the new mapping class.

  1. Open your main form in design view as shown above and double-click on the Next button to generate a new method for this button to follow when it’s clicked.
  2. Type the name of another new method that you’ll be creating, LoadMapLevel().
private void btnNext_Click(object sender, EventArgs e)
{
    LoadMapLevel();
}
  1. Right-click on the LoadMapLevel line to generate a new method from the Quick Actions and Refactorings menu.
private void LoadMapLevel()
{
    MapLevel newLevel = new MapLevel();

    lblArray.Text = newLevel.MapText();
    Application.DoEvents();
}

The actual call to the MapLevel class will probably be a bit more complex but right now, we’re just testing the map function so we just need something basic. This code declares a new instance of our MapLevel class and then assigns the output of the class MapText() function (which does not exist yet) to the Text property of the lblArray label on your main form. That’s the label that we expanded to cover the entire form and positioned in back of all the other controls.

The DoEvents() method tells the program to proceed with any other business that needs to be taken care of. I’ll get more into that later and why you should be very careful using it for anything other than testing.

Finally, add a new method for the Generate button the same as you did for the Next button.

private void btnGenerate_Click(object sender, EventArgs e)
{
    for (int i = 0; i < 101; i++)
        LoadMapLevel();
}

Now you have a Next button that will generate the next random map and a Generate button that will generate 100 of them, one after the other. This is one way to do a lot of testing quickly to find any program errors.

Walking the Array – Method #1

Back in the MapLevel class, we need to add a new function that will read every cell in the levelMap array and concatenate the DisplayCharacter of all the array’s MapSpace objects into a string that can be written to the form as shown in the last section. There are a couple ways to do this and we’re going to compare both.

If you’ve been following the series, you’ll recognize the first method easily enough:

public string MapText()
{
    //Output the array to text for display.

    string retValue = "";

    for (int y = 0; y <= MAP_HT; y++)
    {
         for (int x = 0; x <= MAP_WD; x++)
                retValue += levelMap[x, y].DisplayCharacter;

         retValue += "\n";
    }
    Debug.Write(retValue);
    return retValue;
}
  1. By the time this method is called, every cell in the array should have a MapSpace object in it, so the method uses a nested string to look at each one and append its DisplayCharacter property to an output string. If you’re typing this in, you’ll notice that Intellisense automatically supplies the DisplayCharacter property from the array reference because C# treats that reference just like the object that it contains. Also, notice the increment operator (+=) works on strings as well as numbers.
  2. Note the lack of braces on the second FOR loop. The reason for this is that it contains only one line. FOR and WHILE loops in C# can dispense with the braces if there’s only a single line but there needs to be a blank line afterward.
  3. The Debug.Write function is found under the System.Diagnostics namespace which you’ll need to add to the usings statements at the top of the class. This Write function sends messages to the Output window which you can view by selecting Debug -> Windows -> Output from the main menu.

Escape Characters

In the outside loop, you’ll notice the following line:

retValue += "\n";

This is an example of an escape character in C#. Escape characters are used to provide special formatting in strings or allow characters that would normally cause an error. The backslash indicates an escape character to C# and, in this case, it’s adding a new line to the string so that whatever control is displaying it will insert a new line at that position. This is a two-dimensional array, after all; we need it to display as intended.

The backslash can also be used to indicate that certain characters should be printed as they are. For example, the double-quote is often used to delimit strings in C#, or to indicate their start and end. What if you have a string value that needs to contain a double quote?

value = "The last book I finished was "Heretics of Dune" by Frank Herbert.";

That’s meant to be one string with double-quotes around the book title but it would actually cause an error because C# cannot distinguish between the use of the quotes as delimiters and their use to contain the book title. You could replace them with single quotes, which is a common practice, but the code can also be written this way instead:

value = "The last book I finished was \"Heretics of Dune\" by Frank Herbert.";

The backslashes notify the compiler that the quotes are to be included in the string as literal characters but the slashes themselves will not be printed to any textboxes or labels where you might use the values.

The backslash will only affect the character immediately after it, so if you entered a second double-quote, it would be treated as the end of the string.

Walking the Array – Method #2

Simply concatenating the characters works just fine but there’s also another way of manipulating strings in C#. Notice the lines in bold which have been changed.

public string MapText()
{
    // Output the array to text for display.
    StringBuilder sbReturn = new StringBuilder();

    for (int y = 0; y <= MAP_HT; y++)
    {
        for (int x = 0; x <= MAP_WD; x++)
            sbReturn.Append(levelMap[x, y].DisplayCharacter);

        sbReturn.Append("\n");
    }
    Debug.Write(sbReturn.ToString());
    return sbReturn.ToString();
}

In C# and other languages, strings are actually immutable, meaning that they can’t be changed. This might seem really surprising since we’ve been doing it all along but how the compiler handles something in the background is often very different from what you see on the screen.

In reality, when you change a string value, the compiler creates a new string in memory with the changes you asked for and disposes of the old one. If you’re just doing a few string operations, this isn’t a big deal. If you’re doing a couple thousand as we’re doing here, it can use excess memory and processing.

The StringBuilder class fixes this by putting the string value into a class that does the work of changing the original value while managing the resources. In addition to the Append method, it also has Insert and Replace methods that make advanced string operations a bit easier. The actual changes to the code aren’t that significant although remember that a StringBuilder object is not a string itself. It needs to be cast to a string with the ToString() function as shown here.

Which is Better?

Common wisdom says that the StringBuilder is the better way when doing a lot of work with strings as we’re doing but let’s test it for ourselves. I’m actually going to put this demonstration into a video so you can see it in real time.

As you can see in the video, the StringBuilder class actually is much more efficient. The manual concatenation took up to 2 milliseconds per map which is pretty fast but the StringBuilder reduced that to less than 0.1 milliseconds, consistently. In this case, it didn’t make much difference in the memory usage, possibly because it’s only 2000 characters.

The code in the video also references a couple of other items such as DateTime and the math operations that can be done on it. You can use this method to time the execution of any method so it’s a good idea to try it out.

After you finish entering the new code, try running your application.

Now you have a map display with the randomly-generated rooms and the start of some hallways! You can use the Next and Generate buttons to test it to make sure you entered everything right.

In the next chapter, we’ll finish those hallways.

DoEvents

Earlier, I mentioned that the Application.DoEvents() function, while handy, needs to be used with care. It’s use in this application is to allow the Windows Form to update itself each time its data is updated. If you try commenting out that line and then clicking the Generate button, you’ll notice that you only see the last map that was generated.

This is because the code for the map generation is still running and screen updates and other events are delayed until the code is finished. DoEvents tells the currently running code to pause long enough for any background processes like screen updates or button clicks to be handled. The problem is that, in a complex application, this can allow other parts of your code to execute which might cause errors that are hard to track down because you don’t know where they’re coming from. That’s one reason many programmers avoid DoEvents and its equivalent functions in other languages. Constantly pausing the code also slows down the application.

The alternative is to use asynchronous programming which allows you to start intensive operations such as the map generation in a separate thread that will not delay other functions. This is a more advanced concept that I’ll save for later in the course where it might be useful as we get into large numbers of calculations on different objects.

Working with Strings in C# – Microsoft Learning

Next –

Sign up for our newsletter to receive updates about new projects, including the upcoming book "Self-Guided SQL"!

We respect your privacy and will never share your information with third-parties. See our privacy policy for more information.

×