Chapter 9
Stringing in the Key of C#
In This Chapter
ᮣ
Pulling and twisting a string — but you still can’t push it
ᮣ
Parsing strings read into the program
ᮣ
Formatting output strings manually or using the
String.Format()
method
F
or many applications, you can treat a
string
like one of the built-in
value-type variable types such as
int
or
char
. Certain operations that
are otherwise reserved for these intrinsic types are available to strings, as
follows:
int i = 1; // declare and initialize an int
string s = “abc”; // declare and initialize a string
In other respects, shown as follows, a
string
is treated like a user-defined
class:
string s1 = new String();
string s2 = “abcd”;
int nLengthOfString = s2.Length;
Which is it — a value-type or a class? In fact,
String
is a class for which C#
offers special treatment because strings are so widely used in programs. For
example, the keyword
string
is synonymous with the class name
String
, as
shown in the following code:
String s1 = “abcd”; // assign a string literal to a String obj
string s2 = s1; // assign a String obj to a string variable
In this example,
s1
is declared to be an object of class
String
(spelled with
an uppercase S), while
s2
is declared as a simple
string
(spelled with a low-
ercase s). However, the two assignments demonstrate that
string
and
String
are of the same (or compatible) types.
In fact, this same property is true of the other intrinsic variable types, to a
more limited extent. Even the lowly
int
type has a corresponding class
Int32
,
double
has the class
Double
, and so on. The distinction here is that
string
and
String
really are the same thing.
15_597043 ch09.qxd 9/20/05 2:01 PM Page 187
Performing Common Operations
on a String
C# programmers perform more operations on strings than Beverly Hills plastic
surgeons do on Hollywood hopefuls. Virtually every program uses the “addi-
tion” operator that’s used on
string
s, as shown in the following example:
string sName = “Randy”;
Console.WriteLine(“His name is “ + sName); // means concatenate
The
String
class provides this special operator. However, the
String
class
also provides other, more direct methods for manipulating strings. You can
see the complete list by looking up
“String class”
in the Help Index.
The union is indivisible, and so are strings
You need to know at least one thing that you didn’t learn before the 6th grade:
You can’t change a
string
object itself after it has been created. Even though
I may speak of modifying a string, C# doesn’t have an operation that modifies
the actual
string
object. Plenty of operations appear to modify the
string
that you’re working with, but they always return the modified
string
as a
new object, instead.
For example, the operation
“His name is “ + “Randy”
changes neither of
the two strings, but generates a third string,
“His name is Randy”
. One
side effect of this behavior is that you don’t have to worry about someone
modifying a
string
“out from under you.”
Consider the following simplistic example program:
// ModifyString - the methods provided by the class String do not
// modify the object itself (s.ToUpper() does not
// modify s; rather, it returns a new string that
// has been converted)using System;
namespace ModifyString
{
class Program
{
public static void Main(string[] args)
{
// create a student object
Student s1 = new Student();
s1.sName = “Jenny”;
// now make a new object with the same name
Student s2 = new Student();
s2.sName = s1.sName;
// “changing” the name in the s1 object does not
188
Part III: Object-Based Programming
15_597043 ch09.qxd 9/20/05 2:01 PM Page 188
// change the object itself because ToUpper() returns
// a new string without modifying the original
s2.sName = s1.sName.ToUpper();
Console.WriteLine(“s1 - {0}, s2 - {1}”, s1.sName, s2.sName);
// wait for user to acknowledge the results
Console.WriteLine(“Press Enter to terminate...”);
Console.Read();
}
}
// Student - we just need a class with a string in it
class Student
{
public string sName;
}
}
The
Student
objects
s1
and
s2
are set up so that their
sName
data member
points to the same string data. The call to the
ToUpper()
method converts the
string
s1.sName
to all uppercase characters. Normally, this would be a prob-
lem because both
s1
and
s2
point to the same object. However,
ToUpper()
does not change
sName
— it creates a new, independent uppercase string.
The following output of the program is simple:
s1 - Jenny, s2 - JENNY
Press Enter to terminate...
This property of strings is called immutability (meaning, unchangeability).
The immutability of strings is also important for
string
constants. A string
such as
“this is a string”
is a form of a
string
constant, just like 1 is an
int
constant. In the same way that I reuse my shirts to reduce the size of my
wardrobe, a compiler may choose to combine all accesses to the single con-
stant
“this is a string”
. Reusing
string
constants can reduce the foot-
print of the resulting program but would be impossible if a
string
could be
modified.
Equality for all strings:
The Compare() method
Numerous operations treat a string as a single object — for example, the
Compare()
method.
Compare()
, with the following properties, compares two
strings as if they were numbers:
ߜ If the left-hand string is greater than the right string,
Compare()
returns a 1.
ߜ If the left-hand string is less than the right string, it returns a –1.
ߜ If the two strings are equal, it returns a 0.
189
Chapter 9: Stringing in the Key of C#
15_597043 ch09.qxd 9/20/05 2:01 PM Page 189
The algorithm works as follows when written in “notational C#” (that is, C#
without all the details, also known as pseudocode):
compare(string s1, string s2)
{
// loop through each character of the strings until
// a character in one string is greater than the
// corresponding character in the other string
foreach character in the shorter string
if (s1’s character > s2’s character when treated as a number)
return 1
if (s2’s character < s1’s character)
return -1
// Okay, every letter matches, but if the string s1 is longer
// then it’s greater
if s1 has more characters left
return 1
// if s2 is longer, it’s greater
if s2 has more characters left
return -1
// if every character matches and the two strings are the same
// length, then they are “equal”
return 0
}
Thus,
“abcd”
is greater than
“abbd”
, and
“abcde”
is greater than
“abcd”
.
More often than not, you don’t care whether one string is greater than the
other, but only whether the two strings are equal.
You do want to know which string is “bigger” when performing a sort.
The
Compare()
operation returns a 0 when two strings are identical. The fol-
lowing test program uses the equality feature of
Compare()
to perform a cer-
tain operation when the program encounters a particular string or strings.
BuildASentence
prompts the user to enter lines of text. Each line is concate-
nated to the previous line to build a single sentence. This program exits if the
user enters the word EXIT, exit, QUIT, or quit:
// BuildASentence - the following program constructs sentences
// by concatenating user input until the user
// enters one of the termination characters -
//
// this program shows when you need to look for
// string equality
using System;
namespace BuildASentence
{
public class Program
{
public static void Main(string[] args)
{
190
Part III: Object-Based Programming
15_597043 ch09.qxd 9/20/05 2:01 PM Page 190
Console.WriteLine(“Each line you enter will be “
+ “added to a sentence until you “
+ “enter EXIT or QUIT”);
// ask the user for input; continue concatenating
// the phrases input until the user enters exit or
// quit (start with a null sentence)
string sSentence = “”;
for(;;)
{
// get the next line
Console.WriteLine(“Enter a string “);
string sLine = Console.ReadLine();
// exit the loop if it’s a terminator
if (IsTerminateString(sLine))
{
break;
}
// otherwise, add it to the sentence
sSentence = String.Concat(sSentence, sLine);
// let the user know how she’s doing
Console.WriteLine(“\nYou’ve entered: {0}”, sSentence);
}
Console.WriteLine(“\nTotal sentence:\n{0}”, sSentence);
// wait for user to acknowledge the results
Console.WriteLine(“Press Enter to terminate...”);
Console.Read();
}
// IsTerminateString - return a true if the source
// string is equal to any of the termination strings
public static bool IsTerminateString(string source)
{
string[] sTerms = {“EXIT”, “exit”, “QUIT”, “quit” };
// compare the string entered to each of the
// legal exit commands
foreach(string sTerm in sTerms)
{
// return a true if you have a match
if (String.Compare(source, sTerm) == 0)
{
return true;
}
}
return false;
}
}
}
After prompting the user for what the program expects, the program creates
an empty initial sentence string
sSentence
. From there, the program enters
an “infinite” loop.
191
Chapter 9: Stringing in the Key of C#
15_597043 ch09.qxd 9/20/05 2:01 PM Page 191
The controls
while(true)
and
for(;;)
loop forever, or at least long enough
for some internal
break
or
return
to break you out. The two loops are equiv-
alent, and in practice, you’ll see them both. Looping is covered in Chapter 5.
BuildASentence
prompts the user to enter a line of text, which the program
reads using the
ReadLine()
method. Having read the line, the program
checks to see whether it is a terminator by using the
IsTerminateString()
function, which I wrote for the job. This function returns
true
if
sLine
is one
of the terminator phrases and
false
otherwise.
By convention, the name of a function that checks a property and returns a
true
or
false
starts with
Is
,
Has
,
Can
, or some similar word. In this case,
the name of the function
IsTerminateString()
implies the question, “Is
sLine
a terminate string?” Of course, this is a human convention only — C#
doesn’t care.
If
sLine
is not one of the terminate strings, it is concatenated to the end of
the sentence using the
String.Concat()
function. The program outputs the
immediate result just so the user can see what’s going on.
The
IsTerminateString()
method defines an array of strings
sTerms
. Each
member of this array is one of the strings you’re looking for. Any of these
strings causes the program to return a
true
, which causes the program to
quit faster than a programmer forced to write COBOL.
The program must include both
“EXIT”
and
“exit”
because
Compare()
con-
siders the two strings different by default. (The way the program is written,
these are the only two ways to spell exit. Strings such as
“Exit”
and
“eXit”
would not be recognized as terminators.)
The
IsTerminateString()
function loops through each of the strings in the
array of target strings. If
Compare()
reports a match to any of the terminate
phrases, the function returns a
true
. If the function reaches the end of the
loop without a match, the function returns a
false
.
Iterating through an array is a great way to look for one of various possible
values.
Here’s an example run of the
BuildASentence
program:
Each line you entered will be added to a
sentence until you enter EXIT or QUIT
Enter a string
Programming with
You’ve entered: Programming with
Enter a string
C# is fun
You’ve entered: Programming with C# is fun
192
Part III: Object-Based Programming
15_597043 ch09.qxd 9/20/05 2:01 PM Page 192
Enter a string
(more or less)
You’ve entered: Programming with C# is fun (more or less)
Enter a string
EXIT
Total sentence:
Programming with C# is fun (more or less)
Press Enter to terminate...
I have flagged my input in bold to make the output easier to read.
Would you like your compares
with or without case?
The
Compare()
method used within
IsTerminateString()
considers
“EXIT”
and
“exit”
to be different strings. However, the
Compare()
function
is overloaded with a second version that includes a third argument. This
argument indicates whether the comparison should ignore the letter case.
A
true
indicates “ignore.” (Chapter 7 discusses function overloading.)
The following version of
IsTerminateString()
returns a
true
whether the
string passed is uppercase, lowercase, or a combination of the two:
// IsTerminateString - return a true if the source string is equal
// to any of the termination characters
public static bool IsTerminateString(string source)
{
// indicate true if passed either exit or quit,
// irrespective of case
return (String.Compare(“exit”, source, true) == 0) ||
(String.Compare(“quit”, source, true) == 0);
}
This version of
IsTerminateString()
is simpler than the previous looping
version. The function doesn’t need to worry about case, and it can use a single
conditional expression because it now has only two options to consider.
This
IsTerminateString()
doesn’t even use an
if
statement. The
bool
expression returns the calculated value directly to the user — it gets the
if
out.
What if I want to switch case?
I almost hate to bring it up, but you can use the
switch()
control to look for
a particular string. Usually, you use the
switch()
control to compare a
193
Chapter 9: Stringing in the Key of C#
15_597043 ch09.qxd 9/20/05 2:01 PM Page 193
counting number to some set of possible values; however, the
switch()
does
work on
string
objects, as well. The following version of
IsTerminate
String()
uses the
switch()
control:
// IsTerminateString - return a true if the source
// string is equal to any of the termination strings
public static bool IsTerminateString(string source)
{
switch(source)
{
case “EXIT”:
case “exit”:
case “QUIT”:
case “quit”:
return true;
}
return false;
}
}
This approach works because you’re comparing only a limited number of
strings. The
for()
loop offers a much more flexible approach for searching
for string values. Using the case-less
Compare()
gives the program greater
flexibility in understanding the user.
Reading character input
A program can read from the keyboard one character at a time, but you have
to worry about newlines and so on. An easier approach reads a string and
then parses the characters out of the string.
Parsing characters out of a string is another topic I don’t like to mention for fear
that programmers will abuse this technique. In some cases, programmers are
too quick to jump down into the middle of a string and start pulling out what
they find there. This is particularly true of C++ programmers because that’s the
only way they could deal with strings, until the addition of a string class.
Your programs can read strings as if they were arrays of characters using
either the
foreach
control or the index operator
[]
. The following
StringToCharAccess
program demonstrates this technique:
// StringToCharAccess - access the characters in a string
// as if the string were an array
using System;
namespace StringToCharAccess
{
public class Program
{
public static void Main(string[] args)
{
194
Part III: Object-Based Programming
15_597043 ch09.qxd 9/20/05 2:01 PM Page 194
// read a string in from the keyboard
Console.WriteLine(“Input some random character string.”
+ “Make sure it’s completely random”);
string sRandom = Console.ReadLine();
// first output as a string
Console.WriteLine(“When output as a string: “ + sRandom);
Console.WriteLine();
// now output as a series of characters
Console.Write(“When output using the foreach: “);
foreach(char c in sRandom)
{
Console.Write(c);
}
Console.WriteLine(); // terminate the line
// put a blank line divider
Console.WriteLine();
// now output as a series of characters
Console.Write(“When output using the for: “);
for(int i = 0; i < sRandom.Length; i++)
{
Console.Write(sRandom[i]);
}
Console.WriteLine(); // terminate the line
// wait for user to acknowledge the results
Console.WriteLine(“Press Enter to terminate...”);
Console.Read();
}
}
}
This program first outputs some string picked totally at random. In fact, I
may have read it somewhere or done a tap dance on the keyboard. The pro-
gram first outputs the string using the conventional
WriteLine(string)
method. It follows this by using the
foreach
to fetch each character in the
string, one at a time. Finally, it uses a
for
loop with the array brackets
[]
to
do the same thing. The results are as follows:
Input some random character string. Make sure it’s completely random
Stephen Davis is one handsome individual
When output as a string: Stephen Davis is one handsome individual
When output using the foreach: Stephen Davis is one handsome individual
When output using the for: Stephen Davis is one handsome individual
Press Enter to terminate...
In some cases, you don’t want to mess with any white space on either end of
the string. The term white space refers to the characters that don’t normally
display on the screen, for example, space, newline (or \n),and tab (\t).
195
Chapter 9: Stringing in the Key of C#
15_597043 ch09.qxd 9/20/05 2:01 PM Page 195