C# Classes: Lesson 7 Serves as an Introduction

This lesson introduces you to C# Classes. We have included all the necessary aspects to provide an introduction to the topic. Our objectives with C# class are as follows:

  • Implement Constructors.
  • Know the difference between instance and static members.
  • Understand Destructors.
  • Familiarization with Class Members.

Since the beginning of this tutorial, you have been using classes. By now, you should have a sense of what a class is for and how to specify one. This lesson will build upon what you already know and introduce the various class members.

Classes are declared by using the keyword class, followed by the class name and a set of class members surrounded by curly braces. Every class has a constructor, called automatically at any time an instance of a class is created. The purpose of constructors is to initialize class members when an instance of the class is created. Constructors do not have return values and always have the same name as the class. Listing 7-1 is an example of a class.

Listing 7-1. Example C# Classes: Classes.cs
// Namespace Declaration
using System;

// helper class
class OutputClass 
{
    string myString;

    // Constructor
    public OutputClass(string inputString) 
    {
        myString = inputString;
    }

    // Instance Method
    public void printString() 
    {
        Console.WriteLine("{0}", myString);
    }

    // Destructor
    ~OutputClass() 
    {
        // Some resource cleanup routines
    }
}

// Program start class
class ExampleClass 
{
    // Main begins program execution.
    public static void Main() 
    {
        // Instance of OutputClass
        OutputClass outCl = new OutputClass("This is printed by the output class.");

        // Call Output class' method
        outCl.printString(); 
    }
}
<%--
 

Get Setup Instructions For How to Run this Program

--%>

Listing 7-1 shows two classes. The top class, OutputClass, has a constructor, instance method, and a destructor. It also has a field named myString. Notice how the OutputClass constructor is used to initialize data members of the class. In this case, the OutputClass constructor accepts a string argument, inputString. This string is copied to the class field myString.

Constructors are not mandatory, as indicated by the implementation of ExampleClass. In this case, a default constructor is provided. A default constructor is simply a constructor with no arguments. However, a constructor with no arguments is not always useful. To make default constructors more useful, you can implement them with initializers. Here is an example:

    public OutputClass() : this("Default Constructor String") { }

Imagine this constructor included in class OutputClass from Listing 7-1. An initializer follows this default constructor. The colon, “:”, marks the beginning of the initializer, followed by the this keyword. The this keyword refers to this particular object. It effectively makes a call to the constructor of the same object defined in. After the this keyword is a parameter list with a string.

The action taken by the initializer above is to invoke the OutputClass constructor that takes a string type as an argument. The initializer helps you to ensure your class fields are initialized when a class is instantiated.

The example above illustrates how a class can have multiple constructors. The specific constructor called depends on the number of parameters and the type of each parameter.

Furthermore

In C#, there are two types of class members, instance and static. Instance class members belong to a specific occurrence of a class. Every time you declare an object of a certain class, you create a new instance of that class. The ExampleClass Main() method creates an instance of the OutputClass named outCl.

You can create multiple instances of OutputClass with different names. Each of these instances is separate and stand alone. For example, if you create two OutputClass instances as follows:

    OutputClass oc1 = new OutputClass("OutputClass1");
    OutputClass oc2 = new OutputClass("OutputClass2");

You create two separate instances of OutputClass with separate myString fields and separate printString() methods. On the other hand, if aclass member is static, you can access it simply by using the syntax <classname>.<static class member>. The instance names are oc1 andoc2.

Suppose OutputClass had the following static method:

    public static void staticPrinter() 
    {
        Console.WriteLine("There is only one of me.");
    }

Then you could call that function from Main() like this:

    OutputClass.staticPrinter();

You must call static class members through their class name and not their instance name. That means that you don’t need to instantiate a class to use its static members. There is only ever one copy of a static class member.

Good use of static members is when there is a function performed, and no intermediate state is required, such as math calculations. Matter of fact, the .NET Frameworks Base Class Library includes a Math class that makes extensive use of static members.

Another type of constructor is the static constructor. Use the static constructor to initialize static fields in a class. You declare a static constructor by using the keyword static just in front of the constructor name. A static constructor is called before an instance of a class is created, before a static member is called, and before the static constructor of a derived class (covered in a later chapter). They are called only once.

Furthermore

OutputClass also has a destructor. Destructors look just like constructors, except they have a tilde, “~”, in front of them. They don’t take any parameters and do not return a value. Destructors are places where you could put the code to release any resources your class held during its lifetime. They are normally called when the C# garbage collector decides to clean your object from memory.

Note: You’ve probably noticed the use of the public modifier (an access modifier), meaning that a class member can be accessed from other classes. When used on a class, it means that the class can be accessed by DLLs outside of the Assembly (which is commonly a *.exe or *.dll file). Lesson 19: Encapsulation discusses access modifiers in more depth.

So far, the only class members you’ve seen are Fields, Methods, Constructors, and Destructors. Here is a complete list of the types of members you can have in your classes:

  • Constructors
  • Destructors
  • Fields
  • Methods
  • Properties
  • Indexers
  • Delegates
  • Events
  • Nested Classes

Those items not covered in this lesson will be covered in later lessons for C# Classes.

Final Thoughts About C# Class

In summary, you can declare the instance and static constructors. You know how to initialize class fields. When there is no need to instantiate an object, you can create static class members. You can also declare destructors for cleaning up resources.

A modifier of class is internal by default but it can be public. One should use a class keyword to declare the type class. The identifier must begin with a capitalized letter. A colon precedes the name of the class’ parent. Note that is optional. A colon precedes a comma-separated list of interfaces implemented by the class. Curly braces surround the class body.

I invite you to return for Lesson 8: Class Inheritance.

C# Data Types: Operators and Variables in Lesson 2

This lesson introduces C# data types, operators and variables. Its goal is to meet the following objectives:

  • Understand what a variable is.
  • Familiarization with C# built-in types.
  • Get an introduction to C# operators.
  • Learn how to use Arrays.

Variables and Types

“Variables” are simply storage locations for data. You can place data into them and retrieve their contents as part of a C# expression. The interpretation of the data in a variable is controlled through “Types”.

C# is a “Strongly Typed” language. Thus all operations on variables are performed with consideration of what the variable’s “Type” is. There are rules that define what operations are legal to maintain the integrity of the data you put in a variable.

The C# simple types consist of the Boolean type and three numeric types – Integrals, Floating Point, Decimal, and String. The term “Integrals”, which is defined in the C# Programming Language Specification, refers to the classification of types that include sbyte, byte, short, ushort, int, uint, long, ulong, and char. More details are available in the Integral Types section later in this lesson. The term “Floating Point” refers to the float and double types, which are discussed, along with the decimal type, in more detail in the Floating Point and Decimal Types section. The string type represents a string of characters and is discussed in The String Type section, later in this lesson. The next section introduces the boolean type.

The Boolean Type

Boolean types are declared using the keyword, bool. They have two values: true or false. In other languages, such as C and C++, boolean conditions can be satisfied where 0 means false and anything else means true. However, in C# the only values that satisfy a boolean condition is true and false, which are official keywords. Listing 2-1 shows one of the many ways that boolean types can be used in a program.

Listing 2-1. Displaying Boolean Values: Boolean.cs
using System;

class Booleans
{
    public static void Main()
    {
        bool content = true;
        bool noContent = false;

        Console.WriteLine("It is {0} that C# Station provides C# programming language content.", content);
        Console.WriteLine("The statement above is not {0}.", noContent);
    }
}

In Listing 2-1, the boolean values are written to the console as part of a sentence. The only legal values for the bool type are either true or false, as shown by the assignment of true to content and false to noContent. When run, this program produces the following output:

It is True that C# Station provides C# programming language content.
The statement above is not False.

Integral Types

In C#, an integral is a category of types. For anyone confused because the word Integral sounds like a mathematical term, from the perspective of C# programming, these are defined as Integral types in the C# programming language specification. They are whole numbers, either signed or unsigned, and the char type. The char type is a Unicode character, as defined by the Unicode Standard. Table 2-1 shows the integral types, their size, and range.

Table 2-1. The Size and Range of C# Integral Types
Type Size (in bits) Range
sbyte 8 -128 to 127
byte 8 0 to 255
short 16 -32768 to 32767
ushort 16 0 to 65535
int 32 -2147483648 to 2147483647
uint 32 0 to 4294967295
long 64 -9223372036854775808 to 9223372036854775807
ulong 64 0 to 18446744073709551615
char 16 0 to 65535

Integral types are well suited for those operations involving whole number calculations. The char type is the exception, representing a single Unicode character. As you can see from the table above, you have a wide range of options to choose from, depending on your requirements.

Floating Point and Decimal Types

A C# floating point type is either a float or double. They are used any time you need to represent a real number, as defined by IEEE 754. For more information on IEEE 754, visit the IEEE Web Site. Decimal types should be used when representing financial or money values. Table 2-2 shows the floating point and decimal types, their size, precision, and range.

Table 2-2. The Floating Point and Decimal Types with Size, precision, and Range
Type Size (in bits) precision Range
float 32 7 digits 1.5 x 10-45 to 3.4 x 1038
double 64 15-16 digits 5.0 x 10-324 to 1.7 x 10308
decimal 128 28-29 decimal places 1.0 x 10-28 to 7.9 x 1028

Floating point types are used when you need to perform operations requiring fractional representations. However, for financial calculations, the decimal type is the best choice because you can avoid rounding errors.

The String Type

A string is a sequence of text characters. You typically create a string with a string literal, enclosed in quotes: “This is an example of a string.” You’ve seen strings being used in Lesson 1, where we used the Console.WriteLine method to send output to the console.

Some characters aren’t printable, but you still need to use them in strings. Therefore, C# has a special syntax where characters can be escaped to represent non-printable characters. For example, it is common to use newlines in the text, which is represented by the ‘\n’ char. The backslash, ‘\’, represents the escape. When preceded by the escape character, the ‘n’ is no longer interpreted as an alphabetical character but now represents a newline.

You may be wondering how you could represent a backslash character in your code. We have to escape that too by typing two backslashes, as in ‘\\’. Table 2-3 shows a list of common escape sequences.

Table 2-3. C# Character Escape Sequences
Escape Sequence Meaning
\’ Single Quote
\” Double Quote
\\ Backslash
\0 Null, not the same as the C# null value
\a Bell
\b Backspace
\f form Feed
\n Newline
\r Carriage Return
\t Horizontal Tab
\v Vertical Tab

Another useful feature of C# strings is the verbatim literal, which is a string with a @ symbol prefix, as in @”Some string”. Verbatim literals make escape sequences translate as normal characters to enhance readability. To appreciate the value of verbatim literals, consider a path statement such as “c:\\topdir\\subdir\\subdir\\myapp.exe”. As you can see, the backslashes are escaped, causing the string to be less readable. You can improve the string with a verbatim literal, like this: @”c:\topdir\subdir\subdir\myapp.exe”.

That is fine, but you have the problem where quoting text is not as easy. In that case, you would specify double quotes. For example, the string “copy \”c:\\source file name with spaces.txt\” c:\\newfilename.txt” would be written as the verbatim literal @”copy “”c:\source file name with spaces.txt”” c:\newfilename.txt”.

C# Operators

Results are computed by building expressions. These expressions are built by combining variables and operators together into statements. The following table describes the allowable operators, their precedence, and associativity.

Table 2-4. Operators with their precedence and Associativity
Category (by precedence) Operator(s) Associativity
Primary x.y  f(x)  a[x]  x++  x–  new  typeof  default  checked  unchecked delegate left
Unary +  –  !  ~  ++x  –x  (T)x right
Multiplicative *  /  % left
Additive +  – left
Shift <<  >> left
Relational <  >  <=  >=  is as left
Equality ==  != right
Logical AND & left
Logical XOR ^ left
Logical OR | left
Conditional AND && left
Conditional OR || left
Null Coalescing ?? left
Ternary ?: right
Assignment =  *=  /=  %=  +=  -=  <<=  >>=  &=  ^=  |=  => right

Left associativity means that operations are evaluated from left to right. Right associativity means all operations occur from right to left, such as assignment operators where everything to the right is evaluated before the result is placed into the variable on the left.

Most operators are either unary or binary. Unary operators form expressions on a single variable, but binary operators form expressions with two variables. Listing 2-2 demonstrates how unary operators are used.

Listing 2-2. Unary Operators: Unary.cs
using System;

class Unary
{
    public static void Main()
    {
        int unary = 0;
        int preIncrement;
        int preDecrement;
        int postIncrement;
        int postDecrement;
        int positive;
        int negative;
        sbyte bitNot;
        bool logNot;

        preIncrement = ++unary;
        Console.WriteLine("pre-Increment: {0}", preIncrement);

        preDecrement = --unary;
        Console.WriteLine("pre-Decrement: {0}", preDecrement);

        postDecrement = unary--;
        Console.WriteLine("Post-Decrement: {0}", postDecrement);

        postIncrement = unary++;
        Console.WriteLine("Post-Increment: {0}", postIncrement);

        Console.WriteLine("Final Value of Unary: {0}", unary);

        positive = -postIncrement;
        Console.WriteLine("Positive: {0}", positive);

        negative = +postIncrement;
        Console.WriteLine("Negative: {0}", negative);

        bitNot = 0;
        bitNot = (sbyte)(~bitNot);
        Console.WriteLine("Bitwise Not: {0}", bitNot);

        logNot = false;
        logNot = !logNot;
        Console.WriteLine("Logical Not: {0}", logNot);
    }
}

When evaluating expressions, post-increment (x++) and post-decrement (x–) operators return their current value and then apply the operators. However, when using pre-increment (++x) and pre-decrement (–x) operators, the operator is applied to the variable prior to returning the final value.

In Listing 2-2, the unary variable is initialized to zero. When the pre-increment (++x) operator is used, unary is incremented to 1 and the value 1 is assigned to the preIncrement variable. The pre-decrement (–x) operator turns unary back to a 0 and then assigns the value to the pre Decrement variable.

When the post-decrement (x–) operator is used, the value of unary, 0, is placed into the post Decrement variable and then unary is decremented to -1. Next, the post-increment (x++) operator moves the current value of unary, -1, to the post Increment variable and then increments unary to 0.

The variable bitNot is initialized to 0 and the bitwise not (~) operator is applied. The bitwise not (~) operator flips the bits in the variable. In this case, the binary representation of 0, “00000000”, was transformed into -1, “11111111”.

While the (~) operator works by flipping bits, the logical negation operator (!) is a logical operator that works on bool values, changing true to false or false to true. In the case of the logNot variable in Listing 2-2, the value is initialized to false, and the next line applies the logical negation operator, (!), which returns true and reassigns the new value, true, to logNot. Essentially, it is toggling the value of the bool variable, logNot.

Furthermore

The setting of positive is a little tricky. At the time that it is set, the postIncrement variable is equal to -1. Applying the minus (-) operator to a negative number results in a positive number, meaning that positive will equal 1, instead of -1. The minus operator (-), which is not the same as the pre-decrement operator (–), doesn’t change the value of postInc – it applies a sign negation. The plus operator (+)doesn’t affect the value of a number, assigning negative with the same value as postIncrement, -1.

Notice the expression (sbyte)(~bitNot). Any operation performed on types sbyte, byte, short, or ushort return int values. To assign the result into the bitNot variable we had to use a cast, (Type), operator, where Type is the type you wish to convert to (in this case – sbyte). The cast operator is shown as the Unary operator, (T)x, in table 2-4. Cast operators must be performed explicitly when you go from a larger type to a smaller type because of the potential for lost data. Generally speaking, assigning a smaller type to a larger type is no problem since the larger type has room to hold the entire value. Also, be aware of the dangers of casting between signed and unsigned types. You want to be sure to preserve the integrity of your data. Many basic programming texts contain good descriptions of bit representations of variables and the dangers of explicit casting.

Here’s the output from the Listing 2-2:

pre-Increment: 1
pre-Decrement 0
Post-Decrement: 0
Post-Increment: -1
Final Value of Unary: 0
Positive: 1
Negative: -1
Bitwise Not: -1
Logical Not: true

In addition to unary operators, C# has binary operators that form expressions of two variables. Listing 2-3 shows how to use the binary operators.

Listing 2-3. Binary Operators: Binary.cs
using System;

class Binary
{
    public static void Main()
    {
        int x, y, result;
        float floatresult;

        x = 7;
        y = 5;

        result = x+y;
        Console.WriteLine("x+y: {0}", result);

        result = x-y;
        Console.WriteLine("x-y: {0}", result);

        result = x*y;
        Console.WriteLine("x*y: {0}", result);

        result = x/y;
        Console.WriteLine("x/y: {0}", result);

        floatresult = (float)x/(float)y;
        Console.WriteLine("x/y: {0}", floatresult);

        result = x%y;
        Console.WriteLine("x%y: {0}", result);

        result += x;
        Console.WriteLine("result+=x: {0}", result);
    }
}

And here’s the output:

x+y: 12
x-y: 2
x*y: 35
x/y: 1
x/y: 1.4
x%y: 2
result+=x: 9

Listing 2-3 shows several examples of binary operators. As you might expect, the results of addition (+), subtraction (-), multiplication (*), and division (/) produce the expected mathematical results.

The floatresult variable is a floating point type. We explicitly cast the integer variables x and y to calculate a floating point value.

There is also an example of the remainder(%) operator. It performs a division operation on two values and returns the remainder.

The last statement shows another form of the assignment with the operation (+=) operator. Any time you use the assignment with operation operator, it is the same as applying the binary operator to the left hand and right-hand sides of the operator and putting the results into the left-hand side. The example could have been written as result = result + x; and returned the same value.

The Array Type

Another data type is the Array, which can be thought of as a container that has a list of storage locations for a specified type. When declaring an Array, specify the type, name, dimensions, and size.

Listing 2-4. Array Operations: Array.cs
using System;

class Array
{
    public static void Main()
    {
        int[] myInts = { 5, 10, 15 };
        bool[][] myBools = new bool[2][];
        myBools[0] = new bool[2];
        myBools[1] = new bool[1];
        double[,] myDoubles = new double[2, 2];
        string[] myStrings = new string[3];

        Console.WriteLine("myInts[0]: {0}, myInts[1]: {1}, myInts[2]: {2}", myInts[0], myInts[1], myInts[2]);

        myBools[0][0] = true;
        myBools[0][1] = false;
        myBools[1][0] = true;
        Console.WriteLine("myBools[0][0]: {0}, myBools[1][0]: {1}", myBools[0][0], myBools[1][0]);

        myDoubles[0, 0] = 3.147;
        myDoubles[0, 1] = 7.157;
        myDoubles[1, 1] = 2.117;
        myDoubles[1, 0] = 56.00138917;
        Console.WriteLine("myDoubles[0, 0]: {0}, myDoubles[1, 0]: {1}", myDoubles[0, 0], myDoubles[1, 0]);

        myStrings[0] = "Joe";
        myStrings[1] = "Matt";
        myStrings[2] = "Robert";
        Console.WriteLine("myStrings[0]: {0}, myStrings[1]: {1}, myStrings[2]: {2}", myStrings[0], myStrings[1], myStrings[2]);

    }
}

And here’s the output:

myInts[0]: 5, myInts[1]: 10, myInts[2]: 15
myBools[0][0]: true, myBools[1][0]: true
myDoubles[0, 0]: 3.147, myDoubles[1, 0]: 56.00138917
myStrings[0]: Joe, myStrings[1]: Matt, myStrings[2]: Robert

Listing 2-4 shows different implementations of Arrays. The first example is the myInts Array, which is a single-dimension array. It is initialized at declaration time with explicit values.

Next is a jagged array, myBools. It is essentially an array of arrays. We needed to use the new operator to instantiate the size of the primary array and then use the new operator again for each sub-array.

The third example is a two-dimensional array, myDoubles. Arrays can be multi-dimensional, with each dimension separated by a comma. It must also be instantiated with the new operator.

Furthermore

One of the differences between jagged arrays, myBools[][], and multi-dimension arrays, myDoubles[,], is that a multi-dimension array will allocate memory for every element of each dimension. A jagged array will only allocate memory for the size of each array in each dimension that you define. Most of the time, you’ll be using multi-dimension arrays, if you need multiple dimensions. You will only use jagged arrays in very special circumstances when you can save significant memory by explicitly specifying the sizes of the arrays in each dimension.

Finally, we have the single-dimensional array of string types, myStrings.

In each case, you can see those array elements are accessed by identifying the integer index for the item you wish to refer to. Array sizes can be an int type value. Their indexes begin at 0.

To Wrap Up C# Data Types: Operators and Variables

A variable is an identifier with a type that holds a value of that type. Simple types include the integrals, floating points, decimal, and bool. C# has several mathematical and logical operators that participate in forming expressions. C# also offers the single dimension, multi-dimension and jagged array types.

Integer data type enables a variable to store numeric values. Character data type enables a variable to store only one character. Floating point data type consists of a float and a double. A float enables a variable to store decimal values.  Double data type permits up to 10 digits after the decimal. 

In this lesson, you learned how to write simple statements and code a program that works linearly from start to finish. However, this is not as useful as it can be because you need to be able to make decisions and execute different blocks of code depending on different conditions. I invite you to return for C# If Statement: Control Selection in Lesson 3, where you can learn how to branch your logic for more powerful decision making.

Follow Joe Mayo on Twitter.

We hope that this lesson on C# Data Types was informative. Please email us with your feedback.

C# If Statement: Control Selection in Lesson 03

In the last couple of lessons, every program you saw contained a limited amount of sequential steps and then stopped. There were no decisions you could make with the input, and the only constraint was to follow straight through to the end. The information in this lesson will help you branch into separate logical sequences based on the decisions you make. More specifically, the goals of learning the C# if statement are as follows:

  • Learn the if statements.
  • Learn the switch statement.
  • Learn how break is used in switch statements.
  • Understand proper use of the goto statement.

The if Statement

An if statement allows you to take different paths of logic, depending on a given condition. When the condition evaluates to a boolean true, a block of code for that true condition will execute. You have the option of a single if statement, multiple else if statements and an optional else statement. Listing 3-1 shows how each of these types of if statements work.

Listing 3-1. forms of the if statement: IfSelection.cs
using System;

class IfSelect
{
    public static void Main()
    {
        string myInput;
        int myInt;

        Console.Write("Please enter a number: ");
        myInput = Console.ReadLine();
        myInt = Int32.Parse(myInput);
        // Single Decision and Action with braces
        if (myInt > 0)
        {
            Console.WriteLine("Your number {0} is greater than zero.", myInt);
        }
        // Single Decision and Action without brackets
        if (myInt < 0) 
            Console.WriteLine("Your number {0} is less than zero.", myInt);
        // Either/Or Decision
        if (myInt != 0)
        {
            Console.WriteLine("Your number {0} is not equal to zero.", myInt);
        }
        else

       {
            Console.WriteLine("Your number {0} is equal to zero.", myInt);
        }
        // Multiple Case Decision
        if (myInt < 0 || myInt == 0)
        {
            Console.WriteLine("Your number {0} is less than or equal to zero.", myInt);
        }
        else if (myInt > 0 && myInt <= 10)
        {
            Console.WriteLine("Your number {0} is in the range from 1 to 10.", myInt);
        }
        else if (myInt > 10 && myInt <= 20)
        {
            Console.WriteLine("Your number {0} is in the range from 11 to 20.", myInt);
        }
        else if (myInt > 20 && myInt <= 30)
        {
            Console.WriteLine("Your number {0} is in the range from 21 to 30.", myInt);
        }
        else

       {
            Console.WriteLine("Your number {0} is greater than 30.", myInt);
        }
    }
}

The statements in Listing 3-1 use the same input variable, myInt as a part of their evaluations. This is another way of obtaining interactive input from the user. Here’s the pertinent code:

        Console.Write("Please enter a number: ");
        myInput = Console.ReadLine();
        myInt = Int32.Parse(myInput);

We first print the line “Please enter a number: ” to the console. The Console.ReadLine() statement causes the program to wait for input from the user, who types a number and then presses Enter. This number is returned in the form of a string into the myInput variable, which is a string type. Since we must evaluate the user’s input in the form of an int, myInput must be converted. This is done with the command Int32.Parse(myInput). (Int32 and similar types will be covered in another lesson on advanced types). The result is placed into themyInt variable, which is an int type.

Now that we have a variable in the type we wanted, we will evaluate it with if statements. The first statement is of the form if (boolean expression) { statements }, as shown below:

        // Single Decision and Action with braces
        if (myInt > 0)
        {
            Console.WriteLine("Your number {0} is greater than zero.", myInt);
        }

You must begin with the keyword if. Next is the boolean expression between parenthesis. This boolean expression must evaluate to a true or false value. In this case, we are checking the user’s input to see if it is greater than (>) 0. If this expression evaluates to true, we execute the statements within the curly braces (we refer to the structure with curly braces as a “block”). There could be one or more statements within this block. If the boolean expression evaluates to false, we ignore the statements inside the block and continue program execution with the next statement after the block.

Note: In other languages, such as C and C++, conditions can be evaluated where a result of 0 is false, and any other number is true. In C#, the condition must evaluate to a boolean value of either true or false. If you need to simulate a numeric condition with C#, you can do so by writing it as (myInt != 0), which means that the expression evaluates to true if myInt is not 0.

The second if statement is much like the first, except it does not have a block, as shown here:

        // Single Decision and Action without braces
        if (myInt < 0) 
            Console.WriteLine("Your number {0} is less than zero.", myInt);

If its boolean expression evaluates to true, the first statement after the boolean expression will be executed. When the boolean expression evaluates to false, the first statement after the boolean expression will be skipped, and the next program statement will be executed. This form of if statement is adequate when you only have a single statement to execute. If you want to execute two or more statements when the boolean expression evaluates to true, you must enclose them in a block.

Most of the time, you’ll want to make an either/or kind of decision. This is called an if/else statement. The third if statement in Listing 3-1 presents this idea, as shown below:

        // Either/Or Decision
        if (myInt != 0)
        {
            Console.WriteLine("Your number {0} is not equal to zero.", myInt);
        }
        else
         {
            Console.WriteLine("Your number {0} is equal to zero.", myInt);
        }

When the boolean expression evaluates to true, the statement(s) in the block immediately following the if statement is executed. However, when the boolean expression evaluates to false, the statements in the block following the else keyword are executed.

When you have multiple expressions to evaluate, you can use the if/else if/else form of the if statement. We show this form in the fourth if the statement of Listing 3-1, and repeated below:

        // Multiple Case Decision
        if (myInt < 0 || myInt == 0)
        {
            Console.WriteLine("Your number {0} is less than or equal to zero.", myInt);
        }
        else if (myInt > 0 && myInt <= 10)
        {
            Console.WriteLine("Your number {0} is in the range from 1 to 10.", myInt);
        }
        else if (myInt > 10 && myInt <= 20)
        {
            Console.WriteLine("Your number {0} is in the range from 11 to 20.", myInt);
        }
        else if (myInt > 20 && myInt <= 30)
        {
            Console.WriteLine("Your number {0} is in the range from 21 to 30.", myInt);
        }
        else
         {
            Console.WriteLine("Your number {0} is greater than 30.", myInt);
        }

This example begins with the if keyword, again executing the following block if the boolean expression evaluates to true. However, this time you can evaluate multiple subsequent conditions with the else if keyword combination. The else if statement also takes a boolean expression like the if statement. The rules are the same when the boolean expression for the else if statement evaluates to true, then the block immediately following the boolean expression is executed. When none of the other if or else if boolean expressions evaluate to true, the block following the else keyword will be executed. Only one section of an if/else if/else statement will be executed.

One difference in the last statement from the others is the boolean expressions. The boolean expression, (myInt < 0 || myInt == 0), contains the conditional OR (||) operator. In both the regular OR (|) operator and the conditional OR (||) operator, the boolean expression will evaluate to true if either of the two sub-expressions on either side of the operator evaluates to true. The primary difference between the two OR forms is that the regular OR operator will evaluate both sub-expressions every time. However, the conditional OR will evaluate the second sub-expression only if the first sub-expression evaluates to false.

Furthermore

The boolean expression, (myInt > 0 && myInt <= 10), contains the conditional AND operator.  Both the regular AND (&) operator and the conditional AND (&&) operator will return true when both of the sub-expressions on either side of the operator evaluate to true.  The difference between the two is that the regular AND operator will evaluate both expressions every time. However, the conditional AND operator will evaluate the second sub-expression only when the first sub-expression evaluates to true.

The conditional operators (&& and ||) are commonly called short-circuit operators because they do not always evaluate the entire expression. Thus, they are also used to produce more efficient code by ignoring unnecessary logic.

The switch Statement

Another form of selection statement is the switch statement, which executes a set of logic depending on the value of a given parameter. The types of the values a switch statement operates on can be booleans, enums, integral types, and strings. Lesson 2: Operators, Types, and Variables discuss the bool type, integral types and strings and Lesson 17: Enums will teach you what an enum type is. Listing 3-2 shows how to use the switch statement with both int and string types.

Listing 3-2. Switch Statements: SwitchSelection.cs
using System;

class SwitchSelect
{
    public static void Main()
    {
        string myInput;
        int myInt;

        begin:

        Console.Write("Please enter a number between 1 and 3: ");
        myInput = Console.ReadLine();
        myInt = Int32.Parse(myInput);

        // switch with integer type
        switch (myInt)
        {
            case 1:
                Console.WriteLine("Your number is {0}.", myInt);
                break;
            case 2:
                Console.WriteLine("Your number is {0}.", myInt);
                break;
            case 3:
                Console.WriteLine("Your number is {0}.", myInt);
                break;
            default:
                Console.WriteLine("Your number {0} is not between 1 and 3.", myInt);
                break;
        }

        decide:

        Console.Write("Type \"continue\" to go on or \"quit\" to stop: ");
        myInput = Console.ReadLine();

        // switch with string type
        switch (myInput)
        {
            case "continue":
                goto begin;
            case "quit":
                Console.WriteLine("Bye.");
                break;
            default:
                Console.WriteLine("Your input {0} is incorrect.", myInput);
                goto decide;
        }
    }
}

Note: Listing 3-2 will throw an exception if you enter any value other than an int. i.e. the letter ‘a’ would be an error. You can visit Lesson 15: Introduction to Exception Handling to learn more about how to anticipate and handle these type of problems.

Listing 3-2 shows a couple of switch statements. The switch statement begins with the switch keyword, followed by the switch expression. In the first switch statement in listing 3-2, the switch expression evaluates to an int type, as follows:

        // switch with integer type
        switch (myInt)
        {
            case 1:
                Console.WriteLine("Your number is {0}.", myInt);
                break;
            case 2:
                Console.WriteLine("Your number is {0}.", myInt);
                break;
            case 3:
                Console.WriteLine("Your number is {0}.", myInt);
                break;
            default:
                Console.WriteLine("Your number {0} is not between 1 and 3.", myInt);
                break;
        }

The switch block follows the switch expression, where one or more choices are evaluated for a possible match with the switch expression. Each choice is labeled with the case keyword, followed by an example that is of the same type as the switch expression and followed by a colon (:). In the example we have case 1:, case 2:, and case 3:. When the result evaluated in the switch expression matches one of those choices, the statements immediately following the matching choice are executed, up to and including a branching statement, which could be a break, continue, goto, return, or throw statement. Table 3-1 summarizes the branching statements.

Table 3-1. C# Branching Statements
Branching statement Description
break Leaves the switch block
continue Leaves the switch block, skips remaining logic in enclosing loop and goes back to the loop condition to determine if the loop should be executed again from the beginning. Works only if the switch statement is in a loop as described in Lesson 04: Control Statements – Loops.
goto Leaves the switch block and jumps directly to a label of the form “<labelname>:”
return Leaves the current method. Methods are described in more detail in Lesson 05: Methods.
throw Throws an exception, as discussed in Lesson 15: Introduction to Exception Handling.

You may also include a default choice following all other choices. If none of the other choices match, then the default choice is taken and its statements are executed. Although the use of the default label is optional, I highly recommend that you always include it. This will help catch unforeseen circumstances and make your programs more reliable.

Each case label must end with a branching statement, as described in table 3-1, which is normally the break statement. The break statement will cause the program to exit the switch statement and begin execution with the next statement after the switch block. There are two exceptions to this: adjacent case statements with no code in between or using a goto statement. Here’s an example that shows how to combine case statements:

        switch (myInt)
        {
            case 1:
            case 2:
            case 3:
                Console.WriteLine("Your number is {0}.", myInt);
                break;
            default:
                Console.WriteLine("Your number {0} is not between 1 and 3.", myInt);
                break;
        }

By placing case statements together, with no code in-between, you create a single case for multiple values. A case without any code will automatically fall through to the next case. The example above shows how the three cases for myInt equal to 1, 2, or 3, where case 1 and case 2 will fall through and execute code for case 3.

A case statement can only be an exact match, and you can’t use logical conditions. If you need to use logical conditions, you can use an if/else if/else statement.

Another way to control the flow of logic in a switch statement is by using the goto statement. You can either jump to another case statement or jump out of the switch statement. The second switch statement in Listing 3-2 shows the use of the goto statement, as shown below:

        // switch with string type
        switch (myInput)
        {
            case "continue":
                goto begin;
            case "quit":
                Console.WriteLine("Bye.");
                break;
            default:
                Console.WriteLine("Your input {0} is incorrect.", myInput);
                goto decide;
        }

Note: in the current example, “continue”, is a case of the switch statement — not the keyword.

The goto statement causes program execution to jump to the label following the goto keyword. During execution, if the user types “continue”, the switch statement matches this input (a string type) with the case “continue”: label and executes the “goto begin:”instruction. The program will then leave the switch statement and start executing the first program statement following the begin: label. This is effectively a loop, allowing you to execute the same code multiple times. The loop will end when the user types the string “quit”. This will be evaluated with the case “quit”: choice, which will print “Bye.” to the console, then break out of the switch statement and end the program.

Warning: You should not create loops like this. It is *bad* programming style. The only reason it is here is that I wanted to show you the syntax of the goto statement. Instead, use one of the structured looping statements, described in Lesson 04: Control Statements – Loops.

Furthermore

When neither the “continue” nor “quit” strings are entered, the “default:” case will be entered. It will print an error message to the console and then execute the goto decide: command. This will cause program execution to jump to the first statement following the decide: label, which will ask the user if they want to continue or quit. This is effectively another loop.

Clearly, the goto statement is powerful and can, under controlled circumstances, be useful. However, I must caution you strongly on its use. The goto statement has great potential for misuse. You could possibly create a very difficult program to debug and maintain. Imagine the spaghetti code that could be created by random goto statements throughout a program. In the next lesson, I’ll show you a better way to create loops in your program.

Final Thoughts About C# If Statement

The if statement can be written in multiple ways to implement different branches of logic. The switch statement allows a choice among a set of bool, enum, integral, or string types. You use break, continue, goto, return, or throw statements to leave a case statement. Be sure to avoid the goto statement in your code unless you have an extremely good reason for using it.

In addition to branching based on a condition, it is useful to be able to execute a block of statements multiple times. A goto statement is not proper or adequate for such logic. Therefore, I invite you to return for Lesson 4: Control Statements – Loops. This will be a continuation of the same topic.

Follow Joe Mayo on Twitter.

Please email us with question or comments about the C# if statement.

Introduction to Strongly-Typed DataSet

Introduction

This article teaches how to create a strongly-typed dataset class library in your C# database applications. Our objectives are as follows:

  • Learn what a strongly-Typed DataSet is
  • Let Visual Studio Create an ST Data Set
  • (Semi)Manually create an ST DataSet using XSD (XML Schema Definition)

For this article, you will need to use Visual Studio .NET and a relational database. SQL Server is required for the automatic generation of ST DataSets, and I will use Sybase ASE for the manual ST DataSet. Sybase ASE is a cross-platform database server with Java-based administration tools similar to MS SQL Server tools. Sybase ASE is available in a free developer version at www.sybase.com. (Tip: If you install Sybase ASE, be sure to install Jisql which is under the “Free Utilities” option. This utility lets you type in SQL statements to execute.)

strong-Typing

C# itself is a strongly-typed language. Variables must be assigned a type, in contrast to languages like JavaScript or VBScript where a variable can hold any data type. The same is true with your database server, whether it is Oracle, Sybase, or Microsoft SQL Server. Database columns must be assigned a type: whether it is Text, Char, VarChar, Numeric, Int, or Binary.

Using ADO.NET, you can choose to use a generic (untyped) Data Set, or you can create a strongly-typed DataSet by creating an XSD. When you use a dataset to assign values to instance members in your business objects, the data set type must match your business object member or you must convert the type from object to the type. Creating a strongly-typed dataset also ensures that a dataset does not change (in the case of a modified stored procedure) and enables intellisense rather than dictionary-based access.

By default a data set is not strongly-typed; to create a strongly-typed dataset you must first create it as a DataSet Class. Thankfully, Visual Studio .NET automates much of this task.

Creating a strongly-Typed DataSet from SQL Server

While you can create an ST Data Set anywhere, I strongly recommend creating a data transport DLL. In distributed applications, this DLL may be placed both on multiple servers, and you do not want the entire data access DLL on each server. This will be a lightweight DLL with nothing but DataSet classes and will typically be referenced by the Data Access DLL and the Business Logic DLL.

The first step is to create a DS Common project and give it a proper namespace. I’ll give mine the namespace Demo.DSCommon and I’ll have a good way of referencing it. The next step is to create a DataSet class. In the Visual Studio Solution Explorer, Right click your project and select “Add New Item”. Click DataSet and name it. We’ll call it “SalesByCategory.xsd” as we’ll be using that procedure in the Pubs database. We’ll actually be creating an XSD (XML Schema definition) that Visual Studio will use to generate the class with. We’ll later look at writing these XSDs ourselves, which is handy for “unsupported” databases.

Open the Server Explorer pane and add a new data connection to the Pubs database on your DB server. It’s fine to use the “SA” account here as we won’t be using this connection in our application, only to administer the database.

Next, select the “SalesByCategory” Stored Procedure and drag it to the design surface. You’ll see that it creates an XML “table” for us, and generates the required XML code. Change the table name to Sales (from SalesByCategory) as it cannot share the same table name as the class.

Listing 17-3. Autogenerated XSD Code
<?xml version="1.0" encoding="utf-8" ?>
<xs:schema id="SalesByCategory" targetNamespace="http://tempuri.org/SalesByCategory.xsd" 
    elementformDefault="qualified" attributeformDefault="qualified" 
    xmlns="http://tempuri.org/SalesByCategory.xsd" 
    xmlns:mstns="http://tempuri.org/SalesByCategory.xsd" 
    xmlns:xs="http://www.w3.org/2001/XMLSchema" 
    xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
    <xs:element name="SalesByCategory" msdata:IsDataSet="true">
        <xs:complexType>
            <xs:choice maxOccurs="unbounded">
                <xs:element name="Sales">
                    <xs:complexType>
                        <xs:sequence>
                            <xs:element name="ProductName" type="xs:string" />
                            <xs:element name="TotalPurchase" msdata:ReadOnly="true"

 type="xs:decimal" minOccurs="0" />
                        </xs:sequence>
                    </xs:complexType>
                </xs:element>
            </xs:choice>
        </xs:complexType>
    </xs:element>
</xs:schema>				

To use this dataset, simply compile this class library and create a method in a data access class that will return it. To see the files it created, select “view all files” in the solution explorer. It is only the SalesByCategory class we will use, but since we will only use the DLL, there is no need to be concerned about extra XSD files.

We will now create a Data Access project and give it the default namespace of Demo.Data. In your data access project, be sure to include a reference to System.Data and the DSCommon dll we just created. Create a “Sales” class in Data.

Listing 17-4 Data Access Code
using System;
using System.Data;
using System.Data.SqlClient;
using Demo.DSCommon;
using System.Diagnostics;


namespace Demo.Data
{
    public class Sales
    {
        public static DSCommon.SalesByCategory ByCategory(string cat, string year){
        //Create the dataset
        DSCommon.SalesByCategory ds = new DSCommon.SalesByCategory();

        //Store the connection string (preferably not here!!!)
        string conn = "server=dbserver;database=Northwind;uid=sa;pwd=secure";

        //Create a connection
        System.Data.SqlClient.SqlConnection connect = 
        new SqlConnection(conn);
			
        //Create the proc string
        string proc = "dbo.SalesByCategory";

        //Create the command
        System.Data.SqlClient.SqlCommand command = 
        new System.Data.SqlClient.SqlCommand(proc,connect);

        command.CommandType = CommandType.StoredProcedure;

        //Create the params (create 1 and reuse it)
        System.Data.SqlClient.SqlParameter param;
        param = command.Parameters.Add("@CategoryName", SqlDbType.VarChar, 15);
        param.Direction= System.Data.ParameterDirection.Input;
        param.Value = cat;

        //param = command.Parameters.Add("@OrdYear", SqlDbType.NVarChar, 4);
        //param.Direction= System.Data.ParameterDirection.Input;
        //param.Value = year;

        connect.Open();

        //Create a SQL Adapter and fill the ds
        System.Data.SqlClient.SqlDataAdapter da = 
        new SqlDataAdapter(command);

        //Add the table mapping(s)
        da.tableMappings.Add("table", "Sales");
	
        //--- Syntax for multiple DS tables:
        //da.tableMappings.Add("table1", "SalesPerson");
        //da.tableMappings.Add("table2", "SalesTeam");

        da.Fill(ds);

        connect.Close();
        connect.Dispose();

        return ds;
        }
    }
}

Listing 17-4 illustrates the data access class. The elusive key of getting Data Sets to fill properly is to add the table Mappings. If your DataSets aren’t getting filled, this is probably your mistake. You won’t get a compile or run-time error either making it an extremely annoying bug in your code!

All that is left now is implementing a client application. Our client application will simply call the static method of Demo.Data.Sales and will receive the typed dataset. Typically this will be done in a middle-tier business object, but we will just write the data to the console.

Listing 17-5. Implementing the Client: Class1.cs
using System;

namespace Demo.Client
{
    class Class1
    {
        [STAThread]
        static void Main(string[] args)
        {
            Console.WriteLine("Dataset Demo");

            Demo.DSCommon.SalesByCategory ds = 
            new Demo.DSCommon.SalesByCategory();

            ds = Demo.Data.Sales.ByCategory("Seafood","1997");
			
            foreach (DSCommon.SalesByCategory.SalesRow row in ds.Sales)
            {
                Console.WriteLine(row.ProductName + " " + row.TotalPurchase.ToString());
            }

            //Keep it open long enough to read.
            string foo = Console.ReadLine();
        }
    }
}

strongly Typed DataSets with Sybase ASE

To access ODBC data sources such as Sybase ASE from .NET, you will need to install the ODBC .NET Data Provider. You can download it from http://msdn.microsoft.com/downloads/default.asp?url=/downloads/sample.asp?url=/msdn-files/027/001/668/msdncompositedoc.xml.

On my Sybase ASE Server, I have an Accounts table in the Utility database. I have a simple stored procedure named UserList that I want to create a dataset from. Since I don’t have a native provider to Visual Studio I’ll have to create the XSD manually this time. We’ll use the same application as before to do this. Open the DS Common project and add a new blank DataSet. (Hint: to get the schema definition correct, refer to an existing DataSet XSD! You can also refer to Wrox press Professional ADO.NET for full details.) We’ll call it UserList.xsd, and it will get a list of names and phone numbers of our account users and a list of companies. The Data tables we want are Users and Companies.

Listing 17-6. Empty XSD Shell
<?xml version="1.0" encoding="utf-8" ?>
<xs:schema id="UserList" 
    targetNamespace="http://tempuri.org/UserList.xsd" 
    elementformDefault="qualified" attributeformDefault="qualified"
    xmlns="http://tempuri.org/UserList.xsd"
    xmlns:mstns="http://tempuri.org/UserList.xsd"
    xmlns:xs="http://www.w3.org/2001/XMLSchema"
    xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
    <xs:element name="UserList" msdata:IsDataSet="true">
        <xs:complexType>
            <xs:choice maxOccurs="unbounded"></XS:CHOICE>
        </xs:complexType>
    </xs:element>
</xs:schema>

Starting with the empty XSD shell, we will only need to add two Data tables to the XSD. While you can do this visually, we will look at the XSD method. Choose the XML view tab in Visual Studio. You may even notice that the XSD is initially not well formed- it is missing a closing tag on xs:choice!

To add an Data table, we’ll add the following element and attributes. The element is the Data table, and the attributes are its columns. To add multiple data tables, just create multiple elements.

Listing 17-7. UserList.xsd
<?xml version="1.0" encoding="utf-8" ?>
<xs:schema id="UserList" 
    targetNamespace="http://tempuri.org/UserList.xsd" 
    elementformDefault="qualified" attributeformDefault="qualified"
    xmlns="http://tempuri.org/UserList.xsd"
    xmlns:mstns="http://tempuri.org/UserList.xsd"
    xmlns:xs="http://www.w3.org/2001/XMLSchema"
    xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
    <xs:element name="UserList" msdata:IsDataSet="true">
        <xs:complexType>
            <xs:choice maxOccurs="unbounded">
                <xs:element name="Users"> <xs:complexType> <xs:sequence> <xs:element
                    name="UserName" type="xs:string" minOccurs="0" /> <xs:element name="Phone"
                    type="xs:string" minOccurs="0" /> </xs:sequence> </xs:complexType>
                    </xs:element>
                                    <xs:element name="Companies"> <xs:complexType> <xs:sequence>
                                        <xs:element name="CompanyName" type="xs:string" minOccurs="0" /> </xs:sequence>
                                        </xs:complexType> </xs:element>
            </XS:CHOICE>
        </xs:complexType>
    </xs:element>
</xs:schema>

After compiling the DSCommon dll, we will now return to the Demo.Data project and create a new class (.cs) file. This time we’ll call it User.cs. The code will be similar to the SQLClient code in Sales.cs, but this time we’ll use OleDB.

Listing 17-8 User.cs
using System;
using System.Data;
using Microsoft.Data.Odbc;

namespace Demo.Data
{
    public class User
    {
        public static DSCommon.UserList UserData()
        {
            //Create the dataset
            DSCommon.UserList ds = new DSCommon.UserList();

            //Store the connection string (preferably not here!!!)
            string conn = "DSN=nathanUtility;NA=192.168.0.14,2048;DB=Utility;UID=

            sa;PWD="; //Create a
            connection Microsoft.Data.Odbc.OdbcConnection connect 
            = new
			
            OdbcConnection(conn); //Create the proc
            string string proc =

            "dbo.UserList"; //Create the
            command Microsoft.Data.Odbc.OdbcCommand command 
            = new

            OdbcCommand(proc,connect); command.CommandType =

            System.Data.CommandType.StoredProcedure;

            connect.Open(); //Create an Adapter and fill the ds
            Microsoft.Data.Odbc.OdbcDataAdapter da = 
            new Microsoft.Data.Odbc.OdbcDataAdapter(command);
	        

            //Add the table mapping(s)
            da.tableMappings.Add("table", "Users");
            da.tableMappings.Add("table1", "Companies");

            da.Fill(ds);
			
            connect.Close();
            connect.Dispose();

            return ds;
        }
    }
}

We will now modify the Console Application code to use the Sybase data. Below is the code listing for the Main method, which simply receives the DataSet and writes it to the Console.

Listing 17-8 Updated User.cs Main method
static void Main(string[] args)
{
    Console.WriteLine("Dataset Demo"); 
    Demo.DSCommon.UserList ds = Demo.Data.User.UserData(); 

    foreach (DSCommon.UserList.UsersRow row in ds.Users)
    {
        Console.WriteLine(row.UserName + " " + row.Phone);
    } 

    foreach (DSCommon.UserList.CompaniesRow row in ds.Companies)
    {
        Console.WriteLine(row.CompanyName);
    }

    //Keep it open long enough to read.
    string foo = Console.ReadLine();
}

As you saw, creating strongly-Typed Data Sets manually is not difficult, and we have seen the plumbing of the DataSet in the process. There are additional attributes you can add to the XSD to further modify the generated DataSet classes, and they make a nice data transport mechanism.

There are several cons to creating datasets, however. They are more expensive than the lightweight DataReader and do have slight performance hits in their creation, however, this is negligible considering the data transport mechanism they provide. You may, however, opt to consider other data transport mechanisms depending on your project, and how much development resources you want to put into this tier. There are also multiple benefits of using datasets, especially in using ADO.NET’s “out-of-the-box” functionality. You can also use a DataSet to transport data regardless of its provider. You can fill a DataSet programmatically from business objects, XML data, text files, database sources, and just about anything else. Data Sets enable us to loosely integrate multiple data sources, and make it easier to swap data sources.

For further exploration, I recommend the Wrox press book “Professional ADO.NET Programming”, an in-depth study of the ADO.NET data tier including advanced topics such as creating a custom .NET data provider.

Summary

This has been an introduction to strongly-Typed Data Sets. By now, you should have a good understanding of what a strongly-Typed DataSet is and how to create them in your projects. You should be able to create them from a managed data provider (SQL Server or Oracle) or an ODBC data source such as Sybase ASE. Please feel free to contact me with any questions or comments you may have about this lesson.

Feedback

About Daniel Larson

Daniel Larson is a Microsoft Certified Solutions Developer (MCSD) and independent .NET software development consultant, specializing in SQL Server-based .NET applications.

Create and manage MS Word Documents with Jisys WordReports

Introduction

For several months, I searched for a .NET component that would allow me to easily create and manage Word documents as well as incorporate mail merging functionalities into my web application. In my mind, the component had to meet these minimal requirements:

  1. Must expose the same functionality that MS Word provides in performing document creation, management, and mail merging functions.
  2. Must not rely on Microsoft Word.
  3. Must not employ Automation (explained below), because I wanted to avoid having to learn the MS Word object model.
  4. Must provide a simple interface to use so that I could easily incorporate the component into my application.
  5. Must allow royalty free distribution of the assembled component. The fee I charge for my web application is based on an Application Service Provider model, thus, I did not want to add any additional costs to the base price.

Naively enough (in retrospect), I did not think that these requirements were so far fetched! After all, I was certain that someone else must have had this set of requirements at one point or another. My first stop was the www.asp.net. I did not find a component that even came close to meeting my requirements. I then turned to the rest of the web and searched in the major search engines for what I thought was a product that I would definitely find. To my surprise, my efforts in finding a solution to my problem were futile.

Through a colleague I heard about a product, under development at the time, which could potentially meet my requirements. I immediately looked up the company on the web and to my disappointment found the site ‘under development.’ Well, Like Pavlov’s dog, I eagerly anticipated getting my hands on this product for almost two months. Like clock work I visited the site at least once a week looking for the announcement of this .NET component. Finally, in July of this year, the site became available and I was able to download and test this component.

This article presents a brief product review of the WordReports .NET component by Jisys. In my humblest of opinions, this company has successfully identified and filled a market gap in the generation and manipulation of Word documents without relying on MS Word and Automation. Because the folks at Jisys have done an outstanding job in documenting the features and “how to’s” of this product, I will not be redundant and present all of the code samples I tested. Instead, I will point you to their web site at http://www.jisys.com. There you will find well documented examples and tutorials that will guide you in the installation and use of this product. It took me less than 10 minutes to incorporate the WordReports component into my application to instantly generate mailing labels (excerpt code listed below).

What is Automation?

Before we delve into WordReports, let’s discuss what Automation is and how to incorporate it into a .NET application. Prior to the introduction of WordReports, creating mail-merged documents using Microsoft .NET managed code meant (1) using MS Word, and (2) relying on Automation as a “bridge” into the classes and methods made available by the MS Word object model. But what is Automation? Well, simply stated, Automation is a process that allows a program written in a language, such as C# or VB.NET, to control other Microsoft applications (such as Word, Excel, or PowerPoint) programmatically. Thus, tasks that are normally performed via the user interface can be executed by the controlling program. Candidate tasks for Automation can include creating new MS Word documents, opening an existing document, adding text to documents, performing MS Word mail-merge functions, etc…

Create and manage MS Word Documents with Jisys WordReports
Photo credit to MakeUseOf

How does Automation work?

Automation is accomplished by using a collection of classes and methods exposed by the program that you would like to assume control of. In MS Word, the COM object model is the bridge to using the functionalities typically available via the Word user interface. Access to this object model begins with establishing a reference to the type library. I will demonstrate how this is done in Visual C# .NET. Bare in mind that this can be performed from any of the .NET managed languages. Anyway, here are the steps I followed to add the Word COM object into my project:

  1. Go to the solutions explorer in Visual Studio .NET.
  2. Right click on the references system folder.
  3. Select “Add Reference” from the pop-up options.
  4. In the Add Reference dialog, click on the COM tab.
  5. Search for the Microsoft Word component and click on the Select button.

Now, with the Word COM assembly in the application, the different types of functions available to the MS Word via the user interface can be performed. Here’s an example of how to open a new MS Word document, append some text to it, save it, and quit MS Word.

The first thing we need to do is create an instance of MS Word in our application with a declaration statement like this:

Word.ApplicationClass myWordApp = new Word.ApplicationClass();

With the instance in place, we can call the methods and properties provided by MS Word in creating, managing, and manipulating Word documents.

Word.Document myWordApp =
myWordApp.Documents.Add(ref missing, ref missing, ref missing, ref missing);
myWordApp.Activate();                                                                  // start Word
myWordApp.Selection.TypeText(“Hello World”);                             // Enter the text
myWordApp.Section.TypeParagraph();
myWordApp.SaveAs(“c:\\HelloWorld.Doc);                                     // Save the file
myWordApp.Application.Quit(ref missing, ref missing, ref missing); // Quit Word

That did not seem too difficult. However, there are two issues that forced me to move away from Automation.

  1. A registered copy of MS Word is required on the system where my web application will run.
  2. I have to learn the Word object model in order to perform the most rudimentary functions.

In my case, these two issues were in contrast to my original requirements as stated in the introduction section. First of all, I do not want to have to worry about MS Word licensing fees and requirements for every web solution I distribute. Secondly, learning the Word object model was not an appetizing thought to me. I needed a simpler solution and found it in WordReports.

WordReports

With WordReports, I was able to easily create Word documents and reports without having to rely on Microsoft Word or Automation. WordReports supports all MS Word page layouts and exposes a simple object model which gave me complete control of Word document generation. Via the object model I was able to programmatically manage page layouts, font sizes, styles, colors, document headers and footers. In addition, I tested several table templates with minimal effort and incorporated images, such as company logo’s, into the reports. I can’t say enough about how pleased I was when testing this product.

Anyway, all of WordReports functions are exposed via an extremely simple interface. In fact, here’s the same example previously discussed in the Automation section:

// Create instance of the WordReport Wordwriter component

WordWriter doc = new WordWriter();  
doc.WriteLine(“Hello World”);                       // Hello World
doc.Save(“MyDocument.doc”);                    
// Save the document

Three lines of code are all it took to duplicate the same functionality we attempted with Word Automation. Now, that’s what I call efficiency!

In the introduction section of this article, I mentioned my effort in developing a web application that would use mail merging functionality. The code listed below is an excerpt of how I incorporated WordReports into a control (.ascx) file to create mailing labels. I simply ‘cut-n-pasted’ the code directly from samples provided by Jisys and made the appropriate modifications. Everything worked perfectly the first time I compiled the program. As a note, I am using Microsoft’s IBuySpy portal solution as the framework for my application.

  public void Make_Labels(object sender, System.EventArgs e)
    {
      // Obtain contact information from the Investor table
      InvestorsDB Investors = new InvestorsDB ();
      SqlDataReader reader = Investors.GetInvestors(ModuleId);

      // Create instance of the WordReports Word writer.
      WordWriter writer = new WordWriter();

      // Spacing.
      writer.Paragraph();

      // Initiate.
      writer.Setfont(fontFace.Arial, 9);

      // Cell count.
      int pos = 0;

      // Decrease default margins by 50%
      writer.MarginTop /= 2;
      writer.MarginBottom /= 2;
      writer.MarginLeft /= 2;
      writer.MarginRight /= 2;

      // Compute cell width.
      int width = (writer.PaperWidth - 
         (writer.MarginLeft + writer.MarginRight)) / 3;

        // Row count.
      int rows = 0;

      // Read the results.
        while(reader.Read())
      {
          // New row?
         if(pos == 0)
           {
             // Start the table row.
            writer.Row();
         }

         // Get the fields.
         string companyName = 
            reader["Investor_Company_Name"].ToString();
         string address = 
             reader["Investor_Company_Address"].ToString();
         string city = 
            reader["Investor_Company_City"].ToString();
         string state = 
            reader["Investor_Company_State"].ToString();
         string postalCode = 
            reader["Investor_Company_Zip_Code"].ToString();

         // Write this address.
         writer.Cell(width);
         writer.WriteLine(companyName);
         writer.WriteLine(address);
         writer.WriteLine(city + " " + state + " " + postalCode);
         writer.WriteLine("");
           writer.EndCell();

         // Increment.
           pos++;

         // New row?
         if(pos == 3)
           {
             // End row.
            writer.EndRow();

            // Increment row.
            rows++;

            // End of page?
            if(rows > 9)
            {
                 // New page.
                 writer.PageBreak();
                 writer.Paragraph();
                 rows = 0;
            }

            // Reset.
            pos = 0;
           }
        }

      // Pending row?
      if(pos > 0)
      {
          // End the row.
         writer.EndRow();
      }

      // Close the reader.
      reader.Close();

      // Spacing.
      writer.Paragraph(2);

      string LabelFolder = Server.MapPath("MailingLabels");
      string filedate = DateTime.Now.ToLongDateString();

      writer.Save(
         LabelFolder + "/" + "CustomerLabels" + filedate + ".doc");
  }

Summary

If you have any need to generate Word documents in your .NET application, you can’t go wrong with WordReports. The product documentation includes detailed coding examples and tutorials that will have you up and running with this component in no time. To Jisys, great job and I look forward to more innovative products from your company.

About the author

For nearly 19 years, Joél Contreras has been employed in the Information Technology area of a major retirement and financial firm. During this tenure, he has played significant roles in possibly every area of the company’s technological organization including Database Management System, Network Infrastructure, Project Management, Disaster Recovery, and Systems Programming. Although he has been in management for over fourteen years, he has never lost the passion for “playing” with new technology and thus, it is no wonder he has embraced the .NET framework and C#. Joél has a BA in Computer Information Systems and a Master of Business Administration. He can be reached at [email protected]