Programming in Visual Studio 2017 C# - Combined PDF
Programming in Visual Studio 2017 C# - Combined PDF
Audience:
This book is volume one of a three volume set and is intended for beginning Microsoft
Visual Studio C# (C Sharp) programmers, versions 2015 through VS 2017. This is a
hands-on book, with actual programs, starting in Chapter 1 and it talks about the
practical day-to-day, nuts-and-bolts programming that real people need to know.
Each topic has step-by-step instructions with numerous code examples and over 900
cropped and annotated illustrations. It explains the "why" of a program.
Prior Experience
If you already have moderate programming experience, especially in Visual Basic, this
book will expand your skills, giving confidence in the new language.
The goal is to have as much time on the keyboard, working with common business
problems. After studying this book and working through the examples, you will be a
proficient programmer – able to write real programs that do real work. You will be able
to read files, parse data, write to database, and build data input screens.
This book is different than most publications. Little time is spent on theories and
technical side-trips are rare. Starting in Chapter 1, you will immediately begin working
with loops, if-statements and string-manipulation. This means some topics, such as
conversions, numeric types, and other such concepts, are glossed over until they are more
germane to the concepts being taught.
Where other publications might spend a page or two on a topic, this book dives into the
most common and most useful ways to solve a problem. For example, over 80 pages are
devoted to opening multiple forms and how to pass data between them. The parsing
chapter devotes 70 pages to this subject, covering delimiters, CSV, Tab, Excel, and other
techniques. This is not over-kill. You will find these address real-world data-processing
problems. I cover the tips and tricks you will need to know.
Chapters often show different techniques for the same problem and the benefits and
drawbacks of each are explained. If there is a chance of making a mistake in
punctuation, style, or logic, the examples show how to resolve them. Compiler errors are
scattered throughout the book and there is a comprehensive alphabetic error reference in
the appendix, showing likely causes and recommendations.
A side-effect of this first volume will be a library of utility modules that can be used in
all of your programs. These utilities can automate mundane tasks, such as parsing
delimited files, punctuating phone numbers, street-addresses, and capitalizing proper
names. These libraries will save boat-loads of time and will be literally useful in all your
programs.
You will also notice none of the examples use Console-applications (DOS-like
programs) – all are Windows forms. Besides being more visually interesting, they allow
greater feedback while developing and the programs more accurately reflect what
happens in the business world.
These volumes do not cover web-development but the programming skills taught are
100% transferrable. SQL databases are introduced, but four lengthy chapters only
scratch the surface. However, if you fear treading in this area, these four chapters will
get you started and will show relatively advanced techniques.
Why C#?:
The language is capable and mature. Even if this is your first programming language,
you will be pleased at its versatility and ease.
These three volumes are teaching books and because of that, it makes a poor reference
guide. To get the most utility, start at page one and work your way through chapters.
Each chapter builds on the previous. It takes time and effort to learn programming. As
you work through the chapters you must sit in front of the compiler and write the code.
This series was divided into three volumes, partly to aid in printing, and partly to make
the chapters more accessible.
Volume 1:
1 Introduction to the Editor
2 Introduction to Loops
3 Conditional Branching
4 Strings
5 Numbers and Dates
6 Utility Functions
7 Advanced Utility Functions
8 Class Libraries
9 Variable Scope
10 Form Controls and Events
11 Calling Multiple Forms
A Compiler Error Messages
B Compile and Distribute
Volume 2:
12 ASCII Files
13 Parsing Tab and CSV Files
14 INI Files
15 XML and App.config Files
16 Windows Registry
17 Reading Excel and Access
18 External Programs (Shell)
19 Wait, Delays, Pauses
20 Printing
21 Formatting
Volume 3:
22 Arrays
23 File Manipulation
24 Console Applications
25 SQL Databases
26 SQL Record Edits
27 SQL Data Grids
28 Data Grid Cell Editing
C Installing SQL Server Express
D Routines (of Interest)
Thank you
Thank you for purchasing this book. I hope you enjoy it as much as I have had writing it.
Comments and suggestions are welcome.
This book was written with Visual Studio 2013 through 2017's Community Edition, with
sections referencing Microsoft Office and Microsoft SQL Server 2016 Express.
© 2017 by Tim R.Wolf. All rights reserved.
Original text written with WordPerfect version X7. Illustrations created with Corel's
PaintShop Pro, version X8.
The Compiler and Other Tools:
http://www.visualstudio.com/en-us/products/visual-studio-community-vs
This book is applicable to VS 2010 and newer, with an emphasis in version 2017.
For the database chapters you will need a copy of Microsoft MS-SQL database or a copy
of Microsoft's free downloadable "MS-SQL Express". Details can be found in the
Appendixes.
Printing conventions are designed to help you find pertinent code quickly.
Code examples are show in plain text, white text boxes or shaded text boxes. The
boxing and shading gives you a clue about how "solid" or complete the code is. If you
are skimming through the chapters looking for something, the formatting style tells you
how far along the text is.
At the beginning of each chapter is a summary or outline of the most common commands
or coding techniques. These act as a reference for the details that follow.
Code that is in-line with the text, and without a text-box border, is a discussion or
theoretical block and you are not expected to type or test. For example, this is a
discussion code block:
Paragraph Numbering:
There may be numerous steps to solve a programming goal and you will find I am a fan
of numbering them. Within each section, you will find numbered steps, 1, 2, 3 or
A, B, C.
Setup or pre-requisite steps, as well as testing, are numbered "A, B, C". Actual
programming steps (devoted to the section you are reading), are numbered "1, 2, 3".
Terminology:
"Variable"
A variable is a named place-holder for a value. For example, a field may be named
"FirstName" – the variable's name holds anybody's name, hence its variableness. Other
famous variable names include "i", which typically is nothing more than a counting
variable, where "i" might equal 10.
While programming, make-up or invent your own variable names. The names should be
descriptive, which helps identify its purpose later in the program and I favor long,
descriptive names: frontSideDelimiter and RecordCategoryCode.
"Strings"
Textual information (words, phrases, sentences, letters, and characters) are all called
"strings". Strings can be concatenated (appended to each other), or parsed (taken apart
into smaller, constituent pieces).
Examples of strings:
"Dog"
"M"
"A sentence, such as this one."
"Product ID: 3003-22"
"123 Elm Street"
"65,002"
"10/06/2007"
Strings, by their very definition are nothing but blobs of text. This is where most of your
computing time will be spent. Numbers and dates can be represented as strings, but
mathematical calculations can not be performed against them.
"literal"
A literal is an explicit text string, manually coded into the program. Some call this a
"hard-coded" value. Literals are enclosed in quotes. For example,
where FirstName is a string variable (declared as a string) and "John Smith" is a literal,
typed in quotes. Literals can live inside of other statements, such as:
"Numbers"
Numbers are more interesting. The general intention is to use them for calculations, such
as a summation, count, or other mathematical operations. There are amazingly a dozen
different numeric types, where the most common are "integers" (whole numbers, such as
1, 2, 3, 0, -5, 200, etc) and real numbers (such as 3.24, 7/8ths), which are also called
Floating Point numbers.
"boolean"
Booleans are yes-no, true-false variables and they are neither a string or a number.
Booleans are often expressed in the terms of an if-statement, such as:
if (FirstName == "Smith")
if (EndofFileSwitch)
if (EndofFileSwitch == true)
"Declare"
The text will say "declare" (or define) a variable. This means you are creating a new
variable by telling the compiler what type of variable it is, and its name. Variables are
created by typing a statement, for example:
where "string" declares a new variable "of type 'string'" with a declared name of
"FirstName". Variables do not have to be initialized with a value when they are
declared. For example, it can be declared on one line and populated on another:
string FirstName; //Defined or declared...
(then later...)
FirstName = "John Smith"; //Initialized or populated
int loopCounter;
loopCounter = 15;
and with all variables, you can assign other similarly-typed values to another variable:
int loopCounter = 0;
int someOtherCounter = 200;
loopCounter = someOtherCounter; //where loopCounter now equals 200
After the first couple of chapters, I tend to prefix my variables with an indicator showing
what type of variable these are, for example, "str" for string, "i" for integer. This is a
documentation aid, and I use this style in my own programming:
Not all developers use prefixes and some are violently opposed to them.
Legal Stuff:
Visual Studio, Excel, Access, Visual Basic, Microsoft SQL, SQL Express, are Microsoft
Products with all of their copy-rights and trademarks
This chapter acquaints you with Visual Studio's editing environment and basic syntax.
This is an introductory chapter and is hands-on; you will begin writing a program starting
on the next page.
Topics Covered:
1. Begin by launching Microsoft Visual Studio or Microsoft Visual Studio Express, from
either the Start Menu or from an icon on your desktop.
2. Once the program opens, select File, New, Project from the top menu.
Or alternately, click "More project templates..." from the Start Page:
• On the Installed Templates tree, left-side, expand "Visual C#" by clicking the
triangle (it may be already expanded).
• Within the tree, select the "Windows Classic Desktop", then choose "Windows
Forms App" (in older versions of Visual Studio, select "Windows"):
• Click OK.
4. The editor opens to a screen divided into several sections and some of the window panes
may not be displayed on first launch. Here are the main panes you need to know and
each are described in more detail later in this chapter:
• Properties - Individual object properties, such as name, color, size, etc. In this same
pane are Events, such as "on click", "minimize", and others. The properties and
event panels are context sensitive.
• ToolBox - "Flyout" menus for design tools (for adding textboxes, buttons).
• Error List - A list of compiler and syntax errors while coding. This is likely hidden
and the steps to expose are described below.
When you open your first editing session, some panes, such as Error List, may not be
visible on the design screens. Open with these steps:
At the bottom of the editing screen is the Error List, but often, it is obscured by the
Output tab. Click the Output Tab's "X" to close that window.
Finally, near the top, just above the Form1 form, is a tab-bar, where "Form1.cs [Design]"
is highlighted. In a moment, the "Code View" will appear as a second tabbed item on
this top-row. Use these tabs to switch between the form's design and code editing views.
First Program:
Begin the first program by adding a "text box" (data-entry field) and a button to the
default form, Form1. The things you are adding are called "objects."
• Click the Toolbox tab, found on the left-edge of the screen. The menu will fly-out.
(If the Toolbox flyout is not shown, select View, "Toolbox" from the menu-bar at the
top of the screen.)
• From the Toolbox flyout, scroll to "Common Controls"; open the list by clicking the
triangle (older versions of Visual Studio use "+").
3. Drag a second Button, "button 2" to your form. This will be used in a later example.
Click and drag the new objects, maneuvering them until they appear similar to the
illustration below:
This opens the screen into Code View - e.g. programming view and stubs-in a new
method called "button1_Click". Notice the new tab on the top row: "Form1.cs", where
".cs" stands for "C-Sharp". Alternately, in Solution Explorer, right-mouse-click
"Form1.cs" and choose "View Code / F7" and type the following code by hand.
"IDE1006 Naming rule violation". This is new to Visual Studio 2017 and indicates a
method or procedure should begin with a capital letter (e.g. "Button1_Click"). For now,
this can be ignored.
(If the Error List is not visible on the bottom-left of the screen, select top-menu View,
"Error List". Or, the tab may be obscured by the "Output" tab.)
Additional notes: This book was written using Visual Studio 2017 Release Candidate 1
and may change once the program is released.
Type the following statements between the opening and closing {braces}, starting with
the line "string myFirstWordA;" Each word is upper/lowercase sensitive and each
statement ends in a semi-colon ( ; ). Indent with spaces or tabs, but you can be sloppy
and the editor will align and indent after pressing Enter.
myFirstWordA = "Cat";
label1.Text = myFirstWordA;
}
Important Notes:
In the code, pay close attention to the opening and closing {braces}. The new code must
live between the braces directly below the "private void button1_Click" statement.
Notice the two closing braces at the bottom of the program – these belong to the
solution's namespace and are not part of the button1 block.
Almost every statement, but not all, ends with a semi-colon, " ; ". These mark the end of
the statement. Longer, more complicated statements may take multiple lines in the
editing screen, but it is the closing semi-colon that marks the end of the command. As
you progress through this and the next chapter, the rules for semi-colons become evident.
Indenting with spaces or tabs are optional but considered good programming style and
the editor will help enforce indents.
A. Run the code by pressing the keyboard's F5 key (or pressing the green Start arrow on the
top tool bar).
Click button1;
label1 should change to "Cat".
button2 will do nothing as there is no code written for it.
B. Close the newly-run program by clicking the "X" in the upper corner; this closes the
program and returns to the editor, probably in code view.
For all of the examples in this book, it is assumed the test program is closed before
returning to design or code view. If you forget to do this, a lovely error message greets
you as you try to edit your program: "Changes are not allowed while code is running."
Build Errors:
Note "There were build errors. Would you like to continue and run the last
successful build?"
Click "Do not show this dialog again" and then "No". You never want to run the
"last successful" program – you always want to see the bugs so you can correct them.
When "button1" was clicked in the running program (not in the editor), you initiated an
"event" (button1_click). The code within the opening and closing "squirrelly braces"
defined what happens. This block of code ran for the duration of the routine, up to the
closing brace.
The statement "string myFirstStringA;" creates a holding area (a variable) that stores
text-data – also called a "string". The name, myFirstStringA is an invented name.
For the next example, modify the program, changing all strings to numeric "integers".
The program will add the numbers together and display the results. Follow these steps:
1. Confirm the previously-running program was closed. If needed, click the "X" in the
upper corner or by ending the task on the Windows taskbar. This should return you to
the code-editor. Jump into Code View, when you return to the editor.
2. Scroll down the program code and locate the button1 event ("private void
button1_Click").
Make the following changes to the code (replacing all of the code originally typed). As
before, watch upper and lower cased letters, semicolons and braces:
• Between the opening and closing braces in button1's click event, delete all
statements previously written, but do not delete the braces. If the braces are
accidentally erased, re-type them.
• Create two "int" (integer) declarations (see code example, below), including
semicolons:
int valueA;
int valueB;
• On the next two statements, initialize (assign) each variable with a default value, in
the example, the numbers 3 and 2. (Integers can contain whole numbers, such as 0,
15, 99, -4, etc, but no fractions).
valueA = 3;
valueB = 2;
• Write a statement that changes 'label1.Text'. This command adds values A+B,
converts them to a string, then assigns the results to label1. Note in particular the
parenthesis, the period, and a closing semicolon.
valueA = 3;
valueB = 2;
3. Run the program by pressing F5. Click button1 to run the event.
Comments:
The keyword "int" declares an integer (positive and negative whole numbers: 1, 2, 3, -55,
etc.). Be aware there are a half-dozen or so other numeric-type variables including
floating point, and these will be covered in later chapters.
When reading a line of code like this, read it from the inside-out – starting at the inner-
most parenthesis:
• "Convert.ToString (5)"
– the numeric values, once added, are converted to a string (think text).
• "label1.Text = "
Move (assign) the converted-to-string text to Label1.Text via an equal sign.
If you've worked with Visual Basic before, you may know that VB implicitly converted
numerics to strings and it had little concern about moving numbers into text fields. C# is
not as lenient (especially versions before VS 2008). If you run afoul with this rule, you
It is easy to forget the "Convert.ToString" when working with numbers. Simulate this
type of compiler error by doing the following:
1. If needed, stop the prior running program by clicking the "X in the upper right, returning
to the editing environment.
2. In code view (if needed, click on the top tab "Form1.cs" or by double-clicking button1).
Change the line:
4. Press F5 and attempt to run the program. The compiler should immediately error with
and the program will not run (see illustration, below, noting the Error List). If the Error
List does not appear at the bottom of the screen, choose top-menu, "View, Error List".
5. In the Error List window, double-click the error message text. This jumps the cursor to
the failed line in your program. Note the squiggly line.
It is also easy to mis-type the 'dot-Text' after the label's name. The editor, which
normally helps while typing, can also automate the problem. Make this minor change in
your program:
4. Finally, Visual Basic programmers often forget to type the ".Text" phrase all-together,
which was perfectly acceptable in that language, but not in C-Sharp. When you do this,
the compiler errors with:
Which basically means the new string (Convert.ToString) does not have a string-variable
to arrive at – without the .Text property, C# refuses to play. Correct the error by
returning the word .Text to its proper case and position.
"label1" is an object added to the form, and like all objects in Visual C#, they have
properties. Properties can include things such as the font, where the label is on the
screen, and a myriad of others controls. In this case, the ".Text" (dot-Text) is a property
that describes what the label "says" on the form.
As you were typing ".text", the editor offered help by suggesting a correct spelling, via a
feature called "Intellisense." In your program, try this by deleting and re-typing the .Text
phrase. As you partially type the keyword (".te" or ".tex"), arrow key to the correct entry
in the list and press the space-bar. The editor auto-completes the entry.
If the error makes no sense and line you were previously working on is flagged, check
the following:
• Is the current line, or the line directly above, missing a closing semicolon or closing
parenthesis?
• Do you have a closing semicolon on a line that is not supposed to have one?
In particular, these types of statements do not have semicolons at the end of their
lines:
if-statements,
loop-statements and
"private void..." declarations
For these statements, are opening and closing {braces} present? (These are covered
in detail in future chapters.)
• And probably the most common problem, are closing braces in the right position?
Especially near the end of the program. Be sure your code lives within an opening
anc closing-brace-pair – avoiding the last two in the program. Remember to click
behind a brace to see where its partner lives. They must match and must be lined up
properly.
The two braces at the very bottom of the program should never have anything typed
past them. They belong to these statements:
Compiler errors may prompt with "There were build errors. Would you like to
continue with the last successful build?"
As a reminder, always select checkbox "Do not show again" and click No.
In other words, you would never want to run the previous version of your code (before
the new bugs were introduced – you really want to see the current bugs). I often wonder
why this is even offered as an option. If you accidentally click "[x] Do not show and
then Yes, you can fix this problem with Tools, Options, "Projects and Solutions", "Build
and Run". Set "On Run, when build or deployment errors occur" to "Do not Launch".
If you delete an object, such as button1 (or if you delete the underlying code and leave
the object on the Form), and try to run the program again, you may see this compiler
error: "Form1 does not contain a definition for "button1_Click..."
In Visual Studio, versions 2015 and older, this would cause a colossal crash. Double-
clicking the error will take you to a particularly-nasty-looking panel with a lot of code
you did not write. The highlighted line will be the culprit. Without fear, delete the line
and run the program again.
Starting Visual Studio 2017, the editor recovers more gracefully from these types of
deletes and it unobtrusively orphans the code and does not show an error.
Appendix A is a list of common errors and their solutions. The list is sorted
alphabetically by the error message's text. Most of the errors students will encounter
while using this book are listed. Starting with Visual Studio 2017, error messages and
warnings get a numeric code. Not all codes are listed; search by text.
C# is always concerned about where a variable is defined and when it can be used. This
section attaches code to the unused button2 and demonstrates how variables are cleaned
up and discarded. The routine will attempt (and fail) to use the integers, valueA and
valueB, defined above in button1_Click. Basically, if a variable is declared in one
routine, it is not available or visible to other parts of the program. The correct lingo is
the variables "fell out of scope." This is best described with a short demonstration.
1. Return to the form's design view – see the top-row of tabs, clicking "Form1.cs [Design]".
2 Double-click button2 (not button1). This creates a default event (button2_Click) for
that object.
Add this new code between button2's opening and closing brace. In this code
illustration, button1's previously-typed code is also shown. Button2's code may appear
above or below button1's statements; the order of the block (block of code) does not
matter.
As you type this new code, expect compiler errors in the editor's bottom Error List and
red-squiggly-lines and expect the editor to change what you have typed. Force these
editing changes into the module:
valueA = 3;
valueB = 2;
label1.Text = ConvertToString (valueA = valueB);
}
The editor will be angry and a number of errors will show in the error list.
3. Attempt to run the new code by pressing F5. Notice the compiler errors:
Comments:
Although "int valueA;" was declared in the same program, it was declared within
button1. Because of that, the variables are not visible to other routines. A more
professional explanation is the "scope" of the variable was limited to button1. At
button1's closing brace, when button1 finished running, the variables are discarded and
are no longer available.
Variables with limited scope are double-edged. It is good that they are destroyed - this
leaves the program with a smaller memory footprint. It also keeps you from making
mistakes - especially in large programs where the same variable name ("myCounter")
might be used in different routines. Often, it is best when values die before they are
mistakenly used somewhere else. (Other chapters discuss variable scope and how to
make longer-lived variables)
5. Return to the form's design view (see "Form1.cs [Design]", along the top row of tabs).
6. From the form's design view, highlight button2, and press the keyboard's Delete key to
remove the object.
7. Double-click button1 to return to the code view (or use the Form1.cs tab). Scroll down
the program and note that button2's logic ("private void button2_Click") survived
the delete. This is normal. Deleting the object does not remove the associated code.
Outside of cluttering the program, there is no harm – provided the orphaned code does
not have errors.
Leaving button2's code in place, Press F5 again to re-run the program. You will get the
same compiler errors ("the name ValueA does not exist").
8. Return to code view and delete all statements dealing with button2; this includes the
everything from the "private void button2..." statement, through it's closing brace.
Press F5 to run. The program should run without errors.
"textBoxes" are the data-entry fields seen in a Windows program. The boxes hold data
such as a name, phone number or address and the end-user tabs into the field and types.
The following examples introduce basic methods for building and using text boxes and
later chapters will cover this topic in greater detail.
Visual Studio, version 2015 and older, will prompt to Save. If prompted, don't bother.
j Starting with Visual Studio 2017, it automatically saves, without prompting. If you
accidentally edited, and want to discard the changes, press Shift, Close Solution.
C. On the newly-displayed blank form, create a similar program as before by clicking and
dragging the following objects from the ToolBox flyout menu onto the default Form1:
Double-click (or drag) each object from the ToolBox flyout and arrange them to look
something like the illustration below.
The object's name (e.g. textBox1) can be changed to any name of your choosing, but the
name must be a single word or phrase, without embedded spaces. For example, textBox1
could be renamed to "MyFirstDataEntryField", where most developers prefer to use
"CamelCasing," with initial caps on each word.
Single-click each of the text boxes until you have identified "textBox1", again looking at
the property's name. Arrange the fields on your screen so it is the topmost box in your
design window.
The goal of this example is simple: take two text strings and combine them into one new
string, displayed at label1. The program will start with two hard-coded values, "Cat" and
"Dog".
1. From the form's design view, double-click "button1" to jump into this object's code
view.
2. Add this statement inside of button1's _Click event. As before, the code lives between
the opening and closing braces and, as always, watch upper and lowercase. End the
statement with a required semicolon.
Because the string literals "Cat" and "Dog" were hard-coded, the two textBox fields
were not used in this part of the example.
The line
label1.Text = "Cat" + "Dog";
instructs the compiler to concatenate two literal strings and place the result into
label1's .Text. Except for the fact that you "added" two strings together using a plus-
symbol, this shouldn't be too big of a surprise. (VB and Excel programmers always used
"&" for string concatenation, C# uses a plus.)
The code seems reasonable and on the surface it should work. The problem is this: C#
never works on an object's name by itself – it always needs to manipulate what I call a
'dot-property.' For VB programmers this is a constant surprise because VB assumed
what was not explicitly stated.
Before any value in a textBox can be used, you must explicitly state the .Text property,
as in "textBox1.Text". C# is persnickety in this regard.
Testing:
In design view (where the Form's Design is visible; see top-tab-bar "Form1.cs[Design]"),
single-click textBox1 to highlight the object. In the Properties window, lower-right
corner of the editing screen, scroll down the list. Note properties such as Background
Color, Border Styles. Near the bottom of the list, find the ".Text" property.
The ".Text" is the same ".Text" used earlier and is where the value of this object lives.
From design view, this property's default value can be pre-populated and the end user
can change at run time. Both the design and the user's view change the same value.
1. If you haven't already done so, close the previously running program by clicking the "X".
Return to the Form's design view (see the top-row tab: "Form1.cs [Design]")
Using the same properties list, lock a field so users cannot edit - forcing them to accept
the defaults.
As a last example, combine the two textBoxes with a third literal, adding the word
" and " between "AngryBeavers".
5. Modify button1's click event by adding the literal " and " between the two textBox
values. Double-click button1 to open Code View:
To make this look proper the " and " needs leading and trailing spaces between the
quotes. Without the spaces, the result would be "AngryandBeavers".
This concludes this introduction to C#'s editing environment and a basic textBoxes. The
next chapters go into greater detail, including how to audit and limit what types of data
users can type.
By now you should have a general feeling for how the editor works and how to read and
interpret various compiler errors that we all accidentally type while programming. The
next chapter continues with variables and loops.
Exercise A
Create a program that prompts the user for their first name, mid-name, and their last
name. Use button1 to combine the three names into one string and display the results in
a label. For example, the user may type "John", "Q." and "Smith", with the final results,
"John Q. Smith". Label the fields so they look pretty.
B. Using the Toolbox flyout, drop three textBox fields, textBox1, textBox2, and textBox3
onto the form.
C. Change each textBox's Name property, from textBox1 to a more appropriate variable
name:
FirstName
MidName
LastName
E. Add another label, "label4", leaving it with its default name and default text value. This
will hold the answer, as generated by button1.
Double-click the newly-dropped button and create an on-click event that assembles the
three text fields and places the results in Label4. For example, "John Q. Smith". When
doing this, how can you avoid this result: "JohnQ.Smith"?
Exercise B
From the Visual Studio Getting Started page, note the Recent Section.
Ignoring the Recent section, confirm you can find and re-open the project/Solution
manually by clicking File, "Open Project/Solution"
B. From the Visual Studio "Getting Started" landing page, note the Recent section.
D. Once the solution opens, look in the Solution Explorer pane (top-right side of editing
screen). "Other-mouse-click" Form1.cs, choosing "View Designer" or "View Code".
Later chapters discuss better places to save your projects than the defaults
Microsoft chooses.
Loops tell the computer to repeat a group of instructions multiple times. For example, a
program may need to step through each record in a file or it may need to loop through a
string, looking for a particular set of characters. There are several different types of
loops and while they may seem interchangeable, each is designed for slightly different
needs.
Probably the two most useful loops are "while-loops," looping while a condition is true
and for-next loops, which loops a pre-determined number of times. There are also for-
each loops (processing each element in an array) and nested loops. This chapter
demonstrates each and a solid understanding of the topic is important.
Topics:
• "while" loops
• incrementing with <loopCounter>++
• MessageBox.Show
• Infinite Loops
• ctrl-alt-Break debugging
• scrollbars
• "do" loops
• for-next loops
• Controlling loops with textBoxes
• Interrupting loops with "continue" and "break"
• Nested loops
The loops demonstrated here are simple: print your name ten
times, build geometric shapes and other nonsensical things, but
don't loose track of the goal – learning how loops operate. For
this chapter, there was no need to make the loops any more
complicated. Later chapters make extensive use of these
constructs.
do-loop, Overview
int iloopCounter = 0;
do
{
//Always executes at least one time;
//note semicolon on while-statement
//Looping backupwards, 10 to 1:
Begin the first loop exercise by building a new project in the same manner as the
previous chapter and this same project can be used for all examples in the chapter.
B. From the Visual Studio top menu, select File, New Project,
Select "Windows Form Application"
Accept default filenames, etc.
(See the previous chapter for illustrations)
C. From the ToolBox flyout (Common Controls), drag or double-click these objects to the
new Form window.
textBox1
textBox2
button1
D. Locate textBox1 (by clicking each textBox and looking in the Properties pane for the
object's name).
• Click the textBox object one time to highlight, then scrolling through the Properties
window (on the lower-right of the screen), set the "Multiline" property to True. (If
you don't see the Properties panel in the lower-right corner of your screen, see menu
choice View, "Properties Window")
• Alternately, set the Multi-line property graphically: Click the textBox, then click the
flyout menu-arrow, checking [x] MultiLine:
Re-arrange/move the fields and buttons until the screen looks similar to this illustration.
You may need to resize the form and move fields to make room. Resize the form by
dragging the form's handles (illustrated). To move fields and buttons, click and drag.
"while" loops are probably the most common and most useful looping command. They
have the following characteristics:
Imagine this loop: "while money in wallet go shopping; if there is no money, don't go
shopping." This is the key feature of the loop - check before running. There is a
possibility the loop does not execute.
The first example will loop 10 times, printing some text each time. Later examples
expand this idea by introducing counters and line-breaks.
Write an admittedly pointless loop that prints the word "hi" 10 times.
Program 2.1
1. From design view (built on the previous page), double-click button1, opening button1's
code-view.
When writing these statements, be sure the statements are typed between button1's
opening and closing braces. And notice how the two statements end in semi-colons.
Begin the loop with the keyword "while", typed with a lower-cased "w", followed by a
parenthesis and a conditional-statement that in English reads, "while loopCounter is less
than or equal to 10."
Beneath the while loop is a new set of opening and closing braces, which you must
manually type. This is where the loop's interior statements will live. The blank line
between the variable declarations and the loop is cosmetic.
}
}
Complete the routine, by typing the two statements inside the loop and a MessageBox
statement after the loop's closing brace, but within the button1_Click's closing brace.
The position of these statements is important:
MessageBox.Show("Done");
}
where:
• Stylistically, indent your code with either spaces or tabs; The editor automatically
helps with indenting.
• As you type, count the opening and closing braces to make sure that each brace has a
matched pair.
When typing braces, type the opening and closing braces, one after the other,
before typing the code between. Put a couple of blank lines between, giving
yourself room for the interior statements. Typing the braces in this fashion
helps you keep track of their position. Starting in VS2013, typing the
opening brace, the editor automatically types the closing brace.
Many beginning programmers get the opening and closing braces confused with the
other braces in the routine. It is important they are paired properly. Although the
compiler does not particularly care about indentation, you can get confused about where
the closing braces belong when the formatting is poorly maintained. When typing the
while-statement, I recommend typing it in this fashion in order to keep the braces paired:
Type the "while (loopCounter <= 10)" (no semi-colon); Press Enter,
Type the opening brace, {
Press Enter, Enter (twice)
Type the closing brace, }
After typing the opening and closing brace, place the cursor after either brace and the
editor will highlight the other matching brace. If indented properly, the braces will be
easy to interpret.
Some developers code their braces in this fashion, which is also acceptable:
Results: textBox1 shows "hihihihihihihihihi" along with a simple dialogue "Done". Note
10 concatenated strings.
There are several new concepts in this program. Starting at the top, a variable,
"loopCounter" is declared as an integer (a whole-number, counting variable), followed
by a statement that initializes a default value 1.
Many C# programmers prefer to declare and initialize on the same line, using a
condensed syntax. Either style is acceptable.
int loopCounter = 1;
vs
int loopCounter;
loopCounter = 1;
There is nothing special about the name "loopCounter" – this is an invented name. But
no matter what name is used, remember C# is case-sensitive. If you spell "loopCounter"
with a lowercased-el and a capital-C, you must use that same scheme throughout the
module.
Definitions
camelCasing Where the first letter of the first word is lower-cased and
all other words are uppercased. This is usually used for
variables within the current routine.
Hungarian Notation is now frowned up, but is used in this book to help
illustrate and clarify the variable's purpose.
Examine the while-statement for a moment. "while" <a condition is true> do all the
"stuff" between the next pair of braces. If the condition were initially false, none of the
code within the braces executes and program passes control to the line after the closing
brace, which in this case, is the MessageBox.
In this example, "loop while the counter is less-than or equal-to 10" is guaranteed to run
because the variable was initialized to one.
There are other ways of making this loop run ten times. You could, for instance, do any
of the following, all with the same results:
Appending a String:
The example prints the word "hi" ten times in textBox1, one after the other. To
accomplish this, the code relied on a somewhat oddly-worded programming trick:
• "Take what ever is currently in textBox1 and add the word 'hi'". With strings,
'add' means append or concatenate.
• When the loop first starts, textBox1 is empty so the first iteration concatenates
"hi" to an empty box.
• On the loop's second iteration, the box contain the first loop's "hi", then another
copy is concatenated. Remember, "take what ever is in textBox1 and add a new
"hi" – putting the results back into textBox1.
After ten iterations, the loop will have appended ten "hi"'s, giving: "hihihihihihihihihihi".
Incrementing a Counter:
The loopCounter statement uses the same trick, except with a numeric value:
loopCounter = loopCounter + 1;
The counter was initialized at 1 and then, on the first iteration, a "+1" is added the
current value (one), giving a total 2. On the next loop, the current value equals two plus
one, for three. The statement says, "take what ever the current value is, add one, and put
the results back into the current value." Mentally you can follow the variable's progress
through the loop.
At Loop #2,
"hi " + "hi " is printed
loopCounter = 2; add + 1, for a total of 3
Print "hi hi " + "hi "
At Loop #3,
loopCounter = 3; add 1 (loopCounter = loopCounter + 1) = 4
"hi hi hi " + "hi " ... etc.
= textBox1.text + "hi " may seem like unnecessary gibberish. Why not use a more
straight-forward statement, such as: textBox1.Text = "hi"; Try this now.
With this change, the computer still prints the word "hi" 10 times, but each iteration
replaces the previous – it did not append or concatenate. The computer takes the
instruction literally and the revised command says to 'Put "hi" in textBox1.' This runs
ten times with each "hi" replacing the earlier ones. There would be no consideration on
the previous contents and all ten copies happen in a millisecond – too fast to see. The
end result would be a single "hi" in the box, the tenth iteration.
Here is the key to the loop: Ultimately, the loop enters its 10th iteration - where it prints
"hi" a 10th time. The counter increments to 11 and wraps around to the top, where the
"while" condition again checks the current value. Since it is now at 11, the loop ends
and program control falls to the statement after the closing brace.
++loopCounter; or
loopCounter++;
Using either a "++" in front or behind the variable accomplishes the same goal.
(Technically, if the ++ is in front, it adds one to the variable before using the variable in
other inline calculations. If placed behind++, it adds one after using the value in the
other calculations. This is subtle and in practice, there is little difference between the
This new syntax will cause problems if both styles are mixed on the same line. For
example, this statement is an infinite loop:
Instead:
loopcounter++; or
loopCounter = loopCounter + 1;
As an unimportant, trivial fact, in the bad example, the existing loopCounter++ (value
equals "1") is assigned to loopCounter before the +1 is added. Flipping the order of the
"++" to front of the statement would work properly, although this is non-standard and not
recommended.
Counting by two's:
"++" is the recommended method because this is the style most commonly used and is
slightly more efficient under the hood. But the older style is still needed. Consider a
loop that counts by twos: 2, 4, 6, 8, 10. The "++" method does not work and you must
use the longer style. This grows the counter by two, printing "hi" five times:
MessageBox.Show("Done");
}
The statement that appended "hi " to the existing textBox can also be abbreviated. Since
adding something to itself is an amazingly common operation, C# has a more concise
syntax that is favored by most experienced programmers.
Instead of
textBox1.Text = textBox1.Text + "hi ";
As you study the program above, it probably looks simple, but I've found most beginning
programmers manage to get the brace-positions confused. If a closing brace is missing
or mis-placed, the compiler displays all kinds of strange and hard-to-figure-out error
messages.
You may have typed a semi-colon after the while statement, resulting in a compiler error,
"Possible mistaken empty statement".
Consider this loop problem, where the intent is to loop until strfoundString contains
some value other than an empty or null string (e.g. loop until string is empty). What is
wrong with this statement?
If code lives outside of the boundaries of it's routine, the compiler panics with a bizarre
"invalid token '(' in class, struct, or interface member" error. The highlighted line, which
has perfectly good syntax, will continue to complain as long as it is mis-positioned.
In classes I have taught, beginning students don't always keep their braces typed in a
logical order and they don't always indent properly. Examine this code and decide if the
routine will work. The last two closing braces correctly belong to class and namespace
definitions higher-up in the program.
This routine works but it is difficult for humans to interpret. Indentation matters.
The computer can help you understand how a loop increments. Looking below at
Program 2.1b, line #10, add the new MessageBox statement. By inserting a second
"MessageBox.Show", you get what I like to call a "diagnostic MessageBox" (do not type
the line numbers).
or as one statement, typed on two physical lines, for cosmetic or style reasons:
MessageBox.Show
("Loop Counter = " + Convert.ToString(loopCounter));
or as:
MessageBox.Show
("Loop Counter =
" + Convert.ToString(loopCounter));
C# does not care about "white space." The closing semi-colon marks the real end of the
statement. However, you cannot break a quoted (literal) string in this fashion.
Depending where you put the MessageBox, either above the increment or below, you'll
get differing results on loopCounter's status. For example, the MessageBox at line 10
shows the loopCounter at 10 just as the loop ends, but if the MessageBox were moved to
line 12a it would show 11. Press F5 to try it out.
As you have probably surmised, MessageBox displays a simple dialogue box. The
example contains two MessageBoxes: One shows what the loopCounter equals inside
the loop and the second announces "Done."
The syntax of the MessageBox command should be evident. You might consider
"Messagebox" as a 'keyword' but that isn't strictly true. Amazingly, C# has only a few
dozen reserved keywords and MessageBox isn't one of them. In reality,"MessageBox" is
a "class" (of instructions) and ".Show" is one of the "methods" available to the class. As
you learn more about C#, these distinctions will become clear.
Editing Concerns:
C# does not allow editing while the program is running and you will see problems,
especially if a MessageBox is waiting for you to click "OK". Run the example program
from above, but leave the "Done" Messagebox on the screen. Return to the editor and
attempt to change the program. The editor displays a clear message, "Changes are not
allowed while code is running" (Visual Studio 2010 and older: "Cannot currently modify
this text in the editor. It is read-only")
In the Form Designer's view, the indicators are more subtle. The ToolBox flyout is
either missing (or empty in VS2008, with a disturbing "there are no useable controls in
this group." ) You will also see a red-square ("stop") on the top toolbar as well as "lock"
symbols on the tabbed list.
Infinite loops are surprisingly easy to write, especially by accident, and the most
common problem is when you forget to increment the loop counter. For example, in the
programs above, if you neglected to grow the loopCounter, the variable never reaches 10
and the loop never ends. In this part of the chapter, you will explore how to make an
infinite loop and how to escape from them.
There is no harm in writing an infinite loop, but it does get weird. Read this section
before starting the loop so you will know what to expect.
A. Delete or comment-out (using slash-slash) the loop-increment statement at line 10. You
may have used "loopCounter++":
// loopCounter = loopCounter + 1; or
// loopCounter++;
B. For the first test, add the diagnostic MessageBox at line 10, just before the now-removed
loopCounter increment. This will be used to explore the concept:
:
6 while (loopCounter <= 10)
7 {
8 textBox1.Text = textBox1.Text + "hi ";
9
10 MessageBox.Show("Loop Counter = " +
Convert.ToString(loopCounter));
11
12 // loopCounter = loopCounter + 1;
13 }
:
Because the loopCounter was removed, the variable remains at 1 and never "grows."
The program is in an infinite loop, tempered by the MessageBox. You will be pestered
by MessageBoxes. Click OK a few times to get a feel for the loop.
There are several ways to break the loop. When you 'break" the loop, the editor
highlights the line(s) in the program where it is looping, giving a clue on how to fix the
problem. When breaking into the loop, you are in "debugging mode:" Clicking the tool
bar's "red-square" completely stops the running program and does not enter into the
debugging (break) mode.
Ignore the running program and its diagnostic MessageBox for a moment. Click
anywhere inside of the editor's code view window. This activates the editor, bringing it
to the foreground. You must do this first in order to enter debugging mode.
While the program is still running, and while you have the editor active, do one of the
following:
• Alternately, click inside of the editor's workspace (again, ignoring the looping
program). From the top menu, select Debug, "Break All".
While in a debugging mode (break), hover the mouse over any variable name (especially
loopCounter) to see its current value. In this example, notice how loopCounter is always
"1" – even though you may have been through the loop a thousand times. This is the
very definition of an infinite loop.
Once in a break/debugging mode, press the toolbar's green "Continue" arrow to jump
back into the program, as-if the break hadn't occurred (in this case it would simply loop
around and stop again at the same MessageBox statement).
Although the editor and the debugger may have pushed the program into the background,
the program is still running. You will see "Form1" in the Windows task-bar. End the
program using any of the following methods:
Method 1: Bring the running program (taskbar, "Form1") to the foreground by clicking
on the taskbar icon. Once the Form appears, click the close-X. Be aware
this part of the interface can be disabled, so this option is not always
Method 3: On the taskbar, look for the running program, labeled as "Form1" (not the
Visual Studio icon). Other-mouse-click the "Form1" taskbar button and
select Close. Windows will prompt to "end task".
You will likely be bugged about sending an error report to Microsoft. As humorous as
this sounds, click "Don't Send." See the Appendices (Common Error Messages) for
information on how to disable this prompt.
For a second more realistic test, return to the program editor and remove the diagnostic
MessageBox. When you do this, breaking into the program's debug mode is more
challenging.
• Break the program by clicking inside of the editor and pressing ctrl-alt-Break (the
pause toolbar) and hover over the loop-counter variable, noting it stuck at 1.
• Click the toolbar's red-square (Shift-F5) to stop the program. Do this before the next
editing step.
You may be asking why textBox1 doesn't fill up with an infinite number of "hi"'s? The
computer is so busy looping that it doesn't have time to re-paint the screen.
(After stopping the program), you can force the screen to re-paint with a Refresh
statement:
// loopCounter = loopCounter + 1;
}
:
where:
• "this" refers to the currently-opened Form and it begins with a lower-case 't'. Visual
Basic and Microsoft Access programmers may recall the similar "Me" prefix.
• Refresh ( )'s parenthesis indicate the "Refresh" is a method of the "this" class.
Parenthesis are always required after a method name.
Ultimately the program will crash when the textstring or textBox control exceeds limits.
Windows Task Manager will also report "Program not responding" even if the loop were
still active because it would not respond to other mouse and keyboard events. Ctrl-alt-
Break into, and stop the running program.
Modify the example program, (Program 2.1), so it prints the numbers 1 through 10
(instead of "hi"). You should have enough knowledge to complete this task and you are
encouraged to try this before reading on.
Important: you must un-do the previous infinite-loop logic from the section
above; returning the program to a normal loop. Also, remove the Refresh
command and other internal diagnostic MessageBox commands.
Steps:
1. Return the program to its original state by removing the diagnostics MessageBox
statement and the refresh line. Return the loop counter to normal.
On line 8, append a space after the "Convert.ToString". The space is inserted at each
iteration loop and is part of textBox1 assembly.
This program is no different than the "hi" version. The only minor complexity was
adding the loopCounter to the assembled text as it was moved into the textBox. Numeric
data (loopCounter) should be converted to a string before appending to a text field.
What happens if the while-loop changes from 10 to 100? What about 1000? If you have
a moderately old computer, a 2000-iteration loop takes a minute or so to calculate. At
increasingly larger numbers your program may appear to hang or it may appear to be in
an infinite loop. But if you wait long enough (and your upper limit is reasonable), the
program will complete the task.
Change your program to loop 1000 times now. Why so slow, knowing you probably
have a reasonably fast computer and 1000 iterations is nothing to a computer? The
appending statement:
textBox1.Text = textBox1.Text + Convert.ToText(loopCounter);
has to move a lot of text when it runs for a thousand times. All of the text, along with all
of the previous text, has to move in-and-out of the box with each iteration and each time
there is a larger amount of text to move.
And there is another problem. Since textBox1 is a visible field on the screen, C# spends
a lot of energy worrying about the user-interface, fonts, word-wrapping and the like.
There is a lot of behind-the-scenes work and it takes time.
The example can be slowed down even further by inserting "this.Refresh();" near the
bottom of the loop.
Running the loop 1,000 times takes a surprising amount of time, even on a modern
computer. This non-sense program can be significantly improved by making a minor
change to the program's logic.
To do this, create a new string variable to house intermediate results. As the loop runs,
append all of the text to this intermediate variable. Since this variable is not part of a
display, the loop runs significantly faster. When the loop finishes, move the final (fully-
assembled) string to its final resting place in "one fell swoop." With this, the user-
1. Near the top of the routine, at line 4a, declare a new string variable, "tempString" and
initialize it with an empty string, signified by a "" (quote-nospace-quote).
The loop now manipulates tempString instead of the textBox. The structure and logic of
the statement remains the same
Notice variables do not have a ".Text" property; they are not "objects."
To compare:
2. After the end of the loop's closing brace, at line 12/12a (see code above), move
tempString into textBox1.Text. This is key to the new design. Because of its position
outside of the loop, the move becomes a one-time event.
:
10 loopCounter++;
11 }
12a textBox1.Text = tempString;
13 MessageBox.Show ("done");
14 }
The new string variable, tempString, was allocated above the loop, at line 4a, and was
initialized as an empty string. Initializing to an empty string is required because the
value must be "seeded" with data prior to the while-loop – even if initialized to "".
Without this, the compiler complains with "Use of unassigned local variable 'tempString'
when it first tries to work with the value in line 8. Previous 'textBox' versions of this
program did not have this problem because textBoxes automatically initialize with empty
strings when created.
When progressively larger strings were appended to textBox.Text, the program labored
as larger and larger strings were moved in and out. The program had to worry about
wordwrapping and scrollbars. The improved version moves the data to a temporary
variable, tempString and does not concern itself with visual overhead.
You probably noticed the 1000+ loop's results did not fit in the allotted space within the
multi-lined textBox. It needs a scroll-bar.
After closing the program return to the Form Designer by using the top-row of tabs.
Highlight textBox1 (the field) and look at the Properties panel on the lower-right. You
may find from your previous testing the Output tab overlays the Properties tab. The
Output tab can be closed:
In textBox1's Properties pane, find the setting for "Scrollbars." (It is helpful to click the
A-Z sorting button at the top of the pane.) In other words, "Scrollbars" is a property of
text. Select "Vertical".
Compared to while-loops, "do" loops are anti-climatic. These types of loops are
identical to "while" loops in every respect except one: the conditional is checked at the
end of the loop and this is reflected in the code.
do
{
<code inside of loop>
} while (condition-test);
Why bother? The difference is subtle, but sometimes useful. A do-loop always runs at
least once, then it checks the conditional. Contrast this with a while-loop – it checks the
conditional before attempting the first iteration.
do-loops:
• Always runs at least one time
• Checks the condition at the end of the loop - always running at least once
• loopCounter must be incremented
while-loops:
• May or may not run, depending on the condition
• Checks at the start of the loop, can bail early
• Like a while-loop, the loopCounter must be incremented
do-loops are otherwise identical to a while-loop but the syntax is somewhat unusual and
there is a seemingly oddly-placed semicolon, sitting at the end of the while-clause.
Meanwhile, like all other loop statements, there is no semicolon on the "do" keyword.
In my experience, these types of loops are used infrequently. Since this loop is similar to
the while-loop.
"while" loops are useful because they can run when the total number of loops are
unknown, running until a condition is true; the condition (the count) is usually not
known at the start of the loop. For the illustrations, the while-loops were limited to 10
(or 1000) iterations, but in real life, they would use a variable or perhaps another user-
event to control how many times the loop ran. Commonly, while-loops process input
files while "not End-of-File" (and the end-of-the-file is not exactly known), or "while
string_Input is equal to Smith", etc.
But there are times when you know exactly how many times to run the loop. For
example, you may need to skip across each month in a year, or each character in a string,
examining one character at a time. In both of these examples the number of iterations
are known (12 months in a year, length of the string). This type of loop is called a "for"
loop, but I call them by an older name, "for-next loops." Although you can make a
while-loop do the same thing as a for-next, there are reasons to use this new species of a
loop.
Probably the best reason to use for-next loops is it keeps track of its own counters and
variables – all within one line of code. Since the controls are in one place, the loops are
often easier to read and interpret than other types of loops.
Even though C# doesn't use the keyword "next," I still call the loop a
"for-next" out of habit, from other programming languages. Basic
used the word "next" to close the loop. Calling the loop a for-next is
easier and more descriptive to read in a book because the word
"for" by itself is odd.
Using the same program from other examples in this chapter, follow these steps, with
new logic attached to button1. The program will print the numbers one through ten,
displaying the results in the multi-lined textbox, textBox1.
1. From either the form's design view, double-clicking button1, or from code view, locate
the button1_Click event and remove all statements between the event's opening and
closing braces, leaving an empty event:
2. In the button1_Click event, write these statements. Line 5 is the heart of the loop and the
details are described in a moment:
Results: textBox1 contains: "1 2 3 4 ... 10", where each loopCounter is converted to a
text-string and then appended to textBox1.
for-next loops are composed of three separate phrases and each phrase handles either the
beginning, middle or end of the loop.
• The first phrase is the starting value of the loop - initializing the loop's first value, in
this case, "1". After this, the phrase is ignored for the remainder of the loop.
• The second phrase is the conditional - run the loop while the counter's condition is
true. This condition is checked *before* each iteration.
• The third phrase, at the end of the statement, increments the loop's counter.
Although this is the third phrase in the loop, this part of the statement takes effect at
the bottom of the loop, just before the condition is checked.
• For the increment, you can use either of these three styles and the differences are
immaterial:
loopCounter++ (recommended)
++loopCounter
loopCounter = loopCounter + 1
• Each phrase is separated from the other with an internal semicolon. And, like a
"while" loop, there is not a closing semi-colon at the end of the statement.
The variable that controls the loop must be declared and initialized before the loop
begins. On line 3, loopCounter is declared as an integer, "int loopCounter;" and out of
habit I typically, and optionally, initialize the variable with a value, "int loopCounter =
0".
The variable's declaration and initialization can be combined into one by declaring the
integer and assigning an initial value with the first clause of the for-next loop. Because
this integer is only used for the loop, most programmers prefer this design:
Because loopCounter exclusively controls the for-next loop, it is disposable. Usually the
variable isn't needed anywhere else, and for that reason it does not even warrant a formal
declaration or even a legitimate name. Being so trivial, many programmers simply use
the letter "i" for the variable name (this goes back to the Fortran Programming language
in the 1960's).
For the sake of these illustrations, a descriptive name "loopControl" is being used (or as
you will see in future chapters, "iloopCounter" where the "i" means "integer"), but even
the author often uses a variable simply named "i". It is a sad commentary on the laziness
of programmers. The loop is most-often written like this:
Reading a for-next loops takes practice. Here are some English hints.
In the old days (QuickBasic, VB), this loop was typically read as "for eye equals one to
10, step one" but that didn't really explain the loop to the un-initiated. It would be more
precise to say it like this: "Start eye at one and count forward by one. Do this 'while' eye
is less than or equal to 10. If eye climbs above 10, stop the loop." This type of verbiage
emphasizes the fact that a "while" loop and a "for-next" loop essentially operate in the
same way because both use "while" in their definition.
The details inside the loop's opening and closing braces are nearly the same as the while-
loop examples – with one important exception: there is not a loop-incrementing
statement in the loop's details.
When the loop is first encountered, C# initializes the loopCounter variable as shown in
the first part of the phrase. Next, it checks the conditional to make sure it has the
authority to run the loop – is the loopCounter less than 10? If the condition allows, the
code within the opening and closing brace runs.
For most for-next loops, the count almost always starts with
"1" and loops until some other value. But values other than
one could be variablized, meaning the loop could start at
"9" or "100". The for-next loop still checks for permission
before running.
When each iteration, the loop reaches the closing brace at line 8; the counter increments
by one; using the third phrase and then it loops back to the top and checks the condition.
Consider the closing brace as the "next" part of the loop. Ultimately loopCounter
reaches the cut-off point and the loop ends with the next statement after the closing brace
as the next command.
Semi-Colon Errors:
Out of habit, it is easy to add an errant semicolon at the end of the for-next statement, as
in:
The compiler will complain: "Possibly mistaken empty statement." Delete the semi-
colon.
The examples in this chapter have printed their results in a horizontal list. With a minor
change, the numbers can display vertically by introducing a carriage-return/linefeed:
where the string "backslash-r, backslash-n" represents two ASCII character codes,
CHR$(10) + CHR$(13). (ASCII being a "character set" which includes the letters A-Z,
a-z, numbers, tabs and other special characters).
As before, the resulting list may not fit in the space allotted in textBox1. If the textBox
did not have a vertical scroll bar, users can still place the cursor in the box and arrow
down to reveal the remaining numbers - but without a scrollbar, users have no indication
this is possible. If you have not already done so, close the program and return to design
view and set textBox1's ScrollBar property to "Vertical."
Write a for-next loop that adds all the numbers between 1 and 100. Attach the logic to
button1. Display the results in a MessageBox or in textBox1. The answer is calculated
as: 1 + 2 + 3 + 4...+ 100 = 5050. A for-next loop is being used because the count is
known.
A new, intermediate variable is needed to hold the results of the calculation. Although
any variable name will do, one named grandTotal seems descriptive. Attempt to write
the code now, but as a hint, the loop's logic will work like this:
- At iteration 2, the grandTotal is the previous grandTotal plus the value of the
loopCounter (2), giving a total = 3
- On loop number 3, the new total is the previous grandTotal, 3 plus the value of the
loopCounter, 3; 3 + loopCounter=3 = 6.
Before reading further, attempt writing the routine now. You may be surprised to learn
the interior of the loop is one line long. Here is the solution:
where:
• With each iteration, the current loopCounter's value is added to grandTotal and the
results are put back into grandTotal. This is the same type of formula as i = i + 1.
The only difference is a loopCounter-value is being added instead of a simple
number 1.
• The MessageBox shows the results *after* the end of the loop because it is after the
loop's closing brace. No details are being written in TextBox1.
Exercise: What is the result if looped 1,000 times? 10,000 times? This is
mathematically interesting and you are encouraged to try. There is a simpler
mathematical formula to calculate this, but with the computer, we can use brute force.
for-next loops are interesting because they can have different starting values. You can
start the loop below zero or it can start at an intermediate number. The loop can grow in
increments other than one and it can run backwards, counting from high numbers to low.
Granted, "while" and "do" loops can also do this, but a for-next loop does this with
particular grace and it is self-documenting, looking at one line of code. Below are some
examples on the different ways to start a for-next loop.
There is no law that says you have to start a for-next loop at 1. They can start at zero, a
negative number, or any other number. "Arrays" often need to start at positions other
than one.
Consider this version of a for-next loop: It starts at 5 and results in "5 6 7 8 9 10":
You also don't have to increment by one. Consider this snippet which results in this
string: "2 4 6 8 10" and uses a variable "i":
where:
• the increment is by twos. Because of this you must use the older-styled increment
"i = i + 2" instead of "i++".
for-next loops can start at a higher number and run backwards. When this is done, the
conditional flips from a normal "while less-than or equal-to" to "greater-than or equal-to"
MessageBox.Show ("Blastoff!");
If your code ran – but displayed a single Tee-minus and then jumped immediately to
blastoff, then the conditional was set incorrectly; it was probably set to "while i >= 5".
Study the correct line for a moment. "Start eye at 5 and count backwards by 1. Do this
'while' eye is greater than or equal to zero. When eye falls below zero, stop the loop."
The other common way to incorrectly write this loop is to write the conditional as
"while i <= 0" where the greater-than was written as a less-than. This results in an
instant "blastoff," with no countdown, and it always surprises the launch crew.
There is this possibility: What if your starting value is greater than the conditional? In
other words, what if "i" is initialized at 5,000; in this case none of the code within the
loop runs.
Results: The loop "runs," but does not execute because the loop-counter failed the "less
than or equal to 10". Naturally, in the real world, the 5,000 would not be hard-coded in
the loop. Instead, it would be a variable set by another routine.
In previous examples the loops were controlled with hard-coded values (i <= 10, etc.).
Now, using skills from Chapter 1's textBoxes, use a typed value to change the loop's
iterations based on a variable. For these next examples, use textBox2 to control the for-
next loop. For example, type "15" in textBox2 and loop 15 times.
By definition, textBoxes are strings and their values cannot be used in numeric
calculations or comparisons until converted to a number. You have seen the opposite,
where numbers are converted to strings using: "Convert.ToString(loopCounter)"
Exercise:
Modify the example program, exchanging the hard-coded <= 10 with a converted
textBox2.Text. Detailed steps are documented next.
1. Using Program 2.2, and others in this chapter as a model (counting 1 to 10), modify
button1's logic so it uses textBox2 to control the duration of the for-next loop. Write the
results with CRLF's after each printed number. Here is the final code:
When testing, type only numeric values in textBox2; audit logic has not been written and
the program will crash if otherwise. If you find you cannot type a value into textBox2,
check the "Enabled" property, confirming it is set to "True" (set in an example from
Chapter 1).
where:
• The for-next loop's conditional uses a new term at line 5: "Convert.ToInt32". This
converts textBox2 from a dot-text string to a 32-bit integer – making the string
suitable for numeric calculations. An audit should be written here, intercepting
invalid numbers. This is covered in later chapters.
• Because of the length of the new statement, the for-next loop was written on three
separate lines, making for easy-to-read code. This is considered good programming
style.
The program would crash with a runtime error, generating this difficult error message:
"System.InvalidCastException: Unable to cast object of type
'System.Windows.Forms.TextBox' to type 'System.IConvertible'". Press the tool-bar's
red-square to end the program and return to the editor.
(Results: The program runs but no new results are displayed (previous run results
remain unchanged. Note that the previous results were not erased; this will be fixed in a
moment.)
Question:
Why doesn't the loop run? (It is important that you understand the reason for this. The
answer: Looking at the 'while' clause – "is 1 less-than-or-equal-to negative 15?" No, the
first iteration, 1, is greater than the negative number – the loop does not have permission
to run.)
Modify the code so each time button1 is pressed, textBox1 starts with an empty box and
the previous results are erased. Try this now. Hint: Can you move anything to clear or
erase textBox1.Text prior to the loop?
Solution: At line 3a, add this statement, where quote-quote represents a null string:
textBox1.Text = "";
or better yet, use this method, specifically designed for the task:
textBox1.Clear();
Sometimes, while running a loop, you may need to skip that iteration's processing and
jump to the next item. For example, while looping through payroll records, you may
discover a record or condition that shouldn't be processed due to a data-entry error or
some other flag. Perhaps certain account-codes should be ignored or transactions prior
to a certain date are bypassed. The need for this happens often enough that C# provides
two loop controls just for this purpose:
continue; The current loop iteration immediately ends and starts with the next loop
cycle. When "continue;" is reached, all remaining lines in the loop's details
are skipped and control is passed to the top of the loop. Usually, a continue-
statement is used when a record has some kind of error or exception, but the
error is not serious enough to stop the program. Use with care in a while-
loop.
break; Causes the entire loop to immediately end and control is passed to the next
statement after the loop's closing brace. This does not end the program or
the current routine, only the loop. Usually break statements are used when a
catastrophic data-error is discovered and all processing of a (file) should be
immediately stopped.
Use these statements in any type of loop, including "while", for-next, and do-loops - but
in my experience I most often see them in for-next loops.
'continue' works especially well in a for-next loop but use with great care in
a while-loop. Details later in this section.
Decisions like these always use an "if-statement" somewhere in the interior of the loop to
make a decision. Although "if-statements" are covered in the next chapter, you should
be able to follow these examples.
"continue" Example:
This example uses program 2.4 as a base. The goal is to loop 1 through 20, but skip
items 7 and 13. And just to demonstrate the break command, stop all processing at item
15. This is a contrived example.
if (iloopCounter == 7)
continue;
The next chapter discusses if-statements in more detail but here are the important points:
if (iloopCounter == 13)
continue;
textBox1.Text = textBox1.Text +
Convert.ToString(iloopCounter) + "\r\n";
}
}
Confirm that the output shows all numbers, 1 - 20, skipping 7 and 13.
Now add the "break" statement that ends the loop pre-maturely at 15. (The loop should
have been written "while iloopCounter < 15" instead of using obtuse logic like this
example.)
if (iloopCounter == 13)
continue;
if (iloopCounter == 15)
break; //Numbers 16-20 never run
textBox1.Text = textBox1.Text +
Convert.ToString(iloopCounter) + "\r\n";
}
}
• Having two if-statements, with two separate 'continue' clauses is inefficient and
wordy. The two phrases could be combined into one statement using a double-"OR"
clause. This is described in more detail in the next chapter.
:
int iloopCounter = 0;
textBox1.Text = textBox1.Text +
Convert.ToString(iloopCounter) + "\r\n";
iloopCounter++;
}
:
The 'continue' will jump to the while-statement's closing brace, bypassing the
loopCounter. Because of this, iloopCounter will never grow past 7 and will never reach
the upper-limit, 10.
:
if (iloopCounter == 7)
{
iloopCounter++; //A duplicate increment is needed
continue;
}
:
In a for-next loop, integer declarations can happen above the loop with a stand-alone
"int iloopCounter = 1;" or the variable can be declared and initialized within the for-
next statement using "for (int iloopCounter = 0; ...)".
Consider this example, which declares the variable "iloopCounter" above the loop and
then displays the final loop-counter value after the loop completes; note how
"int iloopCounter" is declared above the loop:
Now change where integer iloopCounter is declared, moving it into the loop definition:
When running this version, you will get a compiler error, flagging the MessageBox
statement with, "The name 'iloopCounter' does not exist in the current context."
In this case, the integer was declared within the for-next and died when the loop ended.
Any code referencing the loopCounter fails because it is un-declared and un-available.
Similarly, no matter where the variable is declared within button1's Click event, it is
unavailable to any other button or routine elsewhere in the program. Once button1
reaches it's closing brace, all references to variables within are destroyed. In other
words, the "scope" of the variable is limited to the routine that declared it. This topic
will be explored in more detail when Functions and Methods are described.
A loop within a loop is called a "nested loop." This topic may seem esoteric, but nested
loops are often used in array work and when processing files with sub-files or other 2
and 3-dimensional data sources. As scary as they sound (and look), nested loops are
interesting to work with and can do things that no other construct can.
In this section write a routine that generates a simple multiplication table and second
routine that displays an ASCII-character Christmas tree. These examples demonstrate
nested loops and they are purposely simplified in order to explain the concepts.
Construct the example program by taking the same routines you've been working with in
the for-next examples (Program 2.2 or 2.4) and making the following cosmetic changes:
The goal of the first nested-loop example is to generate a short 7x5 multiplication table
where
Although not pretty, the on-screen results will look like this:
Consider how this routine is likely built. A normal multiplication table takes a row-
number times a column number. Thus,
This can be exploited with two loops, one dependent on the other. The first loop
controls the number of rows (7) and the second controls the columns (5). The column-
loop will be inside of the row-loop.
1. Begin by deleting all previous code in button1's Click event, leaving the event's opening
and closing braces.
2. Since the number of rows and columns are known (7x5), for-next loops are the best
candidates to use with the loops.
}
}
where:
Each row has five columns. Imagine row #1: while on the row, skip from column 1, to 2,
to 3.... Then, when you are done with the five columns, move to the next row (#2) and
start over with a new column-count, 1, 2, 3....
}
}
}
j For each outside row-loop, the columns run from 1 to 5. This means the interior
column loop is busier than the outside loop. Notice how the column variable, "col",
is re-created / re-initialized to 1 each time the row-loop runs.
:
for (int row=1; row <= 7; row++)
{
for (int col = 1; col <= 5; col++)
{
textBox1.Text = textBox1.Text +
Convert.ToString (row * col) + " ";
{
:
Notice the appended space (" "), which separates one digit from the next:
1 2 3 4 5
5. This leaves one problem: If the program were to run now, all the results would print as
one horizontal line, where each row appends to the previous:
1 2 3 4 5 2 4 6 8 10 3 6 9 12 15 ... etc.
1 2 3 4 5
2 4 6 8 10
3 6 9 12 15 ...
This is a trick but is typical with nested loops. After the column loop completes its
count, 1-5, and before looping back up for the next row, append a CRLF to textBox1 at
the end of the column loop (outside the col-loop, but before the next Row loop):
This logic is more useful than in multiplication tables. Arrays and other table work
often uses this type of logic.
• In order to display better, the textBox was set to a non-proportional font (Courier
New). This helps line up the columns. But the first and second rows clearly do not
line up because they are one digit wide. Fixing the spacing issues can't be resolved
with the tools discussed so far, but in summary, any of these methods would work:
The goal is to build a triangular shape using pound-signs (hash marks). The "tree" has 7
rows with a varying number of columns. The end result will look like this:
1. As before, modify button1's Click event by removing previous logic. Be sure to leave
the event's opening and closing braces. textBox1 remains a multi-lined textbox,
formatted with a non-proportional font.
2. Following the multiplication table example, add an outside "row" loop with a nested,
inside "col" loop.
}
}
}
3. Except for the tree-trunk logic, the rest of the loop remains the same, printing hash-marks
"#" instead of a calculation. As before, at the end of a column loop, print a CRLF.
Here is the code for the completed program. Note little changed from the previous
multiplication table example:
In this code, notice how the column for-next loop is within the row-loop. This means for
each row (1,2,3), the column loop runs multiple times. Without actually running the
program, study the code for a minute and mentally keep track of what happens to the
column variables when the row-count is equal to 1. Then, mentally bump the row-count
to 2 and study the column loop a second time.
The interior column loop is tied to the row-count. When the row is 1, the column count
runs from one-to-one. When the row is 2, the column count runs from 1 to 2. When the
row is 7, the column count runs from 1 to 7.
This is admittedly a strange concept. Here is the logic of the loop; look at the code and
follow along. The code is testable now:
• As each row is reached (1, 2, 3...), the col loop runs, cycling through each column
before moving to the next row. This is the key to the program. The number of
columns varies with the row-count.
• At the end of each column-loop, after that row's #'s are printed, append a
CarriageReturn\LineFeed "\r\n". The CRLF's are added at these locations:
In the code above, after the loop, look for a comment: "\\ Trunk logic goes here".
Add this logic at that location:
This is a normal, non-nested loop that prints the tree trunk. The loop literally prints
"@@" twice, with a CRLF after each print. The "\r\n" can be inserted into an existing
string or appended as a separate string.
The same row-variable is being used because it was no-longer needed in the tree's
construction. In other words, the tree-trunk does not have to begin with row 8 because
this part of the program is unrelated (logically) to the other. If you wanted to start at 8,
then you would have to modify the program to read like this:
Because the original nested-loop's row-variable was destroyed when the main tree-loop
ended, this new loop needs to re-declare the "int row" variable. As an aside, you could
have declared a different variable name, such as "trunkCount", making it self-
documenting.
"foreach" loops are specialized loops that work only with arrays and they are
superficially introduced here because of the chapter's topic. Arrays and foreach loops
are covered in more detail in Chapter 22 (Arrays) and in Chapter 13 (Parsing).
If you are new to programming, arrays will be a foreign concept and you can safely skip
this section, but in many respects, these are the simplest of all loops because they handle
all aspects of the loop automatically.
Arrays are a variable that can hold one or more items in a list. You can think of them as
a mini-spreadsheet. For example, an array of names might appear like this, where the
array is named "alistOfNames":
where:
• The string array is arbitrarily called "alistofNames" and the compiler knows it is a
string array because of the square-brackets [ ]. I like to preface the variable name
with an "a" to remind me it is an array.
• "string [ ]" declares a string array of an undetermined size. The array will grow to
accommodate the string values inserted into it on the other side of the equal sign. In
• On the same line, literal strings, encased in braces, insert data into the array. This is
one of several ways to put data into an array.
• Each item in the array is addressed by a numeric counter, starting at base zero.
For example, alistNames [0] = "Bob", alistNames [3] = "Jane".
"foreach" loops know how many items are in the array and the loop knows how to begin
the loop, increment and when to stop. Within the loop there are no loop-counters or
increment statements.
Because each item in the array has a unique, numbered name, the foreach loop needs to
"variablize" each item, giving it a predictable name that can be used reliably inside the
loop. The "string tempString" statement takes the indexed value and copies it to this
temporary variable.
Arrays and Lists are covered in more detail in Chapter 22 and 13.
I chose to introduce loops early in this book because they are visually interesting – and
they are definitely more entertaining than integer-size and variable-naming discussions.
Admittedly, the examples were somewhat contrived, but I hope this topic captured some
interest in programming. With this foundation you'll soon have your computer doing real
work – looping through files, modifying databases, and a host of other tasks. Loops are
the key.
If you are new to programming, this may have been a challenging chapter, with many
new concepts. Most new programmers struggle with loops and especially nested for-next
loops. The following exercises should help you understand the topic.
while-Loop Summary:
• "while" loops are useful when you don't know how many times to execute. For
example, "while not end-of-file" – where the number of records is undetermined.
The ASCII File Chapter covers this in greater detail.
and
• Be sure to manually increment the "while" loop's condition, which is often a counter
or some kind of boolean true-false flag. This usually happens at the end of the loop's
details. Think iloopCounter++.
Infinite loops are generally bad. However, faster computers can get through
them faster. Don't forget to increment the counter or set the conditional's
flag.
do
{
<code inside of loop, including...>
iloopCounter++
} while (iloopCounter <= 100);
• "do" loops are exactly like "while" loops except they check their condition *after*
executing at least once. Note they have an unusual closing semicolon on the
conditional.
• for-next loops are used when the loop count is known or when the loop count can be
pre-determined.
• for-next loops read them like this: "Start <variable i> at 1, loop while <condition> is
true; at the end of the loop, increment the variable." Unlike other types of loops, the
entire loop is self-contained in one line of code - making them succinct and easy to
interpret.
• for-next loops automatically increment themselves when they reach the closing
brace.
• Within the for-next statement, all variables should point to a <temporary> variable;
often called "i".
• Usually the for-next counter-variable is declared and initialized all in one line, within
the for-next loop using for (int i = 1; ...)
• for-next loops can start at values other than 1 and can easily increment in any
number of steps. For example, two-by-two. They can start at high numbers and
work backwards.
foreach Loops:
• Only works with arrays, array lists, and List<T>. See the Array chapters for details.
• Note how you must create a temporary holding value and that value is used within
the details of the loop.
• You cannot use the tempString to modify the values in the original source array.
A. Write a while-loop that prints your name 10 times in textBox1, one name per line. Get
your name from a separate textbox and do not hard-code your name.
Hints: You will need a carriage-return/linefeed ("\r\n") and don't forget to increment the
loopCounter.
B. Using the same while-loop in Exercise A, print the number "1.", "2.", etc, in front of your
name.
1. John Smith
2. John Smith
3. John Smith
:
C. Using a for-next loop, write a program that displays the numbers 0 through 10 in a multi-
lined text box. Then write the numbers in reverse order.
D. Write a program, in the style used in this chapter, that counts from 1 to 100.
Use textBox1 to hold the multi-lined results and use textBox2 to control the loop-
increment. Instead of row++, use textBox2 to control the growth of the loop.
In other words, in textBox2 type a "2" then the loop will increment by 2:
0, 2, 4, 6... to 100. Type a 5 and the loop jumps from 0, 5, 10, 15... to 100.
Display all of the numbers in the multi-lined textBox1.
E. Using the Christmas-tree program (program 2.85), make a minor change and print the
triangle-tree up-side-down. Leave the "trunk" on the bottom.
F. Modify the Christmas Tree to print only odd-numbered columns. The first row will have
one "#", the second row will have "###" (3), the third row will have 5 pound-signs, etc.
Keep printing the tree as a triangle.
G. Using a program similar to the Christmas tree (program 2.85) and from Exercise G, build
the tree symmetrically, using 2 for-next loops that control the rows and columns.
The only way to make this tree look presentable is to print only odd-numbered rows "*".
The first row gets 1 character; the second row gets 3 characters, etc. This way the tree
can line up. This can be handled automatically by the row-loop that increments by 2.
Hint: Use a separate variable to track which row you are on when indenting. Also,
remember, when "printing", the characters on the "next" print line won't go to a new line
unless a carriage-return was used ("\r\n") – sometimes you don't want a crlf.
H. Write Exercise-G in such a way that the number of rows are adjustable with either a
variable or a text box. For example, print a 5-row-high-tree, as in the illustration above,
or set it as a 7-row tree. When this variable changes, the indents should happen
automatically without code changes and the tree-trunk should self-center under the new
tree.
This is a difficult exercise, but everything can be done with simple integer variables and
three for-next loops.
Conditional Branching is a fancy way of saying an "if" statement: "If this is true, then do
that...". The concept was first introduced in the previous loop chapter, where each loop
contained an imbedded if-statement (do while valueA is less than 10), but the nature of
the conditional was glossed over.
Topics:
Overview:
if-statement Summary
if (valueA == valueB)
{
//"Then" statements;
}
else
{
//optional "else" statements;
}
switch (myColor.ToUpper())
{
case "RED":
case "DARK RED":
//statements;
//Notes
// no {braces} in section;
// Colons, not semi-colons, on case-statements
// break; statements are absolutely required
break;
default:
//acting as an else or otherwise; optional section;
break;
}
There are two major conditional branching statements: "if" statements (which I call "if-
then" statements) and "switch" statements (also called "case" or "case-select" in other
languages).
"Boolean" as a data-type:
When speaking about "if" statements and their brethren, you are speaking about
"boolean" terms. A boolean is a data type, like "integer" or "string," that resolves to
either 'true' or 'false'. Declare boolean variables similarly to how other variables are
declared:
bool myBooleanVariable;
bool continueReading = true;
Variables can be initialized with either a 'true' or 'false' (case-sensitive) or leave un-
initialized. Unlike other programming languages, you can not use "Y/N" or "T/F" or
"0/1" - just true/false.
There are obvious and not so obvious ways to generate true or false values in an if-
statement. You are probably familiar with the first group of operators - such as greater-
than, less-than, and not-equal-to, as in "if valueA > valueB then...". There is a second
group of more intimidating operators, &, &&, ||, and "^".
Comparison Operators
Some of these same operators can be used to compare strings - but are more limited in
scope. More details about string comparisons are found in the next chapter.
When a boolean is paired with an "if-then" statement, you have a conditional branching
command. Mentally, it reads like this: "If this is true then do this stuff; else do that
stuff." You will find if-statements have a somewhat confusing set of rules when it comes
to braces and semicolons and it is further complicated by optional "else clauses," which
act like another set of statements.
When building an if-statement, the 'if' always has a pair of (parenthesis) and in the
parentheses is the conditional-test. For example, if(iValueA > iValueB). Recall
while-loops also used parenthesis in their conditionals. Following the if-statement are
one or more lines of instructions, delimited (or blocked) with a pair of {braces}.
The "else" clause is optional and mentally you should treat it as its own separate
statement with its own set of braces.
Many think of if-statements in this fashion: "If this, then, else that" – a very Excel-like
mentality, where the opening brace is the "true" or "then" side.
Alternates:
if (testString.Equals("Brown"))
or
if (Equals(testString, "Brown"))
The "Equals" method, which works on strings, integers and a variety of other types, can
be made a less more sensitive to non-English comparisons, using this syntax:
if (testString.Equals("Brown", StringComparison.CurrentCulture)).
you must use an equality operator of some type, "==", ">=", ">", etc.. But, if you are
testing an already-created boolean variable (such as 'myFlag', which can be True of
False, from the illustration above), then the true-false comparison is assumed. For
example, these statements are synonymous:
Similar to Program 2.2, begin a new project. On Form1, place two textboxes and a
button. Modify the button1_Click event by double-clicking the button and adding the
following code. As usual, pay attention to the braces and indentation.
if (valueA == valueB)
{
textBox1.Text = "ValueA and B are equal";
}
else
{
textBox1.Text = "ValueA and B are not equal";
}
}
Common Mistakes:
A single equal means "gets," as in, valueA = 10, where valueA gets or is assigned the
value 10, while a double-equals means a comparison or boolean.
Why such a strange message? Here is what happens when you forget the double-equals.
The compiler sees the statement:
Knowing that white space is ignored by the compiler, some programmers style their
opening if-statement braces at the same level as the "if". This is a legal and somewhat
common way to block your code and is common in Java scripting. The editor will
dissuade you from doing this and will try to impose a more standard design:
if (valueA == valueB) {
<do stuff here>
}
else {
<do stuff>
}
Optional Braces:
The statement following the if-clause is normally blocked with a pair of braces. But,
when an if-statement clause has only one instruction, braces are optional. These two if-
statements are functionally equivalent:
//Style 1:
if (valueA == valueB)
{
textBox1.Text = "Value A and B are equal";
}
//Style 2:
if (valueA == valueB)
textBox1.Text = "Value A and B are equal";
In the second version, the braces were discarded – ! but this can only be done when the
if-clause has a single statement and the closing semicolon replaces the closing brace.
Braces are absolutely required If more than one statement exists inside the if-statement
clause.
There are light-hearted arguments in the development community about having the
opening brace on the same line as the if-clause, and about not using braces on a one-line
if-statement. Obviously it saves vertical space and typing; others argue it is inconsistent.
I recommend using opening and closing braces for all blocked code – even if there is
only one line of text. And I recommend the braces typed on their own lines. This seems
to be most prevalent style and in practice, it is easy to type and read. Admittedly, it takes
more vertical space.
Optional "else":
With all if-statements, the first "then" clause is required but the else-side is optional.
What if your logic has nothing to do on the "then" side and all the logic needs to be done
on the "else" side? You have to fool the compiler with one of two tricks: Either put in a
non-functioning then-clause or flip the conditional around.
The following code does not have an executable {statement} after the "if" – comments
are not executable and this will fail:
Adding a "dummy" clause after the "if" solves the problem – a comment is not enough.
Everything in the braces act as a single executable statement, even if there is nothing
within. The braces replace the closing semicolon used in the single-statement example:
or more simply:
}
else
{
MessageBox.Show ("Stuff to do when false");
}
An alternative is to flip the conditional by using a Less-than (or "not-equal" in the case
of an equality):
Alternate Dummy-if
if (valueA < valueB)
{
MessageBox.Show("stuff to do when 'false'");
}
or
if (valueA != valueB)
{
MessageBox.Show("stuff to do when 'false'");
}
(English falls apart in this last example. It is almost like a double-negative: a not-equal
result gives the if-statement a 'true' response. Remember, if statements only think in
terms of true and false.)
Like the loop commands, there are no closing semicolon on the if-statement itself, but
out of habit, you may type one.
By now you should be wondering why loop and if-statements don't use closing semi-
colons at the end of their phrase when almost every other command does. Remember
that all statements in C# are one command long and each command ends in a semi-colon.
if-statements and loops are no exception - they can only be one command long. Reading
the command in English explains why: "If valueA is equal to valueB then do this one
thing. Semicolon."
The compiler works this way for reasons only known to the compiler-gods, but for
aesthetic reasons, humans like to write the statement as a two-lined phrase with the "if"
on one line and the "then" on the next. This makes it appear as two lines when it really
is not. Of course, C# doesn't care about "white space" and the compiler treats it as one
line no matter how many physical lines you type.
With this knowledge, Program 3.1 could be written correctly, without braces. The 'else'
can be considered a separate command. Two commands; two semi-colons:
if (valueA == valueB)
textBox1.Text = "ValueA and B are equal";
else
textBox1.Text = "ValueA and B are not equal";
Because both halves of the if-statement are only one line long – and that line ends in a
semicolon – the two-line method is legal. To continue the example, you can roll up the
lines, removing all white space. For aesthetic reasons, this style is not recommended:
if (
valueA ==
valueB)
TextBox1.Text =
"A and B are equal";
Braces:
And of course the statements could be written with braces to delimit the if-clause and the
else-clause. (But as always, braces are required if the clause has more than one
statement.) This is the key to braces: They act as a single statement – think of them as a
closing semicolon for a single statement.
if (valueA == valueB)
{
//one or more statements can go here
textBox1.Text = "ValueA and B are equal";
}
else
{
//one or more statements can go here
textBox1.Text = "ValueA and B are not equal";
}
The examples above contained only one line of code for each phrase but you could put
any number of statements between the braces. The compiler treats the braces as a block
of code and internally it groups them as one logical statement, with a call to a sub-
routine. In pseudo-code, it looks like this:
if (valueA == valueB)
{ GOTO a subroutine to do other steps, then return after the closing brace; }
else
{ GOTO another subroutine and return when done; }
When compiled, the code within the braces is shuttled to a new location; then the
computer calls the subroutine and returns when done. This happens under the hood.
With this, the compiler can stick to it's rule about all statements being one line long.
However, as mentioned before, many programmers discard the braces on single-line if-
statements. This can introduce hard-to-find bugs. Consider these examples:
If you have more than one line of code within either the "if" or "else" clause, then braces
are absolutely required. Consider this flawed code-snippet, which to a casual eye looks
perfectly logical:
Fix the problem by adding braces around those two lines of code on the if-side.
if (valueA == valueB)
{
textBox1.Text = "ValueA and B are equal";
textBox2.Text = "This is an error";
}
else
textBox1.Text = "ValueA and B are not equal";
But watch what happens when the else-side has two or more statements, but
you forget the braces, as in this snippet:
You will find textBox2 (underlined) runs unconditionally – regardless of the outcome of
the if-statement; it will run whether the test was true or false, even though it was 'clearly'
intended to be part of the else-clause. The trouble is, even though it "looks" like it is part
of the else-clause, it is not. Flaws like this are hard to see. In this case, the compiler will
not show an error. Half the time the if-statement takes the top route and the other half it
takes the bottom, making the error appear intermittent. The indentation fools your eye
and you will have a devil of a time finding this bug.
Boolean statements can be combined with "and"s and "or"s to make a "compound"
statement. With logical ANDS, think "if this-is-true AND that-is-true then the whole
condition is true." if (myColor == "white" AND paintType == "Latex)... then paint
the wall. Both sides of the condition must be true before the if-statement resolves to
true.
Instead of using the word "AND", C# uses the symbols "&" and "&&". Both mean
"AND" and both work similarly. Consider this sample code:
With an AND, both booleans must be true; otherwise 'else'. In the example, even though
B equals 15, A's value does not match and the statement resolves false because of the
AND.
Both halves of the AND must be Boolean – you can think of them as individual if-
statements. Ultimately, the if-statement can be reduced to this English-like text: "If true
and true then..."
There is no difference in how "&" or "&&" compute – both arrive at the same
conclusion. But the "&&" can be more efficient because it bails-out of the comparison if
a first 'false' is encountered.
With a single ampersand ("&" vs. "&&"), both values are calculated, regardless of the
outcomes. Then they are "AND-ed". Sometimes, single-ampersands can get you in
trouble. Consider the following statement, where there is a possibility of dividing by
zero. Here the developer knew about the possibility and attempted to trap the condition
and used a single-AND:
With a single-ampersand, both sides of the AND are computed prior to deciding the "if".
The first half checks to see if valueZ happens to be zero while the second half runs the
division. Both sides are calculated before applying the AND. Since it is against the law
to divide by zero, the program literally crashes when the single-ampersand forces the
divide-by-zero.
If valueZ happens to be zero, the remainder of the if-statement does not even bother to
calculate – and the divide by zero does not happen. If valueZ is greater than zero, the
first clause resolves as true and the computer then moves ahead and computes the second
phrase, as needed.
Code like this is graceful and subtle and the details can become hazy when it is pulled for
maintenance a year later; comments are certainly warranted here. However, this type of
problem (Divide-by-zero) happens frequently in programming and in one statement you
can prevent your program from crashing. The test could be re-written using a construct
called a nested-if (described in the next section). This takes more vertical space but in
some respects it is more self-documenting and easier to interpret:
if (valueZ != 0)
{
if (valueA / valueZ > 10)
{
// greater than 10 stuff
}
else
{
// less than 10 stuff
}
}
The split-vertical-bar (" | ", also known as a pipe-symbol) represents a "logical OR". If
either half of the compound if-statement is true, then the entire statement is considered
true. Think, "If either this or that is true, then..."
Using the values from above, where (valueA equals 10) and (valueB equals 15) – but the
test is against a different value, this statement resolves true.
//Again, the variables are set one way, but the example if-
//statement will test for a different value
}
}
The single-bar or dual-bar logic works along the same lines as the "&&" logic. As a
general rule when working with OR, always use the double-" || "'s.
would still resolve 'true' because the first instance and, because of the double-pipe, the
other statements would not be examined.
If using single-pipes for the three comparisons, it would still resolve as true but the
compiler is forced to compute each of the three phrases. Using the double-pipe is more
efficient.
XOR is an unnatural and little-used boolean. With the "carrot" (sometimes called a "hat"
symbol), if one side or the other resolves as true, then the statement is true; this is the
same as a standard OR. But if both sides are true or both sides false, then the statement
resolves false.
if-statements can be embedded within other if-statements. When you do this, indentation
is the key to human understanding, but as usual, the computer does not care about white-
space.
Consider this situation: You have three numbers (say these are product weights) and you
need to see which is the largest. (Mathematicians should refrain from laughter: what
happens if the numbers are equal? Let this slide.)
For this example, hard-code the numbers as integers to leave the demonstration
uncluttered; pretend they come from other data sources:
If you had more than three values, this type of logic becomes unmanageable. In this
case, consider loading the values into an array and sorting, using concepts in future
chapters.
Nesting if-statements are simply a matter of indenting and punctuation. Done well, the
code is understandable but nested-ifs can be difficult to look at when indented more than
three conditions deep. Although there is no real limit on nesting depths, indenting is
frustrating to type. There is an alternate method, called "else if", which acts like a new
if-statement.
Consider this example, which tries to determine which color was selected:
if (myColor == "Blue")
theAnswerIs = "Blue is my color";
else
theAnswerIs = "None of these colors";
MessageBox.Show (theAnswerIs);
comments:
• Visual Basic programmers typed "elseif" – as one word – instead of "else (space) if".
The VB editor corrected the misspelling automatically, but the C# editor does not. If
you forget, the error reports "; (semicolon) expected".
Also, in the example above, I did not type opening or closing braces, saving on that
vertical space that paper books have limits about. In real life you would probably
have a liberal selection of braces scattered throughout the code. The code above
works, however, because all the statements are single-lined statements.
Earlier, a nested-if was used to test which of the three values was the largest. C#
provides a vaguely similar function that returns the larger or smaller of two numeric
values. It is limited to testing only two values at a time, but it makes for a simpler test
than an if-statement.
Math.Min
Math.Max
:
if (valueA > valueB)
valuex = valueA;
else
valuex = valueB;
MessageBox.Show ("The larger number is " + valuex);
Compound boolean statements can be written with phrases that contain a mixture of
ANDs and ORs. The phrases are often surrounded with parenthesis to clarify the logic
or to make it easier to read. For example, consider this compound if-statement:
With the parenthesis, it reads this way: "If the product category were either A or B and
the factory were "SFC", then proceed with the then-clause." If the product category were
"C", the entire clause would be skipped. If the factory were any Factory other than
"SFC", the entire clause would be skipped.
The statement could also be indented in this fashion, which is easier to read and
understand:
Confusion:
Notice how the parenthesis surround the A and B tests. This is important. Using the
same logic as any mathematical calculation, parenthetical phrases are examined first, in a
left-to-right order.
Without the parenthesis, the intent can be confusing. What happens with this statement?
The question is whether to read this as [(A or B) and SFC] or as [A or (B and SFC)]. In
this poorly-punctuated instance, read the statement left-to-right. In this case the results
would be the same with or without parenthesis but you would have to agree a non-
parenthesis statement is difficult to interpret. With compound booleans always use
parenthesis for clarity.
At some point, statements can get too complicated for most people to comprehend. Take
a look at the following, nonsensical if-statement. When statements get this involved,
consider a different construct – usually a combination of switch and nested-if-statements:
In the previous nested-if example, program 3.65, the variable "myColor" was compared
against various colors - Blue, Green, Red, etc. Once the value was discovered, a text-
phrase was stored in a separate string. Finally, at the end of the routine, the string value
was regurgitated in a MessageBox. The if-logic looked like this:
if (myColor == "Blue")
theAnswerIs = "Blue is my color";
With each possibility, Red, Blue, Green, etc., the nested-if only looked at "myColor".
When the same variable is being compared with multiple choices, there is a better
construct called the "switch" statement. VB programmers know this as a "Select Case".
Although it has limitations, a 'switch' is easy to use and is easy to read. It can handle
dozens of separate tests, without the bother of nested-if's.
Case "Value":
Consider this code, which tests the variable "myColor" against a list of colors and reports
which was selected. Notice the individual Case-value-colon statements. If none of the
colors match the available choices, display a "None of the above" message. For
simplicity in the example, myColor is declared and initialized at the top of the program,
as a hard-coded value:
switch (myColor)
{
case "Blue":
theAnswerIs = "Blue is my color";
break;
case "Green":
theAnswerIs = "Green is the color";
break;
case "Yellow":
theAnswerIs = "Stinkn yellow is the color";
break;
case "Black":
case "Red":
theAnswerIs = "Red or Black is my color";
break;
default:
//acting as an "otherwise" statement...
theAnswerIs = "None of the above!";
break;
}
MessageBox.Show(theAnswerIs);
The trade-off is this: When making their comparisons, nested-ifs can look at more than
one variable. For example, nested-if's can look at myColor, the type of material, and the
day of the week; they can compare any other variable, at any time, even within a nest.
Switch-statements are restricted to a single variable, in this case, myColor.
switch-Statement Punctuation:
"switch" statements are oddly punctuated. To begin, notice none of the cases are
"blocked" – that is, they don't use braces. Compared to all other commands you've seen,
this is counter-intuitive – until you notice the colons (not semi-colons) at the end of each
"case." The colons are actually a "goto label." The "break" ends the goto-section and
tells the compiler to jump to the first statement after the switch.
If you've had prior programming experience, the implication of a goto statement explains
much about how the switch-statement works. In the example, you can think of a switch
as a "goto Green:" or "goto Red:". Goto statements are explained in more detail in the
next section, but for now, their purpose should be fairly obvious.
switch (valueA)
{
case 5:
<do stuff here>
break;
case 10:
<do stuff here>
break;
}
This example numeric-switch only considers the numbers 5 and 10. All other numbers,
and I do mean all other numbers, do not participate.
default Clause:
An optional "default" clause processes all other possibilities not met explicitly with a
case-statement; you can think of this as an "else" or "otherwise." Inserting a default into
the numeric switch example from above, substantially changes the logic: Now, all
numbers are processed - but 5 and 10 are given special consideration.
case 10:
<do stuff here>
break;
default:
<do stuff for all other possibilities here>
break;
}
The default clause requires a break-statement even though it is the last phrase in the
group. Technically, default can be anywhere in the construct – top, bottom, or in-
between – but common sense dictates it should be at the end.
You can only use equalities in a switch-comparison. For example, case "red": means the
case is equal to 'red' – but it will not test for 'Red' or "RED'.
Greater-than, less-than and not-equals are also not allowed. By saying case "red": you
are implying an equal sign. Unless you like compiler errors, do not type the equals (as in
case == red).
This restriction is noticeably different than other languages, such as Visual Basic, which
allows inequalities. In those languages you could, for example, switch-case on numbers
<= 10. With C#, keep in mind the :colons act as a GOTO label and case <= 10 does
not make a good destination label.
Each "case:", including the last, must have a "break"1. This is the same type of command
as a "break" in a loop; control is passed to the switch-statement's closing brace and the
break essentially tells the compiler to GoTo the end of the switch.
The compiler does not allow logic from one clause to flow into another and the next
case-statement label is not enough to prevent it. Forgetting a break statement will fail
1
Or a "return" can be used in lieu of a break. return would exit the current method and return to
the calling routine. But you cannot use a return and a break in the same clause. Return-statements are
discussed in a future chapter.
case 10:
:
The myColor switch example had two labels next to each other (Black and Red); this
acts as an "OR":
Other languages allow you to stack you choices in this manner: case "Black", "Red":,
but this is not a valid "goto" label and the syntax is not allowed in C#.
switch (myColor)
{
case "RED":
case "Red":
case "red";
<do this stuff>
break;
But this logic does not work with "rEd" and it can get cumbersome with other choices.
A better solution is to transform the switch's conditional checking for a more generic
version of "myColor." Imagine your program prompts the user for their favorite color
(and imagine they never misspell it). The comparison-logic would be smoother and more
case "blue":
//Do blue stuff
break;
}
".ToLower()" temporarily changes what ever is in the variable myColor to all lower-
cased letters. From here, each case-statement need only check for lower-cased
possibilities (e.g. "red", "green", etc.). Notice the two empty parenthesis, which are
required by the syntax. There is more on this in the next chapter.
switch (myColor.ToLower())
The conversion only changes "myColor" for the duration of the switch statement. The
original myColor value"RED" remains upper-cased. Prove this to yourself by watching
the MessageBox.Show, as it displays the myColor variable; you will find it is always
cased as-it-was. The reason? The cased value was not assigned to a new variable.
myColor = "RED";
myColor = myColor.ToLower(); //Now 'red'
switch (myColor)
When switching against strings, especially if these are values typed by end-users, it is
wise to shift the case and it would be even wiser to "trim" the results, where .trim
removes leading and trailing spaces (more on this in the next chapters). This would be a
recommended test:
switch (myColor.Trim().ToLower())
{
case "red":
:
• Multiple case-commands can not be combined into one line. For example, say that
both black and red execute the same logic. You can't use this syntax:
case "black", "red":
However, the statements can be "stacked" as long as there is not intervening code
between the conditions. This works because the "case" statements are goto-
destination labels. Again, notice the colons:
case "black":
case "red":
case "dark purple":
<do the same stuff here if any of them>
break;
• C# does not allow logic from one case-statement to "flow" into another. In other
words, you can't move from blue's case-logic and expect to flow into "green's".
Break statements are absolutely required at the end of the code-sections. "default"
also needs a break.
• You can use a "goto" statement, going to another label within the case - such as
goto default; or goto blue;. Just because you can does not mean you should.
• case choices are "case-sensitive". "Red" does not equal "red". Usually this means
you should use a variable.ToUpper() or variable.ToLower(). I like using
lowercase tests because they are easier to type.
switch (myColor.ToLower().Trim())
myColor = myColor.ToLower().Trim();
switch (myColor)
switch (myColor)
{
case strMainColor: //Illegal to use a variable here
case red:
• switch statements cannot use equalities or inequalities (==, >=, etc.) in the
comparisons. This is different than in other programming languages. If these types
of comparisons are needed, use if-statements.
switch (valueA)
{
case <= 10:
<Illegal comparison>
break;
switch-statement read as a "goto-like" label and this is why inequalities are not
allowed. Phrases such as "case valueA <= 10:" is more a calculation than a label.
• There is no limit to how many "case" statements you can have within a switch-block.
Switch statements can have other embedded switch statements but this is poor
programming style. Call another module from within the first switch if you need to
do this (techniques described in a future chapter).
• The "default:" section is optional but recommended. Your program should have
logic that handles unexpected tests - even if that logic is nothing more than a
displayed error message alerting of an internal problem.
• Variables within the switch-statements must be initialized with some value, even if
an empty-string "", prior to entering the statement. In the examples above,
Without initializing internal variables, you will get a "Use of unassigned local
variable 'theAnswerIs'" at compile-time – even if a valid switch would have been
selected. The Default-clause, where "theAnswerIs" can be set to a default, insulates
you from this problem. However, I prefer to initialize above the routine.
:
case "yellow":
theAnswerIs = "Yellow is the color";
break;
case "pink":
break; //execute no logic
}
In this snippet, if "pink" were selected, no logic was run, and more importantly, the
variable "theAnswerIs" would not be populated. A run-time error is at risk when
later MessageBox statements tried to use the variable, which never had a populated
value. This is why "theAnswerIs" variable should be pre-initialized with a default
value, even if that value is an empty string, "". Also, even if there were a default
switch, default would not run because pink was the choice, so again, the variable
"theAnswerIs" would not be set.
Syntax Errors:
Here are some of the common syntax errors you may encounter.
case "yellow":
theAnswerIs = "Yellow is the color";
// break;
Results: An error message: Control cannot fall through from one case label "'case
Yellow". The Yellow "case" statement is highlighted.
Exercise:
Exercise
:
switch (strfoundString.length)
{
case 1:
//do stuff here
break;
case 2:
//do stuff here
break;
default:
//do stuff here
break;
}
Compiler error: " 'string' does not contain a definition for 'length' and no extension
method 'length' accepting a first argument of type 'string' could be found (are you
missing a directive or an assembly reference?)". Solution: Capitalize ".Length"
It may seem the switch statement has too many restrictions to be useful but the statement
is succinct and easy-to-use. Most programs find a good use for it. A well-placed switch-
statement simplifies nested-ifs and other convoluted logic. A year from now, when
reviewing this code, switch statements will be refreshingly clear and easy to understand.
The nature of goto statements has been touched upon earlier. A goto instructs the
computer to "jump" to a new location in code. The location is defined with a destination
– called a "label".
Labels are a non-executable marker in your code – a place for the goto to land on and are
punctuated with a colon:
if (myColor == "Red")
goto bypassLogic;
bypassLogic:
theAnswerIs = "Red is my color";
allDone:
MessageBox.Show (theAnswerIs);
comments:
goto statements often beget other goto statements. If you have one, you invariably need
another, and soon the program is a convoluted mess. Use other constructs and simplify
the logic.
• Do not use goto statements, if for no other reason than to avoid the scorn of fellow
programmers. This verb is considered crude and blunt2.
• goto jump labels end in a colon (not a semicolon). As you type, the compiler shifts
the label one tab-stop to the left so the label stands out from the rest of the code. The
labels themselves are "non-executable" and do nothing other than mark a place in the
program.
2
I originally wrote this sentence as "They are considered crude and blunt" but it was unclear if I were
referring to the GOTO verb or your fellow programmers. Generic pronouns are imprecise, aren't they?
• goto jump labels must have some executable code beneath them. In the example
above, remove the MessageBox.Show line and you'll get a compiler error "Invalid
expression term "}" (closing brace).
• Unscrupulous people use goto's to bypass part of a loop. Almost always a "break" or
"continue" is more graceful. However, in the past several years, there has been
discussion in programming circles that a well-placed, simple goto can be acceptable,
perhaps as an early-exit out of a loop or other routine. I agree to this, if used with
care.
Basic Syntax:
theAnswerIs =
(valueA >= valueB) ? "Answer is A" : "It is B";
MessageBox.Show (theAnswerIs);
comments:
• Note the question-mark and the colon. Think if-then-else, much like an Excel
formula.
• The results of the test (valueA >= valueB) must resolve to a boolean.
• The two phrases after the "?" are "what to assign if True :colon, what to assign if
False". The results are sent to the variable at the front of the statement. Ternary
Operators must assign their results to another variable – in this case I used the
variable "theAnswerIs".
• You could combine the string definition for "theAnswerIs" and the Ternary into one
line, as in:
I mention this command because you might see it occasionally in the wild. Although you
can accomplish several things with one line of code, the abilities of the Ternary is
limited. Unlike a standard if-statement, you cannot add additional statements within the
"then" or "else" clause.
In the style used in this chapter, attach logic to "button1_Click" solving the following
problems. Have your results display either as a MessageBox or in textBox1.Text.
• If a product-code is "AB-123" and its weight is less than 25 grams, mark this product
as "Underweight".
• If the product is 25 grams to 30 grams, mark the product as "OK".
• If above 30 grams, mark the product for "Rework".
Hard-code the test variables at the top of the routine using these statements. For
simplicity, pretend they are input by end-users:
Be sure to test with different product-codes and weights by simply changing the hard-
coded values.
Is this type of test a good candidate for a "switch" statement? Why or why not?
B. Modify the switch program 3.7 (the switch myColor program) so it tests against all
lower-cased variables – regardless of what case was used to populate the original test-
variable.
myColor = "RED";
myColor = "Red";
myColor = "rEd";
string prefix = "Mr."; //Change this variable to other possibilities for testing
If the field is valid, allow the value to pass and MessageBox what was typed.
If not in the list, display a MessageBox error of some type.
As an added feature, if the salutation were typed with improper case (all caps, lower or
mixed-case) or is missing a period (Mr vs Mr.), correct what is being tested and allow it
to pass.
Of interest: This can be solved using a switch statement or if-else statements. No other
fancy code is required. This should be challenging.
D. With the following pizza variables, display a message showing if you've built my favorite
type of Pizza. I happen to like Round Pizzas that are Medium or Large, never Small and
a Combo or Vegi are acceptable, with Thin crust.
Then, write logic testing what was entered in the string variables. If the pizza matches
the requirements, display a messagebox saying "Yes, pizza is editable" or "No it is not".
Run a variety of different pizza-models through your logic. No matter what is entered, it
should compute to a proper Yes or No.
There are many possible solutions to these problems. Here are some suggestions:
switch (myColor.ToLower())
{
case "blue":
MessageBox.Show("Blue was found");
break;
case "red":
MessageBox.Show("Red was found");
break;
case default:
MessageBox.Show("Color not in list");
break;
}
switch(strPrefix.ToUpper())
{
case "MR.":
case "MR":
strPrefix = "Mr."; //Force a known value regardless!
break;
case "MRS.":
case "MRS":
strPrefix = "Mrs.";
break
}
if (shape == "round")
{
if (size == "medium" || size == "large")
{
if (style == "cheese" || style == "vegi")
{
if (crust == "thin")
MessageBox.Show("This is it!");
else
MessageBox.Show("The crust is wrong);
}
else
MessageBox.Show("Wrong style");
}
else
MessageBox.Show("Not the right size");
}
else
MessageBox.Show("I only like round pizzas");
Pizza Problem
if(pizzaShape == "round"
&& (size == "medium" || size = "large")
&& (style == "combo" || style = "vegi")
&& crust == "thin")
MessageBox.Show("Eatable");
else
MessageBox.Show("Send back!");
Alphabetic Listing
This is an alphabetic listing of various compiler messages with likely solutions. These are
from Visual Studio 2005, SP1 through VS 2014.
Errors and warnings are sorted alphabetically. Search by the first non <variable> word.
e.g. "Argument '2': cannot convert from 'double' to 'float' will be found under "cannot..."
Messages such as "The type arguments..." will be under "The"; Messages that begin with
punctuation ("; expected") are listed first.
Symptoms:
The compiler normally shows exactly where a semi-colon is expected and when you get this
error it is normally flagged at the very end of a line. If the compiler shows it in the middle
of a line, it can get confusing.
Problem:
This incorrectly typed command would show an expected missing semi-colon at the
Convert.ToString phrase.:
MessageBox.Show Convert.ToString(loopCounter); //missing paren
Solution:
In this MessageBox example, note that the MessageBox.Show phrase was incorrectly typed;
it is missing a set of parenthesis. This confuses the compiler like something awful. The
correct syntax is:
MessageBox.Show (Convert.ToString(loopCounter));
Problem
In this incorrectly typed command, the word "if" is 'misspelled' with a capital "I" instead of
a lower-cased "if":
If (IsBlank(testString)) //Capital "If" is wrong
A list that is this enumerator is bound to has been modified. An Enumerator can only be used if the list
does not change. (Sic)
Symptoms:
Attempting to delete an item from an array, comboBox, listBox, etc, while in the middle of
a foreach loop.
Issue:
You cannot delete an array-item while in the midst of a foreach loop.
Mark the item's position (counter) – typically in another temporary array and use a separate
loop to remove them, after the first loop completes.
A local variable named 'e' cannot be declared in this scope because it would give a different meaning to
'e', which is already used in a 'parent or current' scope...
Symptoms:
The top of the module, typically button1_Click, already has an 'e', as in "EventArgs e" and
you probably have a try-catch that also uses "(Exception e)"
Recommendations:
See button1_Click's signature line and compare it with the catch statement's signature lines
Change the "(Exception e)" to "(Exception e2)" with corresponding changes to e2.Message.
Or consider moving (most) of the logic from button1_Click to its own routine: e.g.
A100_Process(); which won't have an 'e' in its declaration.
Possible Solution:
Do not define variables or methods (functions) above the form level; form-class level.
If you are trying to make a "global" variable, see Chapter 7.
Symptoms:
While opening a form that uses SQL server resources.
Solution:
Confirm that the SQL Server is running and you have rights to the database.
If the SQL Server is running locally, on the LocalHost, confirm the Microsoft SQL Server
Services are started. From Windows, Start-Run, "Services.msc"; Confirm SQL Server
(SQLExpress) is started
See SQL: An Error has occurred while establishing a connection to the server....
Issue:
"return" is not returning the correct 'type'.
Solution:
Examine the method's signature line to see if it returns a string, integer or other type of
object. The corresponding return statement(s) within the module must also return that same
'type'.
example code:
private string myFunction()
{
if (util.IsBlank(mystring))
return mystring;
This is a generic error that generally means the compiler cannot find the variable or an
associated class was not instantiated.
Possible Solution:
If the variable or method in question is in a different Class, do one of the following:
a) Declare the variable as "public" or "internal" and instantiate the class within your
Form/Class using the "new" keyword. See Chapter 6, External Class Libraries, for
details.
c) If the method is in error, consider declaring the method as "public static...." or better
yet, "internal static" as in
Possible Solution:
Move the declaration into another method, directly above the Instantiation.
In simpler terms, move "cl800_Util util;" just above the line "util = new cl800_Util();"
Possible Solution:
If you have just switched a variable from a local variable to a "public static" variable, re-
compile the program using menu Build, Rebuild Solution.
Possible Solution:
Misspelled or wrong case variable name.
Possible Solution:
Especially when using a (Form's) properties. Do not use the current Form's name (it was not
instantiated within itself); instead, use "this."
ProgramGlobal.IformLeftPos = frmA000Form.Left;
ProgramGlobal.IformLeftPos = this.Left;
Note: You could also simply use "... = Left;", which is considered too vague for
most people even though the code would work.
Argument out of range exception (s) are always due to an array being unalloacted, un-
available or a value [x] within square-brackets was using a larger number than the size of
the array. This always indicates a logic or counting problem and often the problem happens
at the end of a loop, where you over-shoot by one position. Remember, arrays are base-0; a
ten-item array's last position is [9].
In any case, array arithmetic should be protected with a try-catch (if using a for-next loop or
are addressing [addresses] directly. Consider using a for-each loop, if logic is appropriate.
Issue:
The parameter you are trying to send is something other than a <string>; often the results
are an object-type or a "collection".
example:
frmA031CategoryAdd addCat = new frmA031CategoryAdd
(dataGridView1.SelectedRows[0].Cells[0].Value);
Possible Solution:
Convert it to a string using one of these two techniques:
... (dataGridView1.SelectedRows[0].Cells[0].Value.ToString());
... ("" + dataGridView1.SelectedRows[0].Cells[0].Value);
Solution:
When using "By Reference" (ref), both the calling and the called functions need the 'ref'
keyword. C# requires this for documentation purposes.
Example:
appendDefaultAreaCode (ref myPhoneNumber, locationDefaultAreaCode)
Issue:
Sometimes you can declare an open-ended array with a simple statement, such as:
string [] afoundFields;
but if the array is used inside of a loop (while-statement), C# often requires that the array be
initialized with a starting value or by declaring a fixed array size. This is incase the while
statement never runs and downstream commands may panic.
Solution:
Initialize the array with an item count. Consider over-allocating.
string [] aFoundFields = new string [100];
Possible Solution:
Assuming a single-dimension array (a linear array),
aArrayName.GetUpperBound(0);
Presumably you used aArrayName[x,x], when the array only had one dimension,
aArrayName[x].
Summary:
Typed as btnClose()
Should be typed as an Event: btnClose_Click(null, null);
If still an error, look in the output Window. (See top-menu, View, Output)
Symptoms:
Usually while performing a .GetValue(stringName)
Solution:
Move the RegKey.Close command below the GetValue statements. If the GetValues are in
a loop, be sure the Close is after the loop.
Symptoms:
Attempting to manipulate an array-element from within a foreach loop.
Issue:
Within a foreach loop, you cannot modify or change the values used by the foreach loop.
More to the point, you cannot transform or change the array's internal elements with a
foreach loop.
Solutions:
If you are merely trying to change the value of the array's element, move the value to a
secondary (intermediate) temp-string. Consider this example, with particular attention on
strtempString:
If your intent is to actually change the value(s) of the items in the array, you cannot use a
foreach loop. Instead, use a for-next loop.
Note the loop runs to the Array's length, minus-1 – a base-0 calculation
See the Array Chapter, "Transforming Array Elements" for more details.
Symptoms:
When attempting to launch SQL Server Management Studio
Possible Solution:
Are the services (Start, Run, Services.msc) "SQL Server" started?
Solution:
A numeric parameter must be specified as a floating point number "F"
e.g.
Pen myPen = new Pen (Color.Black, 0.3) should be
Pen myPen = new Pen (Color.Black, 0.3F)
Cannot convert method group '<various: GetLength, etc>' to non-delegate type 'int'. Did you intend to
invoke this method?
Possible solution:
ilastHighlighted = myFiles.GetLength();
Cannot Convert method group '<name>' to non-delegate type 'bool'. Did you intent to invoke this
method?
Possible Solution:
if using an implied comparison in an if-statement:
if (A100_SomeMethod_ThatReturns_Bool)
{
//Incorrect, missing ()
}
if (A100_SomeMethod_ThatReturns_Bool() )
if (A100_SomeMethod_ThatReturns_Bool() == true)
{
//optional
}
Solution:
Close the running program before attempting to modify either the code or the design-view.
You cannot edit while the program is running.
Either close the running VS program (your program) or in the Visual Studio Editor (ISE),
click ribbon-bar "Red Square" icon to abruptly close your program.
Issue:
A DateTime method is attempting to return a null value to the calling module when only
"DateTimes" are allowed. This often happens in a try-catch error condition.
Solution:
where the HasValue method only operates on items with a nullable data-type. See below for
more information on this.
Optionally, in the case of this examle's DateTime value, you could also use this command,
bypassing the Nullable solution: return DateTime.MinValue;
Solution:
:
DateTime? dtValue = (some date/time or null if not available);
With this, the downsteam function can return a null, if it has the need to do so.
:
if (dtValue.HasValue)
return dtValue.Value;
else
return null;
Symptoms:
Usually when building a new method or function near the "static class program" / "static
void Main" class – the main driving procedure for your program. You have tried to use a
"private void <functionName>" within a "static" class.
Solution:
Consider changing
private void <functionName> to
private static void <functionName>
Symptoms:
Code is trying to display a text message, a MessageBox, assign a text label, or assign a text
field with both text (string) data and numeric data. The numeric data refuses to cooperate.
Solution:
Use Convert.ToString on any numeric fields (or other non-string data-types) before moving
them or concatenating them to another string [field].
also: <variableName>.ToString();
Cannot implicitly convert type 'long' to 'int' (are you missing a cast?)
Possible Solution:
Examine the return values of the command you are using. It likely is returning a 'long'
value, not an integer. The error will be flagged deep within the code, but it is the function's
(method's) signature line where you may need to make the fix.
For example:
private int A630_ReturnFileLength (string strpassedFileName)
but the fix may be changing the "int" to "long" on the Signature line.
Change "private int ..." to "private long ..."
CS0029
Cannot implicitly convert type 'string' to 'System.Windows.Forms.Label'
Solution:
Be sure to use a ".Text" when populating a label.
For example, assigning a blank string to a label:
Issue:
Missing method name ".CommandType"
example code:
SqlCommand refCategoryCMD = new SqlCommand("RecordCategoryDelete");
refCategoryCMD = CommandType.StoredProcedure; //In error
Solution:
refCategoryCMD.CommandType = CommandType.StoredProcedure;
Cannot implicitly convert type 'object' to 'string'. An explicit conversion exists (are you missing a
cast?)
Symptoms:
You are using a string array and attempting to assign a value to another text field.
For example:
lblDisplay.Text = aNames[1]; //fails
MessageBox.Show(aNames[1]); //fails
Solution:
Convert to String prior to assigning. This can be done explicitly or implicitly:
lblDisplay.Text = aNames[1].ToString();
lblDisplay.Text = (string)aNames[1];
MessageBox.Show("" + aNames[1]);
Symptoms:
In an "if" or other conditional.
Likely solution:
Did you use a required double-equal in the conditional?
if (testString = "Smith") vs
if (testString == "Smith")
and then later, in a different method, initialize with a fixed size, as in:
The author had this error after several mistyped array definitions. But once the array was
declared, as described above, the error persisted. Finally, after selecting menu "Build,
Rebuild Solution"; the problem went away.
Likely Solution:
You neglected the ".Text" appendage.
For example:
Incorrect:
textBox1 = textBox1 + Convert.ToString(<variable>);
Correct:
textBox1.Text = textBox1.Text +
Convert.ToString(<variable>);
For example:
MessageBox.Show("'" + pnlCategoryCode + "'");
vs
MessageBox.Show("'" + pnlCategoryCode.Text + "'");
Issue: You are using a Nullable <DateTime> and since a Null is allowed, you must re-
convert to the same type. This seems redundant in code because the called function may
already be returning a Date Time. Re-cast the returned value:
DateTime dtfileDate;
dtfileDate = (DateTime)A700_ReturnFileCreateDate(textBox1.Text);
Issue:
SQL data field was defined as 'Timestamp' but C# code is trying to insert a Date. Change
the SQL field definition to a date-time or date format.
Likely Solution:
Declare (and possibly initialize) the variable before using:
string myString = "";
if (myString = "House")
Also, you can see this message if an if-statement, either above or below the first error has a
mis-spelled "Else" (vs "else") or missing parenthesis. This may take a while to locate in
large modules.
Solution:
Your program is still running from your last compile (F5 / Run). Locate the program on the
task bar and close before attempting to run it again. Alternately, from the Editor, press
Shift-F5 to force-close the program.
By default, Visual Studio will not allow passed command line arguments, even
though the Start Options are set in the Project's properties.
Symptoms:
The program will behave as if no command-line arguments were passed, especially
if you compile a Release version of the program. Make this additional change in
the program:
Control cannot fall through from one case label ('case "<label>":') to another
Solution:
In a 'switch' statement, a "case" statement is missing a break; command, as in
case "Green":
<do stuff here>
break;
case "Red":
<do other stuff here>
break;
Solution:
The call, typically on btnFormName_Click, instantiates a new form, as in:
frmA031CategoryMaint catMaint = new frmA031CategoryMaint("");
catMaint.InstanceRef = this;
catMaint.ShowDialog();
public frmA031CategoryMaint()
{
The method's signature line must match the calling statement's (values). The two must
match the same count of parameters.
<DataGridView> does not contain a definition for Cells and no extension method Cells accepting a first
argument....
CS1061
For example: 'MainProgram' does not contain a definition for "A000_Base' and no extnsion
method 'A000_Base' accepting a first argument of type 'MainProgram' could be found (are
you missing a using directive or assembly reference)
Likely solution:
In another class (e.g. MainProgram.cs), you have not yet created or have misspelled a
method called "A000_Base".
Symptoms:
This is an Event problem where the original Event's code was either deleted or renamed in
Code View, but the pointer to the event was not changed in the Event Properties.
Solution(s):
There are two ways to correct this error. Either is acceptable.
1. Double-click the error and the editor will take you to the [Form1.Designer.cs] class;
and as scary as this may look, delete the entire highlighted line.
2. Or, open the <event> properties (Lightning Bolt) for the control in question and delete
the event information from the property screen. For example, if this were a
textBox1_TextChanged event, delete the detail-text after the (lightning-bolt) event.
Doing so still leaves the "textChanged" code, orphaned, in the program. It should be
deleted by hand.
Solution:
In the foreach clause, did you use "DataGridViewRows" (and not just "DataGridView")?
Entering Break Mode failed for the following reasons: Source file <server-drive....form.cs> does not
belong to the product being debugged.
Cause:
A previous project was moved from a server-drive to a local disk.
Reference paths still point to the (old) server location.
Solution:
With Visual Studio 2005 or above, select menu Build, Clean Solution followed by Build,
Rebuild Solution.
With Visual Studio Express, these menu choices may not be present. Do the following:
a. Close the Visual Studio Project
b. Using Windows Explorer, locate the solution; delete the "bin" and "obj" sub-
directories. Re-open the Solution and the problem should be fixed.
error CS0234: The type or namespace name 'Tasks' does not exist in the namespace
'System.Threading'
When using a Stored Procedure and attempting a SAVE or INSERT (ADD) operation.
Missing a connection clause with the SqlCommand. For example:
Incorrect:
SqlCommand refCategoryCMD =
new SqlCommand("RecordCategoryUpdate");
Corrected:
SqlCommand refCategoryCMD =
new SqlCommand("RecordCategoryUpdate", refCategoryConn);
where "refCategoryConn" was the connection defined earlier in the routine, as in:
string strConnection = "Data Source = <servername\\SQLExpress;" +
"Initial Catalog = <database name>;" +
"User ID=<sa>; Password = <password>";
refCategoryConn = new SqlConnection(strConnection);
Cause: The name of your function/procedure/method is the same as a built-in name. e.g., if
you built a function called "Left". This is a new warning, starting with Visual Studio 2010.
Solution:
This error can be ignored. But consider renaming your function. For example, instead of
"Left", use "LeftStr". In general, single-word functions, such as Left, Mid, Right should not
be used.
Field '<name>' is never assigned to, and will always have its default value null (warning)
Possible Solution:
A variable was declared but was never set equal to anything. The 'variable' does not need to
be a normal variable, it could be a class name. Consider this example when declaring an
external class library with the "new" statement either commented or not typed in the proper
location:
clSiteGlobals SiteGlobals;
//SiteGlobals = new clSiteGlobals();
IDE1006 Naming rule violation: These words must begin with upper case characters: <button1_Click>
This is an informational message. Rename the procedure or method, shifting the first
character to upper-case. This is to follow recommended naming standards for cross-
platform programs.
Identifier Expected
Possible Solution:
When declaring a function, are all the parameters in the parameter list prefixed with a data-
type? Missing "string", "int", etc.
Likely Solution:
You are calling a button-event from another location but forgot or mis-typed the event-
name.
btnClose ("", null); //Incorrect – not just the btn name
btnClose_Click ("", null); //Corrected: _Click was missing
Symptoms:
dataGridView1.Columns[0].HeaderText = "SEQ";
dataGridView1.Columns[1].Width = 55;
must be written after the statement that populates the actual grid. See Chapter 27 for
examples.
GetMyData(strSelectString)
Confirm the SQL Server (SQLExpress) services are running (Services.msc) or the remote
server is available.
Symptoms:
A generated error, usually from a try-catch, where array operation attempted to access a
point not in the array – usually one position beyond the end of the array [max n + 1].
If you are not looping through the array and are directly accessing the array (e.g. variable
[n]), then likely the array was not populated with data; especially with a previous .Split
command.
Possible Diagnostics:
If using a foreach loop, place a debug break point at the top of the loop and monitor the
loop. If you suspect the error is (1000 records) into the loop, add this diagnostic logic to the
program and break within the if-statement:
foreach ....
{
if (recordCount > 999)
MessageBox.Show
("Reached suspected error; put break point here");
// <regular processing here>>
}
If using a ".Split" and a subsequent command accesses a variable-field [n] directly, likely
the split found an empty record and had nothing to split into the array. After the split, check
for blank records before executing the (parsing) logic within the loop.
Symptoms:
If you are processing a CommandLine (arguments list), what happens when no command-
line arguments are passed? If you reference aargs[1], the program would abend. Consider
this statement:
Symptoms:
Possible Solutions:
Confirm the SQL Service is running (Start, Run, Services.msc; look for MSSQL/SQL
Server).
Confirm the SQL SELECT statement (an assembled string) includes the field-name you
need and it is punctuated with appropriate commas and spaces, especially within the
assembled string.
Are you trying to reference a [column] position before a DataGridView was populated?
Invalid Expression Term ',' (plus "; expected") when using a picture clause
Likely symptoms:
You are using a picture clause (with a String.Format).
Solution:
Did you forget the words "String.Format ("?
Solution:
String.Format requires a string, even if a single numeric variable is being formatted.
Encompass the phrase with quotes:
textBox1.Text = String.Format ({0:dddd}, dtValue); //Incorrect
textBox1.Text = String.Format ("0:dddd}", dtValue); //Correct
Possible Solutions:
The "if" clause above the errored line requires braces for the "then" section. Sections with
more than one command require braces to group them.
if (valueA == valueB)
{
<stuff>
<more stuff>
}
Possible Solutions:
Does the if-statement-clause have an unneeded semicolon on the if-clause itself? Remove
the semicolon.
Symptoms:
An Invalid Column Name <field name> during a SQL Read or SQL ExecuteReader and the
field name is obviously right, when examined in SQLServer Management Studio.
Possible Solution:
The assembled strSQLstmt (the SELECT statement) is mal-formed, usually a space is
missing in a quoted string, especially on the last field-name, just before the FROM clause.
Set a breakpoint at the ExecuteReader and examine the IntelliTrace. For example, in this
illustration, notice how the "FROM" is crammed next to the field "Comment":
This message generally means the compiler is confused about an opening or closing brace
or there is a mis-placed semi-colon that confuses where the compiler expects a brace.
Possible Solution:
You have a semicolon at the end of an if-statement; while-loop or for-next-loop or remove
an unnecessary semi-colon from the end of a statement:
Possible Solution:
There is a statement or group of statements typed after the module's closing brace. Make
sure all your code is above the closing brace (e.g. above button1_Click's closing brace).
Possible Solution:
Check to make sure that all opening braces have a closing brace and all braces are lined-up
properly. Especially near the end of the program/namespace.
Likely solution:
In a statement, such as:
Login Failed for user <xxxx>. Reason: Server is in script upgrade mode. Only the administrator can
connect at this time. Error 18401.
Solution:
The SQL Server service just started and the engine is updating tables. Wait a few minutes
and try the SQL connection again.
Solution:
The method in the Class Library are "private".
Set to either:
"public" if the Class is instantiated, or if the class is in the same namespace as the calling
routine.
Set to "public static" if the Class is not instantiated and it is in another namespace.
Solution:
You forgot to use a dot-method with the command.
For example: MessageBox.Show (...)
where the .Show was missing
Solution:
A field was used in the UPDATE/INSERT statement but it was not defined with an
"AddWithValue" clause. For example:
refCategoryCMD.Parameters.AddWithValue
("@NonRequiredField",
util.StripSQLinjections(pnlNonRequiredField.Text));
Also check the SQLstmt in two places (once for INSERT and once for EDIT), making sure
the field-name is listed, and within the parenthesis of the field list:
Newline in constant
Likely Solution:
You are appending a "\" backslash character in a string, probably to build a directory-path.
Backslash is a reserved character. Use double-backslashes to represent a single backslash.
Likely solution:
Typically with a button or other on-screen event, such as a button, notice the signature line
of the button; there are two parameters. For example, btnSomething_Click(object
sender, EventArgs e). When calling an event like this, make your call in this fashion:
btnSomething_Click(null, null);
Passing a null value for each item in the signature line.
Solution:
Solution:
You are attempting to use a 'property' as-if it were a method. In other words, remove the
trailing parenthesis. For example:
fi.Length ( ); //is incorrect; use instead:
fi.Length;
Solution:
Generally it means something is mis-spelled.
Solution:
You are calling another method, in another library, without having first instantiating the
object. For example, when using the cl710_Formatting.cs library's "ProperNames"
function, you may need to declare the library either at the top (Class level) or within the
current function (e.g. button1_Click):
Solution:
You have declared a variable, such as a string, an array, a number, but have not initialized it
to a value; the variable still contains nulls.
For instance:
string [] aMyArray;
Only assignment, call, increment, decrement, and new object expressions can be used as a statement.
Symptom 1:
In a for-next statement you have mis-keyed one of the three required phrases. For example,
this statement has an error in the first phrase:
for(i; i <= 10; ++i)
Possible Solution:
you can't use a simple variable in the first part of the phrase; it must have an assignment
clause. The statement correctly typed is:
for(i=1; i <= 10; ++i)
Possible Solution:
A method, such as
sr.Close(); or
A180_ClearDateEntryFields();
was typed without opening and closing parenthesis.
Solution:
Use the "+" symbol to concatenate strings. You used to be a Visual Basic programmer,
weren't you?
Possible solution:
In a complex if-statement or while-loop clause, would an extra set of parenthesis help?
Operator '==' cannot be applied to operands of type 'string' and 'method group'
Operator '==" cannot be applied to operands of type 'method group'
Operator '+' cannot be applied to operands of type 'string' and 'method group'
Likely Solution:
You forgot a "( )" after a function name.
"ToString" vs "ToString()" is commonly missed.
For example:
If using a method, such as .ToLower; as in ...textB.ToLower
did you remember the required parenthesis, as in: textB.ToLower()
if (textB.ToLower == "dog") //incorrect
if(textB.ToLower() == "dog") //correct
Symptoms:
You are using a > or < conditional when comparing two strings, as in:
if (testString >= "Brown")
Solution:
You can't use >, < operators against two strings. This is different than (VB). Instead, see
string.Compare(string1, string2, T|F);
string.CompareOrdinal(string1, string2);
Symptoms:
You are using an equal sign in an if-statement; you need double-equals for the comparison.
e.g.
if (passedPhoneNumber.Length = 7 || passedPhoneNumber.Length = 8)
should be:
if (passedPhoneNumber.Length == 7 || passedPhoneNumber.Length == 8)
CS0019
Operator '+' Cannot be applied to operands of type 'TextBox' and 'TextBox'
Operator '+' cannot be applied to operands of type 'System.Windows. Forms.TextBox'
Solution:
You forgot to include the object's (field) dot-property after the object's name.
CS0642
Possible mistaken empty statement
Symptom:
On an if-statement, while, or for-next statement
Likely Solution:
Although this is a warning, it is most likely a true error. Do you have a superfluous
semicolon after an if-clause, for-next, or other loop statement?
Remove the semicolon and let the next line (or the next set of braces) act as the end-of-line.
Warning: Possible unintended reference comparison; to get a value comparison, cast the left hand side
to type string
Solution:
Do one of the following by casting explicitly or implicitly:
Note: The error will only clear after run-time; it will not clear during the editing session
(VS2010).
Solution:
If this is in an if-statement, did you remember to use double-equals (==)?
Solution:
In the "get/set" routines, typically in a Global External Class Library, there is not any logic
for the "set". If your intention is to make a read-only variable, either remove the logic in
your program that is trying to set the variable's value (e.g. myName = "Smith") or add an
empty-set routine, which ignores the myName = Value statement. Fixing the error is
preferable.
Solution:
Your program is still running while trying to edit the source code. Close your running
program before changing the code or an object's property (e.g. Click the editor's ribbon
icon: "Red Square").
Send Error Report / Don't Send "Please tell Microsoft about this problem"
Symptom:
When you ctrl-alt-Break your program and your program may be in an infinite loop or
otherwise crashed. Microsoft sees this as a problem and offers to send a diagnostic error
report to Redmond. This message is annoying and should be disabled.
Solutions:
Click "Don't Send," then make this registry key change to your workstation.
Start/Run/Regedit
HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\PCHealth\ErrorReporting. Dword
Value: DoReport, 0 = Don't Send.
Possible Solutions:
1) If you are connecting from a remote computer: In Microsoft's Surface Area
Configuration tool (SQL 2005), confirm that "Local and remote connections" is
selected. Choose TCP/IP
2) If you are connecting from a remote computer: Consider starting the Windows Service:
SQL BROWSER
3) Does the SQL Express Server have a software Firewall installed? For example, if using
Windows XP SP2 with Windows Firewall, open the Firewall's control panel; click
Exceptions: Add this program: sqlserver.exe. Also add SQLbrowser (udp port 1434).
Other Solutions:
4) You are using the wrong server name in the connection string.
This can be especially true if you have moved your application from one computer to
another (when in development) and your Development SQL Server also moved. The
author had this problem when moving from a Desktop to a Laptop.
5) You are using the wrong Catalog (database name). e.g. from Chapter 16 "Address"
6) And of course, the wrong username and or password. If the password is encrypted; did
you decrypt it prior to executing the command?
SQL: Login Failed for user <xxxx>. Reason: Server is in script upgrade mode. Only the administrator
can connect at this time. Error 18401.
Solution:
The SQL Server service just started and the engine is updating tables. Wait a few minutes
and try the SQL connection again.
Solution:
Was the External Class Instantiated (with a "new" keyword)?
If so, remove the "static" modifier from the variable's declaration and use the "new"
variable's name as a variable prefix.
If the External Class was not Instantiated (using Quick and Dirty Global Variables), prefix
the variable name using the physical Class Name, as seen in Solution Explorer. Do not use
the "new" keyword.
For example:
Use:
MessageBox.Show(<namespace name.> clSiteGlobals.CompanyName);
'String' does not contain a definition for 'length' and no extension method 'length' accepting a first
argument of type 'string' could be found (are you missing a directive or an assembly
reference?)
Solution:
Capitalize the .Length property, as in:
switch (strfoundString.Length)
{
:
}
Symptoms:
When attempting to use an app.config file and
MessageBox.Show (ConfigurationSettings.AppSettings ["<variable name>"]);
And yet, the statement still works properly, except for a compiler warning.
Solution:
In Solution Explorer, References, add a Reference to .NET, System.Configuration.dll
Then change the call statement to
MessageBox.Show (ConfigurationManager.AppSettings ["<variable name>"]);
See the App.Config chapter for full details.
Solution:
Remove the parenthesis from the .Now. This is not Excel.
Likely solution:
You are attempting to Convert.ToInt32(textBox1.Text) and the textBox was empty or
contained non-numeric values, such as a hyphen or other character.
You will see this message if a textBox or other string field is blank and is trying to be
converted to a numeric value - Visual Studio 2012 and older.
You will also see this if Convert.ToInt32(textBox1.Text) - where the .Text property is
missing.
Note: This is a run-time error (an unhandled exception) and your program has crashed.
Provided this is not a syntax error, consider a try-catch block.
System.Windows.Forms.TextBox - various:
CS1061
'System.Windows.Forms.<field>' does not contain a definition for 'text'
Possible Solution:
Usually this means you mis-typed or more likely, mis-capitalized a field's property value.
Consider:
Field1.text (with a lower-cased .text) vs
Field1.Text
CS1061
'MainProgram' does not contain a definition for "A000_Base' and no extnsion method 'A000_Base' accepting
a first argument of type 'MainProgram' could be found (are you missing a using directive or
assembly reference)
Likely solution:
In another class (e.g. MainProgram.cs), you have not yet created or have misspelled a
method called "A000_Base".
The best overload method match for '<form(parameter)>' has some invalid arguments
The best overloaded method match for 'string.PadRight(int,char)' has some invalid arguments
Solution:
When PadLeft or PadRight, the pad-fill is a character, not a string. Delimit a single
character with tic marks, not quotes.
e.g. strtestValue.PadRight(ipadLength, '*');
The best overload method for 'System.Windows.Forms MessageBox.Show(string)' has some invalid
arguments.
Possible Solution:
MessageBox.Show must have a string as the first item in the "show list". For example:
You can also trick the method by appending an empty-string before the first object being
displayed. Often, the object is converted to a string automatically, but the MessageBox
doesn't know this. Force it to get past the compiler by putting an empty-string in the front:
MessageBox.Show ("" + comboBox1.SelectedItem);
Another way around this problem is to use a ".ToString()" method. For example, with this
RegistryKey example (snippet):
MessageBox.Show (RegKey.GetValue("ApplicationName").ToString());
The class name '?' is not a valid identifier for this language
Likely Solution:
Close all frm (forms), then close and re-open the solution. It appears the development
environment can get confused, especially if you have been deleting methods.
The current project settings specify that the project will be debugged with specific security settings
Symptoms:
When using File-IO functions or Command-Line arguments (and others)
Symptoms:
When attempting a SQL Insert where the main table has a relationship with a sub-table. For
example, adding a new NAMES record, pointing to Record Category = 1, when using:
tblNamesCMD.Parameters.AddWithValue("@RecordCategorySeq", 1);
Problem:
@RecordCategorySeq = 1
Looking in the RecordCategory table, there is not a record with a value "1"
Possible Solution:
With if-statements, use a double-equal signs (not single) when comparing values; as in:
if (String.Compare (strReadLine, null) == 0)
Solution:
See "System.Configuration.ConfigurationSettings.AppSettings' is obsolete: "
Symptom 1:
You have not declared the variable using "string", "int", "float", etc, as in:
string myString;
int aNumber;
Or you have declared the value as "myString" but used the variable later as "mystring"
(case-sensitive).
Or you declared the variable in another (routine or module) and that declaration is outside
the scope of your current routine/method/module. A variable was declared in another
construct (such as within a for-next loop) and that construct has ended.
Consider the integer i, which is declared as part of the for-next loop but was used in a
MessageBox outside of the loop; in this case, variable "i" was out of scope and cannot be
used.
Symptom 2:
Error: The name '<FixedSingle>' does not exist in the current context.
You are setting a Property incorrectly, such as
textBox1.BorderStyle = FixedSingle
Possible Solution:
The item on the Right-side of the equal sign may need to be prefixed with a property name,
as in:
textBox1.BorderStyle = BorderStyle.FixedSingle
Possible Solution:
If the 'name' is a keyword-like name:
The name 'IsNumeric' does not exist.... do you need a Class Library prefix, such as:
util.IsNumeric?
The type arguments for method 'System.Array.Resize<T>(ref T[], int)' cannot be inferred from the
usage. Try specifying the Type Arguments explicitly
Issue:
Solution:
Manually copy existing array to a new, larger array -- but you will have problems that the
new array will have a different name. There does not seem to be a good solution to this
problem.
The type or namespace name 'boolean' could not be found (are you missing a using directive or an
assembly reference?)
C# is inconsistent in how one should spell boolean. When used in a function, use "bool".
The type or namespace name 'CurrentUser' | 'Local Machine' does not exist in the namespace
'Registry' (are you missing an assembly reference?)
Symptoms:
When attempting to read a specific registry key from the Windows Registry
Solution:
Confirm you have a "using Microsoft.Win32;" at the top of the program.
Then use this prefix in the RegistryKey command:
RegistryKey RegKey =
Microsoft.Win32.Registry.LocalMachine.OpenSubKey
(@"Software\Test");
The author is unsure why the "Microsoft.Win32.Registry" prefix is required when a "using"
statement is in place.
More generally:
You are missing a 'using' statement (e.g. using System.Management;).
if this does not resolve the problem, often you can add a new "Reference" (in Solution
Explorer). The name will usually be the same ("System.Management").
The type or namespace name 'DllImport' could not be found (are you missing a using directive or an
assembly reference?)
Possible Solution:
Add these two statements at the top of the (DllImport) class:
using System.Collections;
using System.Runtime.InteropServices;
Solution:
Spell "return" with a lower-case 'r'.
If a Void function:
return;
If a non-Void function:
return (some-variable);
The type or namespace name 'single' could not be found (are you missing a using directive or an
assembly reference?)
Solution:
With floating point numbers,
Use Single (with a capital S) or "float" instead.
Unlike "int", Single does not have a shorter alias. Many programmers prefer "float".
The type or namespace name 'StreamWriter' / 'StreamReader' / 'WriteLine' could not be found (are
you missing a using directive or an assembly reference?)
Likely solutions:
Confirm "using System.IO;" near the top of the program.
Confirm you are using the variable name on the WriteLine method.
Use this:
myreviewFile.WriteLine...
The type or namespace name 'Tasks' does not exist in the namespace 'System.Threading'
You are likely using one of the Wait methods and System.Threading.Tasks is only available
in dot net 4.0 and higher.
Solution:
In the project, select top-menu "Project, Project Properties".
Change the target framework from .NET Framework (3.5) to version 4.0 or newer.
Re-compile.
The type or namespace 'Windows' does not exist in the namespace 'System' (are you missing an
assembly reference?) File: cl800_Util.cs
Likely solution:
Recommended Solution:
Delete cl800_Util from Solution Explorer and re-add as a "Copy" (not as a link). Once
added, locate the WAIT routines and remove them from the cl800_Util library. Because
cl800 is copied, you are only damaging this program's local version. If you write a lot of
console applications and wish to continue using cl800, move the WAIT logic into its own
library.
Note: The text was changed to reflect this need. All Wait routines were moved into their
own class library. You would see this message if you tried to combine them contrary to
what the book recommends.
Related Solutions:
Console applications cannot call any "Windows-like" method. For example,
MessageBox.Show will not work in a console application. Adding a "using
System.Windows.Forms" defeats the purpose of a console application.
There were build errors. Would you like to continue and run the last successful build?
Symptoms:
When you compile (F5) your newly-written program.
Solutions:
Select checkbox "Do not show again" and click No. In other words, you would never want
to run the previous version of your code (before newly introduced bugs; you really want to
see the current bugs).
If you had already checked yes, see Tools, Options, "Projects and Solutions", "Build and
Run". Set "On Run, when build or deployment errors occur" to "Do not Launch".
Symptoms:
When attempting to read a SQL record.
Solution:
When assembling the SELECT statement (strSQLstmt), the "WHERE" clause's record
number (e.g. usually a SEQuence number), must be enclosed in tic-marks. For example:
Incorrect:
:
"WHERE NameSeq = " +
strEditPassedNameSeq;
Corrected:
:
"WHERE NameSeq = " +
"'" + strEditPassedNameSeq + "'";
Likely Solution:
A Convert.To phrase is missing a dot-property
It should read
Convert.ToInt32(textBox2.Text)
A string was found with a "\" (backslash) character. This is a reserved character needed for
"escape sequences." If you need a backslash character in a string (typically for a file-
name\path), double-up the backslashes, as in: "C:\\data\\filename.ext"
\t = tab
\r = carriage return
\n = newline
\r\n = crlf
\\ = backslash
\' = tic
\" = quote
Possible Solution:
In your declarations, usually at the top of your routine, a variable, such as
was declared but not initialized with an explicit value. Or you attempted to use a variable
on the right-side of an (equals) statement when it has not yet been populated by another
statement earlier in the code.
Another likely scenario is the variable was not initialized and a "while" loop was going to
set the value but the loop never ran (or more likely, the compiler thought the loop had a
possibility of never running).
Recommendations:
Consider using this type of syntax:
int myInteger;
myInteger = 0
Possible Solution:
The variable was declared in another module and has fallen out of scope.
Visual Studio cannot start debugging because the debug target <your project name\bin\debug> is
missing. Please build the project and retry, or set the OutputPath and AssemblyName
properties appropriately to point at the correct location for the target assembly.
Solution:
The Program must compile at least one time without errors or you will see this message.
Delete or comment-out the line causing a compiler error.
Run the program again (even if the program does nothing but display the form)
Close the running program and re-introduce the errors. This error should go away.
Solution:
Immediately after starting any new project, press F5 to compile the first empty-screen.
Then immediately close the running program and begin your coding work.
Solution (untested):
Select Menu: Project, Properties.
Go to "Build"; check the "Output" section at the bottom
Browse to your project's main directory/path, choosing Bin\debug"; this is where the actual
exe/dll lives.
When casting a number, the value must be a number less than infinity...
See
Error: Unable to cast object of type 'System.Windows.Forms.TextBox' to type
'System.IConvertible'.
Background:
When developing and testing a program, pressing F5 (top menu Debug, Start
Debugging) compiles the program, writes a temporary executable, and then
launches that .EXE as a separate task on the Windows task bar.
On the disk, Visual Studio builds a Debug folder in the Project's directory and in
there you will find a compiled .EXE and other support files – but only the .EXE is
needed for distribution. If you compile for "Release" (described below), a new
directory, "Release" is populated similarly.
The debug version (the .exe) contains code overhead that helps you test and
develop and this version is about 10 to 15% larger than a release version.
Although you can distribute the debug version to end-users, it is not recommended.
Type a short description for the program and fill out the company, copyright,
etc.
Manually set an assembly version (version number) and a file version. The
GUID is a random number, which you should leave as-is.
5. Once built, use Windows File Explorer to open the project's "bin\Release" folder
(for example: C:\data\Proj\VS\FileManipulation\bin\Release)
EXE files placed on a file server / file share, like all executables, are susceptible to
being infected by viruses. Be sure the EXE is in a read-only directory and your
development staff does not have write-access to the EXE or any DLL's in this
directory. This includes you. Use a service account, from a secured workstation,
when updating shared executables.
The taskbar and shortcut icon will be a default Visual Studio icon and your
program deserves better. Unfortunately, you will have to create, buy or steal your
own icon. Of the three techniques, one of them requires a bit of artistry and it is,
of course the most fun.
Obviously, thieving an icon is reprehensible and can get confusing if your program
shares the same icon as another. To help, Microsoft provides a free library of
icons, which can be found in the Microsoft Visual Studio Image Library,
downloadable at this link:
http://msdn.microsoft.com/en-us/library/ms246582.aspx
The number of (Application) icons is limited, but the number of toolbar icons is
expansive. Regardless, it provides a good starting point, especially if you want to
draw a complete set of icons, with more on this in a moment.
Icon Files:
Icon files (.ico) are peculiar because they contain multiple images, at different
resolutions and different color depths. A fully-populated icon has these images
embedded:
To do an icon properly, you need a full-fidelity version at 256 x 256 pixels and
another at 48 x 48 pixels, followed by progressively smaller and less-detailed
versions. You will have poor results if you take a full-sized version and attempt to
scale it down to the smaller sizes; color shading and pixellation will occur and it
is beyond the scope of this book to describe the intricacies. As you will learn,
there is an art to creating icons.
Contrary to popular belief, you cannot create icons with most photo editors and
you can't edit them properly with MSPaint (it only sees one icon within the file).
There are ways to draw an icon locally, saved as a PNG, and then upload to a
website for ico conversion, but these are generally limited to one size, one icon.
As a free solution, Microsoft recommends this web-based editor. It will not build
the larger Windows-8 style tiles, but it is generally workable:
www.xiconeditor.com:
http://msdn.microsoft.com/en-us/library/gg491740%28v=vs.85%29.aspx
Amazingly, Visual Studio, the editor, can also edit ico (icon) files, but it comes
with infuriating limitations, only editing the 16 and 32 pixel icons. And it does not
seem to give full control over color pallets.
With the editor, you can view, but not modify 48 and 256-pixel icons. Also, it
does not appear capable of building a new icon file – it only works against existing
ones, but this restriction is easily worked around.
A. Because you cannot create a new ico file with Visual studio, you must begin your
work with an existing icon. Locate a larger, full-fidelity icon, one with multiple
icons within the file. For example, from the downloaded ImageLibrary (see
above):
Protect the original file by copying the .ico to a temporary location before editing.
I recommend creating an "Images" folder within your project and storing icon and
clipart files there.
B. From any Visual Studio Project, select File, Open. Tunnel to and open the .ico file
and it will open in a tabbed-window, next to your code and form designs. The left-
nav shows each of the different sizes. Note the editing ribbon bar is only available
on 16 and 32-pixel icons.
The icon needs to be attached in two locations: One for the file system and a
second for the running program.
2. Return to the Form Editor (Form1, Design View). In the form's properties, locate
the "Icon" setting. Browse to the same .ico file and select. Again, the editor will
choose the properly-sized icon from within the file.
The "cheap and easy EXE" distribution method described above is my favorite
way of distributing a compiled program, but you can build a setup.exe that
automatically installs the software and builds an un-install routine. The benefits of
this design are:
For example, from Windows 8's Control Panel, Programs and Features:
Flexera Software
http://learn.flexerasoftware.com/content/IS-EVAL-InstallShield-Limited-Edition-V
isual-Studio
From the web site, download and follow the instructions. Once downloaded and
installed, close and restart Visual Studio.
For the Location, type a path that is near but separate from your original Visual
Studio Solution. For example: C:\data\Source\FileManipulation\ (Deployment),
where "Deployment" is the recommended name. As usual, I recommend leaving
Create Directory for the solution and letting the "Name" become the actual
directory.
The new solution opens into an Install Shield Wizard with a row of buttons/icons
showing each step, starting with "Application Information." This is called the
Project Assistant and if you get lost, look in Solution Explorer and double-click the
Project Assistant
9. Finally, select top-menu "Build", "Build Solution". Note that this is not part of the
Wizard steps. This completes the MSI build.
Results:
Note the Setup.exe, Setup.INI and .MSI file.
This entire directory can be positioned on a Share, CD, thumb-drive, etc. and is
ready for use.
Testing:
Possible Warning:
Warning: -7235: InstallShield could not create the software identification tag
because the Tag Creator ID Setting in General Information View is empty.
ISEXP: Warning.
Solution:
This is a warning and can be ignored with no harm. It suggests files required for
automatic inventory scanning are not in place and implies a corporate install. As
of 2014.03, this design is not in wide-spread use.
2. Or Enable the Software Inventory by filling out the fields in the "Software
Identification Tag" section.
Go to this site:
Magnicomp Software Tag Maker (free)
http://www.magnicomp.com/cgi-bin/mcswtagmaker.cgi
a. In your Package's "Application Files" section, add the tag file, so it installs
at the same level as your .EXE program.
b. A second copy of the tag file must also be copied, using "Application
Files": (example file name):
%PROGRAMDATA%\2009-04.com.keyliner\regid.2009-4.com.keyliner.e
xamplefilemanipulation_1396226547.swidtag
Note: The Vendor's Generated Tag webpage will have the exact link and
name you should use – cut and paste.
Your MSI package must also build the Program Data directory:
"%PROGRAMDATA%\2009-04.com.keyliner"
Recompiling:
There are other features, such as automatic updates when version numbers change.
This is beyond the scope of this chapter.
1.01 2015.04.10
Initial Release. Submitted to Wrox Publishers; declined due to length and
competition with other titles.
Advanced copy to D.Parks for review
1.02
Chapter 19 Wait States
Expanded Auto-launch example, 19.5
Chapter 20 Printing
Added reference to "Add Reference" for System.Printing on Console
apps
"Strings," which have already been used in earlier chapters, are nothing more than a
'string' of character data. Examples include words and phrases, as well as non-numeric
data, such as "123 N. Elm Street", phone numbers, part numbers, and the like. This
chapter describes basic and advanced string manipulation techniques.
Topics:
• string declarations
• \escape codes, special characters, @verbatim strings, ASCII strings
• string.Concat and + - appending/ Concatenation
• string.Compare(A, B, true)
• .EndsWith
• .ToLower(), .ToUpper()
• .PadLeft(), .PadRight()
• .Trim() - for lobbing-off leading spaces and other characters
• .TrimStart(), .TrimEnd()
• .Length
• Character arrays
• null and empty strings tests using "+"").TrimStart( ).Length > 0"
• Testing for Blank/empty data
• .IndexOf("=",0) - Finding string positions
• Overloading, introduced
• .LastIndexOf - searches from the back-end forward
• if(<string>.Contains("="))
• IsNumeric, limitations
• .Substring
• (LeftString, RightString, MidString)
• Substring with multiple delimiters
Right Strings:
<string>.Substring (<string.Length> - 10).Trim()
//Right 10 Chars.
Mid Strings:
<string>.Substring (<start position base-0>,
<number of characters>)
<string>.Substring
(delimiterFrontNDX base-0 + delimiterFront.Length,
<number of characters>)
if (Char.IsNumeric('<single-byte char-value>'))
if (Char.IsNumeric("string value", #-of-characters))
For the remainder of this chapter, practice the examples by building a test program
similar to the ones used in prior chapters. All examples below use a screen with
"button1" and (sometimes) textBox1. See Chapter 1 for instructions.
String variables must be declared (defined) before they can be used. Do this with a case-
sensitive "string" command. The keyword "string" declares what type of variable is
being declared, followed by the name of the variable.
String variable names can be any single word or phrase, with no spaces. The name you
assign is an invented name of your choosing. This author likes to prepend the string-
name with "str" as in "strtestVariable".
Optionally, the variable can be initialized with a default value on the same line, or it can
be assigned a value separately with a different statement. Use quotes to surround hard-
coded text strings (also called a 'literal').
Examples:
string strtestVariable;
string strtestVariable2 = ""
string strtestVariable1 = null
string strtestVariable3 = "123 N. Elm Street";
string strtestVariable4 = textBox1.Text;
string strtestVariable5 = strtestVariable4;
strtestVariable = DateTime.Now.ToShortDateString();
Tabs, carriage-return-line-feeds, reserved characters, and other white-text are also strings
but these cannot be typed directly into the program's code. In other words, how do you
type a "tab" without invoking the keyboard's tab and how do you place a quote-mark
within a quoted string?
To work around this, some strings are represented in the editor with special characters,
called "escape codes".
Tab \t (ASCII 9)
Backslash \\ (ASCII 92)
Double-Backslash \\\\ (ASCII 92, 92)
Double Quote \" (ASCII 34)
Single-quote (tic) \' (ASCII 39)
Carrage-Return-LineFeed \r\n (ASCII 13, 10)
Unicode \uxxxx where xxxx = unicode number
These characters are typed as 'clear text' (typed in the editor as a literal value) with a
leading backslash (" \ "), signifying their special nature. For example, a tab is
represented with "\t". Although the "\t" is typed as two characters, the compiler treats
it as a single character – a tab.
Reserved Backslash:
The backslash (" \ ") is reserved as an escape sequence marker. If that actual character is
needed in a string, use double-backslashes (" \\ "), again where two typed characters
represent a single character. Since double-backslashes are used in server and share
names, those would require four backslashes (" \\\\ "):
This can get you in trouble if you make a mistake. Consider this flawed string:
where the embedded slash-t (\test.txt) was intended as a DOS path but was interpreted as
a tab – "C:\Data (tab\t) est.txt". In this case, the editor would not have flagged this as an
error.
Carriage-Return-Line-Feeds CRLF:
Results - noting the line break and the word " and", with a leading space:
four score
and seven years
In the second half of the example, three literals were combined into one string, using a
"+" (concatenation) and both examples are functionally equivalent. Concatenation is
discussed in a few pages.
Embedded Quotes:
Since strings are usually initialized with a quoted literal (e.g. "Bob said"), an embedded
quote-mark ( " ) cannot be placed in the middle of the string. In these cases, use an
escape character: backslash-quote ( \" ).
Although this is somewhat hard to read, the assembled string starts with a regular quote,
followed by a backslash-quote. Note how the end of the string is delimited:
Character strings with embedded backslashes are difficult to read and many developers
choose an alternate technique called a "verbatim" literal. Preface the quoted string with
an at-sign (@) and spell it in the 'natural' way:
Normally, a literal cannot span multiple lines in the editor but with verbatim strings you
can. The results can be weird. Note how the following string has its closing semi-colon
on the third line:
ASCII Codes:
All typed characters are represented with an ASCII code. For example, with the English
code-set, the letter 'A' is represented with an ASCII 65 while a lower-case 'a' is ASCII 97.
The code can also show other, non-visible characters. A tab is ASCII 9 and
CRLF (\r\n) is ASCII 13 + ASCII 10.
This code demonstrates how to convert from 'A' to the number ASCII 65 and back. Note
the new type of declarations, "char" and "byte".
MessageBox.Show(myInt.ToString());
String concatenation combines two or more separate strings into one. C# uses the "+"
(plus symbol) for string concatenation but Visual Basic and Excel programmers will
remember the "&" symbol. Previous chapters have used this idea several times.
MessageBox.Show(strtextA + strtextB);
MessageBox.Show(strtextA + " and " + strtextB);
}
Results: "CatDog", "Cat and Dog". Note the second example uses the word " and " with
leading and trailing spaces.
An alternate way of concatenating uses the string.Concat method and this is identical
to the "+" method above. There is no limit to the number of strings that can be
concatenated:
or
In C#, the "+" symbol actually translates to the 'string.Concat' method. Since this
happens at compile time, there is no particular benefit in choosing one method over the
other. Most developers use the plus.
".Concat( )" is a "method" applied against "string." You can think of a method as a
subroutine or function and methods always require parenthesis. Some methods, such as
the .Concat, require values to be passed through the parenthesis, while others can be
empty, as in .ToString( ) or .ToUpper( ). There is more on this as the chapters progress.
Both string.Concat and "+" can append numeric values (as a string) to other strings,
without first having to explicitly convert the numbers to a string (This is a change,
starting in Visual Studio 2005/2010). For example, the following appends the letter "N"
and a numeric integer into one string, forming "N1030". The code example shows three
different techniques to do this, all yielding the same results:
strassembledPartNumber =
string.Concat(strpartNumberPrefix, iPartNumber);
MessageBox.Show (strassembledPartNumber);
strassembledPartNumber =
strpartNumberPrefix + Convert.ToString(iPartNumber);
MessageBox.Show (strassembledPartNumber);
Each of the examples are acceptable but the "Convert.ToString" most clearly indicates
your intentions. It is somewhat surprising that C# allows an implicit conversion from
numeric to string, especially in the third example. Prior versions of C# did not allow
this, especially if the first term were numeric, and out of habit, I still tend to use
Convert.ToString.
Starting in VS2012, the first parameter in a MessageBox statement can also be numeric
and it will be implicitly converted to a string.
MessageBox.Show(strpartNumberPrefix + floatingPartNumber);
MessageBox.Show
Results: "N123.456"
The if-statements in the prior chapter introduced the concept of equalities. Numeric
values were easily compared with double-equal-signs (==) and with greater-than and less-
than symbols (<, >). But string comparisons are more complicated. Keep these rules in
mind when comparing strings:
This example compares two similar part numbers, one with an uppercase prefix and the
second with a lowercase prefix - "N1030" and "n1030":
if (strtextA == strtextB)
MessageBox.Show("they are equal");
else
MessageBox.Show("they ain't equal");
}
Case-Insensitive Comparisons:
if (strtextA == strtextB.ToUpper())
if ("N1030" == "n1030".ToUpper())
These are unsafe tests because although strtextB was shifted to uppercase and
successfully tested, what would happen if strtextA ("N1030") were accidentally set to
lowercase by another routine? This is easily fixed by shifting both sides to upper-case,
in this time-honored tradition:
if (strtextA.ToUpper() == strtextB.ToUpper())
string.Compare( ):
It will be a shock to VB Programmers, but C# does not allow ">" and "<" symbols in a
string comparison. This forces you to use a more cumbersome "string.Compare" method
for inequalities but it does have a minor improvement because you can make the
comparisons case-insensitive.
The syntax uses a lower-cased keyword "string" (type the word, 'string') followed by a
dot-Compare. Within the parenthesis, list the two variables to compare. A third
parameter specifies whether the comparison is case-sensitive or not (bool ignoreCase).
Oddly, string.Compare( ) returns an integer -1, 0, or 1, instead of a True or False:
integerResult =
string.Compare (string1, string2, IgnoreCase:True|False)
• string.Compare() uses the actual word "string" - you are not replacing this with
your variable names until inside of the parentheses. This command reads as "The
'Compare' method of the 'string' Class.
0 if equal
+1 if textA comes alphabetically before textB
-1 if textB comes alphabetically before textA
With the IgnoreCase flag, string.Compare can test if two strings are equal, saving the
trouble of shifting both strings to upper or lowercase before the test:
As you type "string.Compare", the editor's intellisense displays a pop-up help-box with a
short description of the command, along with a list of available parameters. Note the
"int" (integer) term on the first line, acting as a reminder on what is returned by the
function.
if (compareResults == 0)
MessageBox.Show ("Both values are the same");
else
{
if (compareResults == +1)
MessageBox.Show ("Value1 is first alphabetically");
else
MessageBox.Show ("Value2 is first alphabetically");
}
}
where:
• The compare-results (-1, 0, 1) is stored in an intermediate variable near the top of the
routine. This keeps you from having to perform the test a second-time within the
nested-if.
• As a reminder:
+1 string1 > string2 (alphabetically, string1 is first)
0 string1 and string2 are equal
-1 string 2 > than string1
• In the example, string1 is alphabetically first. The variable order makes a difference
in the results from the string.Compare.
• Nested-ifs are cumbersome. A switch-statement is a better way to code this; see the
next section.
Using if-statements to test for a three-way string.Compare (0, +1, -1) is cumbersome.
Because of this, most programmers use a "switch" statement (see previous chapter).
The order of the case-tests do not matter; the first true condition is used. Notice a
declared integer holding-value for the compare-results is not needed; contrast this with
the nested-if example, which needed an integer iresult.
where:
• When inserting "string.Compare" into an "if" or "switch", keep in mind that the (if-
statement) needs its own parenthesis - which are separate from the parens used by
the string.Compare. Notice the two leading and closing parenthesis:
"string.Equals" performs the same test as "==", returning a true|false. The test is strictly
case-sensitive and does not perform greater-than or less-than comparisons. It is
mentioned here because some programmers believe this command has a slight
performance gain over a simple ==; however, the compiler translates all string double-
equals to this function at compile time, so the gains are artificial. Regardless, this
function is self-documenting and syntactically consistent with other string functions.
string.Equals( ), complete
private void button1_Click (object sender, EventArgs e)
{
string strtextA = "n1030"
string strtextB = "N1030"
Results: <stuff to do if not equal>. This is only a case-sensitive compare, where "n" and
"N" are not equal.
string.CompareOrdinal:
A 65 a 97 0 48
B 66 b 98 1 49
C 67 c 99 2 50
D 68 d 100 3 51
Z 90 z 122 9 57
The computer needs a way to track both visible and non-visible "characters," and this is
why DOS used an 128/256 character ASCII table. (Other computer systems use different
schemes - for example, IBM mainframes use an encoding called "EBCIDC" (or Hex).
Windows now uses a newer table called "Unicode" - which represents 65,000 characters
– giving the ability to display Asian languages.)
You can probably see where this is leading. string.CompareOrdinal compares two text
strings using the ASCII table for the sort-order. For example, "A" comes before "a"
(ASCII 65 < ASCII 97).
string.CompareOrdinal( ), complete
private void button1_Click (object sender, EventArgs e)
{
//string.CompareOrdinal compares ASCII values,
//not by a Dictionary Compare
int result;
iresult = string.CompareOrdinal(strtextA, strtextB)
where:
This method examines the end of a string, looking for a passed-value. If found, the
function returns a boolean true/false. A common use is to look at a filename's extension.
Typically, the command is paired with an if-statement.
General use:
<original-string>.EndsWith(<compare-string>)
<original-string>.EndsWith(<compare-string>, true, null)
where:
A simplistic test like this would likely fail because of upper and lowercase concerns (.xls
vs .XLS, plus a concern with Excel's newer style, .xlsx):
if (myFileName.EndsWith(".xls"))
{
//Do this stuff
}
if (myFileName.ToLower().EndsWith(".xls"))
if (myFileName.EndsWith(".xls", true, null))
The next example carries the test to a more capable level, detecting both the traditional
Excel xls extension and the newer .xlsx, introduced with Microsoft Office 2007. For
efficiency, the test converts the filename to lowercase one time, rather than with each
sub-statement:
myFileName = myFileName.ToLower();
if (myFileName.EndsWith(".xls") ||
myFileName.EndsWith (".xlsx") ||
myFileName.EndsWith (".xlsm") ||
myFileName.EndsWith (".xlsb"))
{
MessageBox.Show("This is an Excel File");
}
else
MessageBox.Show("This is not likely an Excel File");
}
This example uses .EndsWith to help standardize end-user-typed path names, forcing a
closing backslash, preparing for a fully-qualified path (See also Chapter 23:
Path.Combine). For example, if the user typed: "C:\Data", change it to "C:\Data\". If
they typed "C:\Data\" or if they typed a complete path and filename, such as
"C:\Data\09Financials.xls", leave it as-is.
where:
Note: This example is flawed in that it only considers ".xls" files but still demonstrates
the concept. A more substantial routine could be written by looking for a period
(extension).
A string's numeric length is found by examining its ".Length" property. Lengths are
often needed in substring calculations and in data-entry validations. This example looks
at text typed in textBox1:
<string>.Length, complete
private void button1_Click (object sender, EventArgs e)
{
//Display the length of a typed-string in textBox1
int ipartNumberLength;
ipartNumberLength = textBox1.Text.Length;
MessageBox.Show
("The number of characters in TextBox1 is: " +
Convert.ToString(ipartNumberLength));
}
where:
• ".Length" is a property (not a method or function). Because of this, it does not have
parenthesis. The Length property can not be changed in code. See below for
information on how to identify a property from a method..
• Lengths are base-1 counters, where the first character is at position 1, the second at
position 2, etc. In C#, all lengths are base-1 while all other commands start counting
at base-zero, where 0 is the first position.
• If present, leading and trailing spaces are counted as part of the length.
• Empty strings ("" quote-quote) have zero lengths. Note this is not an undefined or
null length - the length is actually zero.
• Null strings have undefined lengths. If you try to retrieve a null-length, the program
will abend and logic is needed to trap the event. Detecting empty and null strings
takes special care; see later in this chapter for details, and again in Chapter 6.
• Numeric variables do not have lengths and the program will not compile if
attempted.
Identifying Properties:
All functions and methods use (parenthesis) but properties such as .Length do not. The
editor gives a hint about which-is-which while typing. For example, as you type
Hovering over the "Length" context menu displays additional fly-out help. The first
word shows "int" (the results of the property is an integer value) and it expects a string as
input (textBox1.Length). Now that all this has been explained, standard strings, such as
strtextA, have only one property – Length; but other objects, such as textBoxes, have
nearly a hundred properties, including Width, Background Color, base-fonts, border-
styles and the like. More about this later.
<string>.ToUpper( )
<string>.ToLower( )
strmyName = textBox1.Text.ToUpper();
strsomeValue = strsomeValue.ToLower();
Because this is a method (note the box-icon in the illustration below), parenthesis are
required. But in this case, the method does not accept parameters and the parenthesis are
always empty.
Strings and textBoxes are often shifted for if-statement comparisons and the shift
happens temporarily, just for the test. For example, the next code-block compares what
was typed in textBox1 with a hard-coded string "MY DEAR AUNT SALLY". Users can
type "my dear aunt sally" in any case: upper, lower or mixed, and the if-statement's shift-
to-upper will generate a positive match.
The if-statement's shift was only for the duration of the test because the .ToUpper was
not assigned to a new value. In other words, textBox.Text remains mixed-case once the
if-statement completes.
strtestA = strtestA.ToUpper();
comments:
Since it was not assigned to a variable, the compiler shifts then discards the
uppercase results. Instead, use this command:
strtestA = strtestA.ToUpper();
• All Methods have parenthesis. This includes loops, if-statements, Concats, and
.ToUpper and .ToLower. Parenthesis are required even if no values are passed
through them. This is described in more detail in future chapters, but for now,
remember methods always have parenthesis while "properties," such as ".Length",
do not.
• If you forget the parenthesis after a .ToUpper or .ToLower, you'll be blessed with a
compiler error similar to this:
VS2012 - VS2015:
Cannot convert method group 'ToUpper' to non-delegate type 'int'. Did you intend
to invoke the method?
• With textboxes, it is easy to forget the .Text property. In other words, you cannot
assign a string to the object, you must assign it to the property:
As a reminder, properties appear this way in the Intellisense editor. You will also
see methods (such as ToUpper) or events (such as "Click" event). Events are
discussed in future chapters.
The .ToUpper() and .ToLower() methods work on any textual data – including
textBoxes, standard variables. Create a new program or modify an existing, giving it the
following characteristics: A single button, "button1" and two textBoxes, textBox1 and
textBox2.
When button1 is clicked, take a hard-coded string ("Space Ghost") and shift to
uppercase, storing the results in textBox1. Then store a lowercase result in textBox2.
A Reminder:
See Chapter 1 for details on how to build the initial program. Click in "Form1.cs
[Design]" to get to the form design view. Use the Toolbox flyout to populate the panel.
with the three objects. Attempt the program now before reading the steps.
Then, from the Toobox flyout menu, "Common Controls", click and drag two "textBox"
objects from the menu to the panel. Drag a "button" object. Arrange them so they look
like the illustration above. If loading for the first time, the toolbox flyout may take a
moment to load.
Click each textbox and examine its properties (Name) to decide which is named
textBox1 and textBox2.
3. Double-click button1, taking you to button1's "Click event." (More on events in the next
chapters.) Add this code:
Results when button1 is clicked: textBox1 gets an upper-cased "SPACE GHOST" and
textBox2 gets a lowercased string. The subsequent MessageBox shows testString
remains unmodified (case). Notice the use of "textBox1.Text" – you must use the .Text
property.
<string>.PadLeft (int);
<string>.PadRight (int);
<string>.PadLeft (int, '*');
".PadLeft" and ".PadRight" append spaces (or other characters) to the string, bringing the
total length to the value of the passed integer. Left-padding with spaces can help to align
lists or columns of numbers, especially in printed reports.
This example takes two numbers and pads them left, for a total length of 10. The results
are displayed both on the panel and in a MessageBox. The panel will be set to a fixed-
space font while the MessageBox will remain a proportional font.
Example Setup:
Note: If, from previous examples, you have a textBox2, delete or move it down, making
room for the multi-lined textbox.
B. Highlight textBox1. In the Properties Window (lower, Right corner of the design view
window), change the font to Courier-New:
where:
• The length of the passed integer (e.g. 10) is expected to be longer than the starting
string's length or nothing appends. In other words, padding with 5 has no affect when
the length of the "1000.00" is already 7 long.
• Generally, when trying to align a stack of numbers, pick a length that is 3+ the widest
expected (string) number. This accounts for a two-character-wide column separator
plus room for a +/- sign.
• When padding, the output device almost certainly needs to be a fixed-width, non-
proportional font. Recommend the true-type font "courier new".
Since spaces are hard to see, the example appended a tic-mark (apostrophe) before and
after the MessageBox's testString. Notice the quote-tic-quote. The resulting punctuation,
(tic) ' 1000.00', shows where the spaces are. This is a trick and is only useful while
debugging with MessageBoxes.
<string>.PadLeft(int, '*');
In this block of code, display a numeric value with a leading dollar-sign and asterisks.
The number is a floating-point number (not an integer or a string) and it is declared using
an "F". More on numbers in the next chapter:
string strPrintedString =
("$" + fDollarAmount.ToString()).PadLeft(10, '*');
MessageBox.Show(strPrintedString);
}
("$" + fDollarAmount.ToString())
• The same effect could be achieved in this fashion, reading left-to-right. But I like the
first method because the order is explicit:
In this example, hardcoding 10 characters for padding will cause problems for numbers
longer than 10 characters. More advanced number formats, discussed in future chapters,
need to account for decimals, commas and other punctuation. A better solution is to print
at least 3 leading "stars," regardless of the length. "Variablize" this by incorporating a
length calculation into the passed integer. Take the length of the existing string and add
three + 1 for the dollar-sign.
string strPrintedString =
("$" + fDollarAmount.ToString())
.PadLeft(fDollarAmount.ToString().Length + 4, '*');
Padding with leading zeros is often needed with day and dates, changing a single-digit
month like "5" to "05". Here is a typical example.
The .Trim method removes leading and trailing spaces from a text string. User-typed
data-entry fields always should be trimmed because users will type leading and trailing
spaces for reasons unknown. When parsing input text files, especially if the input files
were generated by human beings, you invariably need to trim the data.
For example, the text string " Space Ghost", with leading spaces, is trimmed to "Space
Ghost" when passed through the function. Often the result of the trim is assigned directly
back to the field being trimmed:
where:
C# does not trim redundant internal (embedded) spaces. For example, "Space Ghost",
with 3 internal spaces, remains the same once trimmed. This will require a specialized
function; see Chapter 8 for a SuperTrim function.
Combining Commands:
Often, the dot-trim method is combined with other commands. For example, you could
trim leading spaces, shift to upper-case and then pad with leading asterisks, all in one
command. The results of the statement are often assigned back on top of the original
variable. The command is read and processed from left to right:
<string>.TrimStart()
<string>.TrimEnd()
.TrimStart() trims leading spaces, leaving internal and trailing spaces, and .TrimEnd()
removes from the end. (Visual Basic used "LTrim" and "RTrim".) This example removes
leading spaces and uses tic-marks to show the results:
Results in 'Space Ghost '. (Trailing spaces survived; printed with tic-marks).
By default, Trim removes leading and trailing spaces but the command can be modified to
trim other characters. For example, you could trim leading asterisks or quotes.
As neat as this sounds, this is less-than-useful because if you knew the characters you
needed to remove, you probably also know how many and where they were. The added
setup time isn't worth the trouble and other methods could be used to remove the text.
However, since you might occasionally see this command it is still worthy of discussing
and it introduces new concepts.
In order to Trim with other characters, two new concepts are introduced: The 'char'
variable and "arrays."
Earlier, Trim methods always end with a pair of empty parenthesis, as in:
strtestString.Trim();
However, inside the parenthesis you can pass 'char' data. For example, to remove the
letter 'C' from a prefixed-part number ("C1030"), use either of the two following
commands, where the second is more reliable:
//Method 1
//(Will fail if leading spaces are present)
//(Will fail if the 'c' is lower-cased)
strstrippedPartNumber = strpartNumber.Trim('C');
MessageBox.Show(strstrippedPartNumber);
strstrippedPartNumber = strpartNumber.ToUpper().TrimStart('C');
MessageBox.Show(strstrippedPartNumber);
}
There are un-intended consequences with the two Trim statements shown above. The
first command removes both leading and trailing 'C's. If the part-numbers happened to to
be "C1030C", the 'C' is trimmed from both sides.
The second command resolves this problem by trimming only the leading character using
"TrimStart" and it also fixes the problem when you don't know if the part-number is upper
strstrippedPartNumber =
strpartNumber.TrimStart('C').ToUpper(); //fails
In another contrived example, what if your part-numbers could have different prefixes,
such as "A", "B", "C" and possibly leading spaces and you needed to trim the prefixes?
You could use four separate Trim statements or you could use an extended feature,
passing an 'array' of individual characters for the trim. In one statement, all of these
possible prefixes could be trimmed.
Changes in VS2012:
Starting in Visual Studio 2012, Trimming with multiple characters was blessedly
simplified. An array of valid trim-characters could be passed, via an explicit array, or
now you can trim using an implicit array, where the values are typed directly into the
statement. The new design is easier to code and is easier to explain.
To trim the characters 'A', 'B', 'C', and ' ' (space) from a part-number prefix, use an
extended Trim command (called an "overload").
Results: "1030" with the prefixes and leading and trailing spaces removed.
With VS2010 and older, you had to build an explicit array to pass into the Trim function.
Even with newer versions, it is still useful to learn this technique – especially if the
number of Trim characters are in a longer list, where the values are more easily
controlled.
Begin by building a character array. Declare a "char" variable, much like you would
declare a string, but after the char-data-type add brackets, where the brackets indicate an
array. Unlike Visual Basic, the brackets go after the data-type (rather than the variable
name).
The variable's name can be any invented name and these examples I am using a long and
descriptive name, "aremoveTheseCharacters", where I like to use a prefix "a", reminding
me this is an array. Within the array, specify each individual (part-number prefix), using
character-tic-marks and comma-separating the values:
You may have correctly surmised there can be different types of arrays: string arrays,
numeric arrays, and 'char' arrays. However, Trim only accepts 'char' arrays.
The braces mark the beginning and ending of the list and this list becomes the array.
Notice how the array includes a space-character, which is an apostrophe-space-
apostrophe. There are other ways to load this array and this is left for Chapter 22, Arrays.
The array will be fed into the .Trim command and it will trim the letters A, B and C, as
well as spaces. Place the array's name (aremoveTheseCharacters) in the Trim's open-and-
close parenthesis.
string strstrippedPartNumber =
strpartNumber.ToUpper().TrimStart(aremoveTheseCharacters);
MessageBox.Show (strstrippedPartNumber);
}
Results:
PartNumber " C1030" becomes strippedPartNumber = "1030".
Passing " b1030" trims to "1030".
And of interest, "abc1030", "ccc1030" and "a b c 1030" all return "1030".
"Z1030" would not be trimmed.
This is well-and-good but not as useful as it would seem. Consider these variations of a
user-typed phone-number. Assume the program needs to remove all punctuation, both at
the margins and internally:
(208) 383-1234
208-383-1234
208.383-1234
(208383-1234
208/383-1234
char [] aremoveTheseCharacters =
{' ', '(', ')', '-', '.", '/', '\\'};
To clean these numbers, more advanced tools are needed and they are discussed next.
To properly format a phone-number, you need to remove (trim) all user-punctuation, then
insert your standard punctuation back into the string. A fabulous method that does just
that can be found in Chapter 21, Formatting.
Regardless of the function's name, char.IsNumber examines character or string data for
numeric values (zero-9) and returns a boolean "true" if it contains all numbers. The
command is almost always used with an if-statement:
if (char.IsNumber ('9')
{
As you will see, this function is too limited for most real-world
problems. Read through this section for more information and
other possible solutions.
Consider this simplistic example, which examines a character 'A' to see if it is numeric.
This version only looks at a single character:
if (char.IsNumber(testChar))
{
MessageBox.Show
("This is numeric: " + testChar.Convert.ToString());
}
else
{
MessageBox.Show ("This is not numeric");
}
}
As you are typing the "char.IsNumber(" command, the popup-help shows a list of
variations in a scrollable list.
'IsNumber' has 2 overloads, where the first has the least number or parameters and the
second is slightly more complicated. Click the arrows to view each variation. The second
overload shows it returns "boolean" and accepts a passed string, along with a required
index/numeric value.
When you are reading an overload, the parameter list always and
only shows "required" variables – none are optional – but there
are usually multiple overloads, each with their own list. This
way Visual Studio does not have to document all variations on
required and optional parameters. Each entry is a self-contained
list of all required values.
Consider this routine that tests the first three characters in a string for "numeric-ness":
if (char.IsNumber(strTest, 2))
MessageBox.Show ("The first three digits are numeric");
else
MessageBox.Show ("The first three digits are not numeric");
where:
The numeric value is listed as an "index," which in Visual Studio means a numeric
count, starting at zero – base-0. For example: char.IsNumber ("123 Elm", 2) only
tests for numeric against the first three digits. The index starts counting at position
zero. 2 is the third digit.
• Test the entire string ("123 Elm") by using the string's total length. In Visual
Studio/C#, lengths are always calculated as base-1 (by design), which starts
counting at "1". Comparing a base-0 to a base-1 count means you have to off-shift the
counts by one:
char.IsNumber requires an index and to test the entire length, take the calculated
length minus one: char.IsNumber ("123 Elm", 7 - 1 = 6)
if (ifoundSpacePosition > 0)
{
if (char.IsNumber(strstreetAddress, ifoundSpacePostion -1))
{
MessageBox.Show("Address has numeric prefix");
}
else
{
MessageBox.Show("Address not numerically prefixed");
}
}
else
{
MessageBox.Show("No space detected");
}
}
Interestingly, this is the only numeric test available in C# and it is limited in what it
detects. For example, char.IsNumber claims these string values are not numeric:
"-54"
"54-"
"6.2431"
"$54.12"
And what about these user inputs: should they be considered numeric?
"1,800.23"
" $100"
The ".Replace" method replaces any string with another string and the general syntax is:
<string-variable>.Replace
(<search-original-string>, <new replacement-string>)
MessageBox.Show(strtest.Replace("Robert", "Bob");
}
where:
• Search and Replace strings are case-sensitive and the search must match exactly.
• This method does not have a way to replace just the first or last occurrence; use the
"substring" commands or Chapter 6's util commands.
• The example above does not change the original string; it only replaces for the
duration of the MessagBox statement. To replace the original, re-assign strtest to
itself, as in: strtest = strtest.Replace("Robert", "Bob");
Parts of a string can be removed by replacing the Search-string with an empty-string. This
is most commonly used when the results are written back over the top of the original
string:
Programs often need to convert string (character) data, usually from external files or from
data-entry text-box fields, into a pure numeric value. Numeric conversions are required
before mathematical operations can be performed. These operations are the opposite of
the Convert.ToString seen earlier and work in a similar fashion.
Consider this code, which converts a string "123" to a numeric value, then adds 10, then
another 8:
iNumber = Convert.ToInt32(strInput);
MessageBox.Show (Convert.ToString(iNumber));
}
where:
MessageBox.Show
("Intermediate answer: " + (Convert.ToInt32(strInput)+10);
If the input string contain nonsensical data (123 N. Elm), you will incur the wrath of a
runtime error "Input string was not in the correct format." Use a try-catch to intercept
these types of data-problems; see "try-catch Exception Handling" near program 4.4,
below. Later chapters show more sophisticated techniques.
Notice a string, such as "123.10" cannot directly convert to an Integer (it fails with an
"Input string was not in the correct format" because of the period). To convert, first
move the variable to a floating-point number then to an Integer (truncating the fraction);
see below.
Converting from an integer to a floating point number is acceptable because the jump
from "123" to "123.00" is a lossless-change. In instances like these, C# allows "implicit"
conversions without question and the conversion can be made with a simple assignment
(equal):
But converting from a floating (123.10) to integer (123) risks truncation and the compiler
will not allow it unless explicitly converted. Notice how floating-point literals are
suffixed with an "F":
Numbers can also be converted to a new type using a technique called "casting". For
example, an integer can "cast" to a floating-point number and visa-versa. This is best
described with an example:
where
• (int) <variableName> is the casting statement – the word "int" in parenthesis. When
casting, prefix the variable with parenthesis and the type of variable you want it cast
to. The syntax is odd because the parenthesis surround the first part of the statement,
not the variable name.
• Cast as (int), (double), (float). Other types of objects beyond the scope of this chapter
can also be cast.
• Note the "F" in the "floatNumber = 123.1F" declaration. When initializing a floating-
point number as a literal, suffix the declaration with a capital F, letting the compiler
know this is a floating-point number. ('float' and 'single' are the same. More details in
the next chapter.)
• You can cast one type of number to another. But you cannot cast a string to a
numeric data type. Instead, use "Convert.To" – assuming the string is a valid
number.
Casting is Confusing:
When casting is and is not allowed can be confusing. As you research other people's code
on the web, you'll find that some will cast variables from one data-type to another – but
the idea for casting was really designed for changing classes and other object-oriented
features, none of which have been discussed yet. At the risk of over-simplification, I'll
generalize: For commands discussed in this chapter, cast as part of an intermediate
calculation or when moving from similar data-types. Use Convert.To if in doubt or are
having problems with casting. Convert.To is always a safe command to use. In any case,
the compiler will tell you if a cast is not allowed.
Strings hold character data but they can also hold "nothing" – that is, they may be empty,
undefined, null, or they may have spaces. Unfortunately, with several types of
nothingness; testing for them is problematic and most programmers struggle with this.
This section shows how to best detect these states and it demonstrates a powerful tool that
Microsoft does not supply.
This section, along with Chapter 6, demonstrates the tools needed to tackle this subject
with skill, but in order to learn what is needed, I will be taking you on a scenic tour of the
available commands. There are a litany of suggested tests on the web and most have
subtle flaws.
if (strtest == null)
if (strtest == "")
if (strtest.Trim() == "")
if (strtest.Trim().Length == 0)
if (strtest != null && strtest.Length > 0)
Microsoft's recommendation:
if (string.IsNullOrEmpty(<strstring>))
There are several states of "emptiness," each of which is different. Here is a string
variable in various states of declaration:
When a variable is declared but not assigned a value, it contains a null. A null is not an
empty string nor is it a zero. It means the value is undefined or unknown.
Empty Strings:
Strings that are initialized or assigned "empty-strings" are also different than nulls or
undefined. An empty string is where the variable is assigned the value = "" (two double-
quotes – "quote-quote" or alternately with string.Empty(stringname)). An empty-
string is just what the name implies: it is a string with nothing in it. From the compiler's
point of view, this is a perfectly good string. textBoxes default to this value when first
created. The compiler considers this populated with data even though there is no "real"
data.
null:
"nulls" are strange from a data-processing view. They are not empty strings, nor are they
a numeric zero; technically, a null indicates no value was specified. Un-initialized
variables are null and often databases populate unknown values with nulls and certain
array operations, when they have nothing to work with, generate nulls. When most C#
commands encounter one, they implode. Because they are not a string or a number, they
have to be tested carefully and separately.
Spaces:
Setting a field's value to one or more spaces is mentioned because end users sometimes do
this in data-entry fields – that is, they type a space or a line of spaces in order to "blank
out a field." Because of this, you often receive an exported spreadsheet or file with field-
spaces. This type of data is a nuisance to program around. Because a space is an actual
value, it eludes all empty-field detection and yet for all practical purposes, the field is
empty. The IsBlank and IsFilled functions, described later, navigate this problem with
grace. In your code, do not initialize or assign variables with a space-character (quote-
space-quote) just to "clear them out." Use "" or string.Empty().
Nulls are a nuisance. If you suspect either a null or an empty string in the data, especially
if it comes from an outside source, you will have to test separately for each possibility.
Result: Compiler error: "Cannot convert null to 'int' because it is a value type." [this is
an implicit conversion]. The trouble is, this error can also happen at run-time and it will
cause the program to crash, long after it was compiled.
Many string functions also panic with nulls. For example, a string.Length against a null
value gives an error, "Cannot convert null to 'int'". Although empty strings have a
(numeric) length of zero, null strings do not have any length – not even zero. In general,
nulls cause all kinds of problems and they must be treated with care.
One way to resolve these problems are to test for null prior to any calculations.
if (strText == null) or
if (String.Compare (strText, null) == 0) //(Compare returns -1,0,1)
This adequately tests for null, but it does not solve the most likely problems encountered
in a program. Typically, the code needs to concern itself with more than a null value.
Empty-strings, null-values and "blank" fields all cause problems.
Testing for empty strings is relatively easy (remember, these are not "null" strings). Test
explicitly with an empty-string (quote-quote) or by checking for a zero-length. However,
these tests explode3 if they should happen to encounter a null value.
if (strTest == "")
if (strTest < " ") //Illegal: (Use String.Compare)
if (strTest.Length == 0)4
Testing for empty strings using .Length is favored by many programmers and you will see
numerous examples on the web. But because the test explodes when it encounters a null,
it is too dangerous to use in the real world.
3
"Explode" means a nasty run-time error - one that happens after you release your final program to
production – users will call.
4
If testing a Form field, a similar test would be if(textBox1.Text.Length == 0)
if (strTest.Trim() == "")
if (strTest.TrimStart() == "")
//more reliable test for multiple spaces but
does not test for nulls or empty-strings
The Trim tests are particularly interesting because it removes any number of leading
spaces that might have been typed by end-users before comparing with an empty-string,
however, it still dies if it finds a null.
To effectively test for an "empty" field – that is a field that contains nulls, empty-strings,
or blanks – you must combine several tests. The key is to test nulls separately, keeping
the rest of the routine from abending5.
if (strTest == null)
{
//The field is "null" and must test separately before a trim
//is attempted
}
else
if (strTest.Trim() == "")
{
//The field is "empty"
}
}
This test correctly checks for nulls and spaces but involves a half-dozen lines of code.
Since most programs frequently need to test for "blank" fields (data-entry fields, database-
fields, etc.), the test is too tedious.
String.IsNullOrEmpty( ):
If this is starting to look complicated, you are right. Using the techniques above, testing
for spaced, blank, empty and null strings each requires a different test. Starting with C#
version 2005, Microsoft agreed and developed a new boolean string method for these
scenarios.
Consider this code, which uses the new "String.IsNullOrEmpty( )" function:
5
Abend = Abnormal End; also known as a run-time error, an "Exception," or more commonly as a
program crash. These types of errors cause your program to die while end-users are using the program
and these are often hard to debug.
if (String.IsNullOrEmpty(strTest))
MessageBox.Show("No data");
else
MessageBox.Show ("String has data");
}
where:
• Use the key phrase "String.IsNullOrEmpty( )" with a variable as a parameter within
the parenthesis.
The only issue I have with this statement is a space (" ") is considered an occupied data
field. You always have to remember those pesky users who pad fields with spaces in
order to clear them. For most programs, this should still act like it was an empty field,
even though it has 'valid' data within. Although this is a good first-step, the "IsBlank" and
"IsFilled", introduced below, solve this problem.
Nullable Tests:
As an aside, C# also supports a "nullable data-type," which is a value that can contain
either legitimate data or a null, and this can only be applied against numeric data types
and certain constructs, such as an array. The reason for using a nullable data-type is
because sometimes a (numeric) value is not known.
Consider what should happen when importing integer temperature values from a database.
What if the value was never set (e.g. a null). Should your program store a zero (0) – that
would not work because zero degrees is a valid temperature. If you attempted to store a
"null" to a standard integer, the program would either not compile or would crash at run-
time. Similarly, how should unknown dates, such as a birth-date, be represented when the
date-field absolutely requires a date? Storing a null would normally cause the program to
fail. This is where a "nullable data-type" is useful; it can store legitimate numbers *or* a
null.
where the question mark indicates this data-type is a "nullable" type and the variable can
hold a null value. Note: "Strings" are not nullable (with a question-mark definition), but
they can be null – but you cannot use the ?-technique.
Once defined as "nullable", null values can be tested with a property called ".HasValue".
This property will not work on a standard integer, date or string – it must be defined as
"nullable" before this property can be used.
In a contrived and simple example, testing with the .HasValue property could look like
the following code and the if-statement prevents to program from having an "Exception"
(crash). You could also test for a not-equals-null (!=null), with similar results:
if (imyNumberVariable.HasValue)
imyNumberVariable = imyNumberVariable + 100;
else
{
//You cannot use this variable
}
Null-Conditional Operator:
Consider this example, where a null-string is accidentally being passed into a substring
routine. If a null is encountered, a null is immediately returned and the remainder of the
command is not interpreted. Without a test (and without the ?.), this would normally
cause the program to abend:
MessageBox.Show
("The value is '" + strSomeString?.Substring(0,4) + "'");
}
Results: The value is ''. The Substring command is abandoned. The closing apostrophe is
appended to the MessageBox.
6
See also the Terniary ? command in Chapter 3.
This test is succinct and works well for a null-value, but the routine has other failures
which are not accounted for. If an empty-string is passed, or if the string is shorter than 4
characters, the program would still abend, but the intent of this section is to quickly
demonstrate the new 2015 operator.
Often, what is really needed, is a way to ask a question of the data, asking if the string is
null, empty or spaces - e.g. what I call "blank". This is discussed next.
From the previous section, deciding when a field is "blank" requires three different tests:
• Is it null?
• Is it an empty-string?
• Is it only "spaces"?
In order to keep the program from crashing, all three conditions must be tested, in a
specific order, and it requires a tedious nested-if or a mixture of IsNullOrEmpty and other
checks, but there are coding tricks that can simplify this task. In this section you will
build a "function" to automate this idea. The next chapter takes these new functions and
puts them into a library, where they are always available. The final code will be easy-to-
write and more importantly, easy-to-read.
Below are several methods that test for "blank" strings; some
designs are better than others. You are encouraged to try them
in order to learn more about if-statements and logic. Try the
examples in your test program using button1_Click's event.
Using the techniques described in the previous sections, the null and .Length test can be
combined into one semi-complicated statement:
This method is flawed because it doesn't test for a space-character and it relies on a oddly-
worded phrase "not-equal null (!=)" It also uses a sneaky double-ampersand, which first
tests for nulls before attempting the other tests – saving a possible abend. As a reminder,
Chapter 3 discusses the usefulness of the double-ampersand in this types of test. Both of
these "tricks" require mental gyrations to understand – but this is not a bad first step.
Adding another layer to test for "space-characters" would involve adding an "and" or "or"
clause to the command above and it would make the statement un-interpretable to most
humans. However, this idea is still worth exploring. Try this code by doing the
following:
2. Create an if-statement that checks for both null and for string.Length.
Using a trick from previous chapters, use an AND statement within the if-statement
clause.
j Recall the double-ampersand first checks for null and if that fails, it doesn't resolve
the second-half of the "if", saving you from a run-time /abend.
Run the program with strTest as a null: Results "No data" -good
Change strTest from a null to an empty-string (""): Results "No data" -good
Change strTest to a (space): Results: "String has data". This is not the result wanted.
Checking only a string's length is a common but flawed way to tell if a field is empty. It
works well on filled and empty strings, which have zero lengths, but crashes when the
string has a null value or is filled with spaces.
The null-value problem is solved using a trick: Take the string being tested and append
an empty-string; this immediately (and temporarily) converts the null to an empty string.
Once this is done, a Trim (to take care of leading spaces) and a simple string.Length,
makes a reliable "blank" test.
Appending an empty-string is the key to this test and this protects the statement from
nulls. When an empty-string ( "" ) is appended to most other strings, the original string's
value doesn't change.
j The key point is this: The null-step must be done before either the Trim or the .Length
calculation because both panic when they see a null.
In the middle of the test, "TrimStart" resolves the problem with "space" fields by
removing the "redundant spaces." Use a "TrimStart" instead of a more normal "Trim" for
a slight efficiency because there is no need to trim the back-side when you are only
looking to see if the string has a space. Words such as " " (space) or " Dog" (space-dog)
are correctly interpreted.
With the code above, change strtest and try these possibilities. All are detected as
"blank":
• "" (quote-quote)
• "bob" (Normal string)
• " bob" (leading space or spaces)
• "" (a single-space, quote-space-quote)
• " " (several spaces)
• null
All but the simplest programs have to check for "blank" data, usually in multiple
locations. But adding this best-practice-logic in so many places is cumbersome and would
soon become a chore. A better solution is to call a function that returns a true/false when
ever the function were asked, "is it blank?" Chapter 6 describes how to make this a
generic function but here is a hint on what it would look like in your program:
Starting with Chapter 6, you will be able to coin your own function names, essentially
creating your own keywords in C#. "IsBlank" and "IsFilled" are functions that C# should
have, but doesn't. These will become two verbs that you can't live without.
Often, programs need to parse through strings, looking for information. For example,
assume your program reads an input file (perhaps an INI file), where a person's name is
listed with a keyword "Name" followed by an equal-sign-delimiter:
When parsing the line, you need either a delimiter or a known horizontal position in order
to snarf the information. The delimiter (in this case, the equ