ðHgeocities.com/adst_ecit/Csharp_Course.htmgeocities.com/adst_ecit/Csharp_Course.htmdelayedxÀgÔJÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÈ“y,ÂOKtext/html1Ö+Ý,Âÿÿÿÿb‰.HThu, 22 Mar 2001 05:22:01 GMTÀMozilla/4.5 (compatible; HTTrack 3.0x; Windows 98)en, *½gÔJ, C# Reference Guide

Introduction

The NGWS Runtime

You are provided with a runtime environment by NGWS, the NGWS runtime. This runtime manages the execution of code, and it provides services that make programming easier. As long as the compiler that you use supports this runtime, you will benefit from this managed execution environment.

The benefits your applications gain from the NGWS runtime are

Specification)

For the NGWS runtime to provide all these benefits, the compiler must emit metadata along with the managed code. The metadata describes the types in your code, and is stored along with your code (in the same PE-portable executable –file).

As you can see from the many cross language features, the NGWS is mainly about tight integration across multiple different programming languages. This support goes as far as allowing you to derive a C# class from a Visual Basic object (given that certain prerequisites that are discussed in later part)

The NGWS runtime provides the memory management, and the garbage collector releases the objects or variables when their lifetime is over, that is when they are no longer referenced.

 

There are even bonuses when deploying a managed application or component. Because managed applications contain metadata, the NGWS runtime can use this information to ensure that your application has the specified versions of everything it needs. The net result is that your code is less likely to break because some dependency is not met. Another advantage of the metadata approach is that type information resides in the same file where the code resides.

 

Common Language Runtime

The .NET Framework provides a run-time environment called the Common Language Runtime, which manages the execution of code and provides services that make the development process easier. Compilers and tools expose the runtime's functionality and enable you to write code that benefits from this managed execution environment. Code that you develop with a language compiler that targets the runtime is called managed code; it benefits from features such as cross-language integration, cross-language exception handling, enhanced security, versioning and deployment support, a simplified model for component interaction, and debugging and profiling services.

To enable the runtime to provide services to managed code, language compilers are required to emit metadata, which means that they provide information that describes the types, members, and references in your code. Metadata is stored along with the code: every loadable common language runtime image contains metadata. The runtime uses metadata to locate and load classes, lay out instances in memory, resolve method invocations, generate native code, enforce security, and set up run time context boundaries.

The runtime automatically handles object layout and manages references to objects, releasing them when they are no longer being used. Objects whose lifetimes are managed in this way by the runtime are called managed data. Automatic memory management eliminates memory leaks as well as some other common programming errors. If your code is managed, you can use managed data; however, you can instead use unmanaged data if you wish, or you can use both managed and unmanaged data in your .NET application. Because language compilers supply their own types, such as primitive types, you might not always know (or need to know) whether your data is being managed.

The Common Language Runtime makes it easy to design components and applications whose objects interact across languages. Objects written in different languages can communicate with each other, and their behaviors can be tightly integrated. For example, you can define a class, then, using a different language, derive a class from your original class or call a method on it. You can also pass an instance of a class to a method on a class written in a different language. This cross-language integration is possible because language compilers and tools that target the runtime use a common type system defined by the runtime, and they follow the runtime's rules for defining new types, as well as creating, using, persisting, and binding to types.

 

As part of their metadata, all managed components carry information about the components and resources they were built against. The runtime uses this information to ensure that your component or application has the specified versions of everything it needs; as a result, your code is less likely to break due to some dependency not being met. Registration information and state data are no longer stored in the registry where it can be difficult to establish and maintain; instead, information about the types you define (and their dependencies) is stored with the code as metadata, making the tasks of component replication and removal much less complicated.

Language compilers and tools expose the runtime’s functionality in ways that are intended to be useful and intuitive to their developers. This means that some features of the runtime might be more noticeable in one environment than in another. Therefore, how you experience the runtime depends on which language compilers or tools you use. For example, if you are a Visual Basic developer, you might notice that with the Common Language Runtime, the Visual Basic language has more object-oriented features than before. The following benefits of the runtime might be particularly interesting to you:

If you use Visual C++, you can write managed code using managed extensions to C++, which provide the benefits of a managed execution environment while giving you access to powerful capabilities and expressive data types that you are familiar with. You might find the following runtime features especially compelling:

inheritance.

so that reference counting is unnecessary.

Definition Language) unnecessary.

system that supports the runtime.

 

 

Metadata

In the past, a compiled component (.exe or .dll) was only able to communicate with another compiled component through a binary interface. Furthermore, since different languages sometimes had conflicting protocols for defining and storing data, inter-language communication was made difficult. One way that the .NET Framework solves these problems is by allowing compilers to emit additional declarative information into all .NET Framework files and assemblies. This information, called metadata, serves as a roadmap for compiled files to seamlessly interact.

Metadata is binary information describing your code that is either stored in a .NET Framework portable executable (PE) file or in memory. When your code is compiled into a PE file, metadata is inserted into one portion of the file while your code is converted to Microsoft intermediate language (MSIL) and inserted into another. Every type and member defined and referenced in a file or assembly is described within metadata. When code is executed, the Common Language Runtime loads metadata information into in-memory data structures that it references when it requires information about your code's classes, members, inheritance, etc. Runtime reflection services are used to retrieve information from in-memory data structures.

Many key .NET Framework benefits stem from the runtime's use of metadata. Metadata provides a common frame of reference that enables communication between the runtime, compilers, debuggers, and code that has been compiled into MSIL. It enables the runtime to locate and load your code, generate native code at runtime, and provide memory management services. The runtime is able to track which portions of memory your code is allowed to access and prevent it from accessing memory that it shouldn't. Metadata also helps the runtime and garbage collection keep track of memory that will be released back to the operating system when it is no longer needed.

JIT Compilation

Before MSIL can be executed, it must be converted by a .NET Framework Just In Time (JIT) compiler to native code, which is CPU-specific code that runs on the same computer architecture that the JIT compiler is running on. Because the runtime supplies a JIT compiler for each CPU architecture that the runtime operates on, developers can write a set of MSIL that can be JIT-compiled and executed on computers with different architectures. (If your managed code calls platform-specific native APIs or a class library that is platform-specific, your code is limited to running on a specific operating system.)

The idea behind JIT compilation recognizes the fact that some code may never get called during execution; therefore, rather than using time and memory to convert all of the MSIL in a PE (portable executable) file to native code, it makes sense to convert the MSIL as it is needed during execution and store the resulting native code so that it is accessible for subsequent calls. The loader creates and attaches a stub to each of the type's methods when the type is loaded; on the initial call to the method, the stub passes control to the JIT compiler, which converts the MSIL for that method into native code and modifies the stub to direct execution to the location of the native code. Subsequent calls of the JIT-compiled method proceed directly to the native code that was previously generated, reducing the time it takes to JIT compile and execute the code.

As part of compiling MSIL to native code, the code must pass a verification process (unless an administrator has established a security policy that allows the code to bypass verification). Verification examines MSIL and metadata to see whether the code can be determined to be type-safe, which means that it is known to access only the memory locations it is authorized to access. Type safety is necessary to ensure that objects are safely isolated from each other and therefore safe from inadvertent or malicious corruption. It also provides assurance that security restrictions on code can be reliably enforced.

For code that is verifiably type-safe, the runtime can rely on the following statements being true:

During the verification process, MSIL code is examined in an attempt to confirm that the code can access memory locations and call methods only through properly defined types. For example, code cannot allow an object's fields to be accessed in a manner that allows memory locations to be overrun. Additionally, verification inspects code to see whether the MSIL has been correctly generated, since incorrect MSIL could lead to a violation of the type safety rules. The verification process passes a well-defined set of type-safe code, and it passes only code that is type-safe. However, some type-safe code might not pass verification due to limitations of the verification process, and some languages by design do not produce verifiably type-safe code.

 

 

C# Program Structure

C# is a simple but powerful programming language intended for writing enterprise applications.

The C# language is an evolution of C and C++. It uses many C++ features in the areas of statements, expressions, and operators.

C# introduces considerable improvement and innovations in areas such as

 

The following is a sample program showing the structure of an C# program. Some of the components used, have been explained a little later. In the following program, the stress is only on the structure of the program.

Eg.1.1  A Simple Welcome Program:  Welcome.cs

// Namespace Declaration
using System;

// Program start class
class WelcomeToECIL {

        // Main begins program execution.
        public static void Main() {

            // Write to console
            Console.WriteLine("Welcome to Ecil"); 

        }
}

The program eg.1.1 has 4 primary elements, a namespace declaration, a class, a "Main" method, and a program statement.

The namespace declaration indicates that you are referencing the "System" namespace.  Namespaces contain groups of code that can be called upon by C# programs.  With the "using System;" declaration, you are telling your program that it can reference the code in the "System" namespace without pre-pending the word "System" to every reference

The class declaration, "class WelcomeToECIL", contains the data and method definitions that your program uses to execute.  It is one of a few different types of elements your program can use to describe objects, such as interfaces and structures, which will be discussed in more detail in a later lesson.  This particular class has no data, but it does have one method.  This method defines the behavior of this class (or what it is capable of doing).

The one method within the WelcomeToECIL class tells what this class will do when executed.  The method name, "Main", is reserved for the starting point of a program.  Before the word "Main" is a "static" modifier.  The "static" modifier explains that this method works in this specific class only, rather than an instance of the class.  This is necessary, because when a program begins, no object instances exist.  Classes, objects, and instances will be covered in more detail in a later lesson. Every method must have a return type.  In this case it is "void", which means that "Main" does not return a value.  Every method also has a parameter list following it's name with zero or more parameters between parenthesis.  For simplicity, we did not add parameters to "Main".  Later in this lesson we'll show what type of parameter the "Main" method can have.

The "Main" method specifies it's behavior with the "Console.WriteLine(...)" statement.  "Console" is a class in the "System" namespace.  "WriteLine(...)" is a method in the "Console" class.  We use the ".", dot, operator to separate subordinate program elements.  It should be interesting to note that we could also write this statement as "System.Console.WriteLine(...)".  This follows the pattern "namespace.class.method" as a fully qualified statement.  Had we left out the "using System" declaration at the top of the program, it would have been mandatory for us to use the fully qualified form "System.Console.WriteLine(...)".  This statement is what causes the string, "Welcome to Ecil!" to print on the console screen.

Observe that comments are marked with "//".  These are single line comments, meaning that they are valid until the end-of-line.  If you wish to span multiple lines with a comment, begin with "/*" and end with "*/".  Everything in between is part of the comment.  You may place a single line comment within a multi-line comment.  However, you can't put multi-line comments within a multi-line comment.  Comments are not considered when your program is compiled.  They are there to document what your program does in plain English.  

All statements end with a ";", semi-colon.  Classes and methods begin with "{", left curly brace, and end with a "}", right curly brace.  Any statements within and including "{" and "}" define a block.  Blocks define scope (or lifetime and visibility) of program elements, which will be discussed in a later lesson.

Many programs are written to accept command-line input.  Collection of command-line input occurs in the "Main" method.  Eg.2 shows a program which accepts a name from the command line and writes it to the console.

Eg.1.2  Getting Command-Line Input:  NamedWelcome.cs

// Namespace Declaration
using System;

// Program start class
class NamedWelcome {

    // Main begins program execution.
    public static void Main(string[] args) {

        // Write to console
        Console.WriteLine("Hello, {0}!", args[0]);
        Console.WriteLine("Welcome to ECIL!"); 

    }
}

Remember to add your name to the command-line, i.e. "NamedWelcome Sri".

In eg.1.2, you'll notice an entry in the "Main" method's parameter list.  The parameter name is "args".  It's what you use to refer to the parameter later in your program.  The "string[]" expression defines the Type of parameter that "args" is.  The "string" Type holds characters.  These characters could form a single word, or multiple words.  The "[]", square brackets denote an Array, which is like a list.  Therefore, the Type of the "args" parameter is a list of words from the command-line.

You'll also notice an additional "Console.WriteLine(...)" statement within the "Main" method.  The argument list within this statement is different than before.  It has a formatted string with a "{0}" parameter embedded in it.  The first parameter in a formatted string begins at number 0, the second is 1, and so on.   The "{0}" parameter means that the next argument following the end quote will determine what goes in that position.  Hold that thought, and now we'll look at the next argument following the end quote.  

This is the "args[0]" argument, which refers to the first string in the "args" array.  The first element of an Array is number 0, the second is number 1, and so on.  For example, if I wrote "NamedWelcome Sri" on the command-line, the value of "args[0]" would be "Sri".

Now we'll get back to the embedded "{0}" parameter in the formatted string.  Since "args[0]" is the first argument, after the formatted string, of the "Console.WriteLine()" statement, it's value will be placed into the first embedded parameter of the formatted string.  When this command is executed, the value of "args[0]", which is "Joe" will replace "{0}" in the formatted string.  Upon execution of the command-line with "NamedWelcome Sri", the output will be as follows:

>Hello, Sri!

>Welcome to ECIL!

Another way to provide input to a program is via the console.  Eg.1.3 shows how to obtain interactive input from the user.

Eg.1.3. Getting Interactive Input:  InteractiveWelcome.cs

// Namespace Declaration
using System;

// Program start class
class NamedWelcome {

    // Main begins program execution.
    public static void Main() {

        // Write to console/get input
        Console.Write("What is your name?: ");
        Console.Write("Hello, {0}! ", Console.ReadLine());
        Console.WriteLine("Welcome to ECIL!"); 
    }
}

This time, the "Main" method doesn't have any parameters.  However, there are now three statements and the first two are different from the third.  They are "Console.Write(...)" instead of "Console.WriteLine(...)".  The difference is that the "Console.Write(...)" statement writes to the console and stops on the same line, but the "Console.WriteLine(...)" goes to the next line after writing to the console.  The first statement simply writes "What is your name?:  " to the console.  

The second statement doesn't write anything until it's arguments are properly evaluated.  The first argument after the formatted string is "Console.ReadLine()".  This causes the program to wait for user input at the console, followed by a Return or Enter.  The return value from this method replaces the "{0}" parameter of the formatted string and is written to the console.  

The last statement writes to the console as described earlier.  Upon execution of the command-line with "InteractiveWelcome", the output will be as follows:

What is your Name?  <type your name here>
Hello, <your name here>!  Welcome to ECIL!

Exercises:

  1. Write a program to print your name on screen.
  2. Write a program to accept your course from command line.
  3. Write an interactive program to print your course and name on screen

 

Expressions, Types, and Variables

 

"Variables" are simply storage locations for data.  You  may 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.  That is if declare a variable as an integer, then C# will not allow you to use it as an character. 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 in order 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, and decimal.

Eg 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 ECIL- Narayanaguda provides C# programming language content.", content);
        Console.WriteLine("The statement above is not {0}.", noContent);
    }
}

In eg2.1, the boolean values are written to the console as a part of a sentence.  The "bool" type is simply either a true or false.  When run, this program produces the following output:

It is True that ECIL - Narayanaguda provides C# programming language content.

The statement above is not False.

The following table shows the integral types, their size, and range.

 

 

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.  

The following table shows the floating point and decimal types, their 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 may be your best choice.

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.

Category

Operator(s) 

Associativity

Primary

(x)  x.y  f(x)  a[x]  x++  x--  new  typeof  sizeof  checked  unchecked

left

Unary

+  -  !  ~  ++x  --x  (T)x

left

Multiplicative

*  /  %

left

Additive

+  -

left

Shift

<<  >>

left

Relational

<  >  <=  >=  is

left

Equality

==  !=

right

Logical AND

&

left

Logical XOR

^

left

Logical OR

|

left

Conditional AND

&&

left

Conditional OR

||

left

Conditional

?:

right

Assignment

=  *=  /=  %=  +=  -=  <<=  >>=  &=  ^=  |=

right

 

 

 

Left associativity means that operations are evaluated from left to right.  Right associativity mean 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.

Unary Operators

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

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

In eg.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 "preDecrement" variable.

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

The variable "bitNot" is initialized to zero 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".  

Notice the expression "(sbyte)(~bitNot)".  Any operation performed on types sbyte, byte, short, or ushort return integer 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).  Cast operators must be performed explicity 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.

The logical not (!) operator allows you to toggle the value of a boolean variable.  In the example, the "logNot" variable is changed from false to true.  You can expect the following output from the above program.

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

Binary Operators

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

Eg.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 operation (+=) operator.  Any time you use the assignment with operation operator, it's the same as applying the binary operator to both 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.

One type you've seen a lot in the last two lessons is the "string" type.  The "string" type is represented by a list of Unicode characters within single quotes.  i.e. "This is a string."

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

Arrays

Eg.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] = "Sri";
        myStrings[1] = "Sam";
        myStrings[2] = "Raj";
        Console.WriteLine("myStrings[0]: {0}, myStrings[1]: {1}, myStrings[2]: {2}", myStrings[0], myStrings[1], myStrings[2]);

    }
}

Eg.2.4 shows different implementations of Arrays.  The first example is the "myInts" Array.  It is initialized at declaration time with explicit values.

Next is a jagged array.  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.  Arrays can be multi-dimensional, with each dimension separated by a comma.  it must also be instantiated with the "new" operator.

Finally, we have the one dimensional array of strings.

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

 

Exercises

  1. Program to find the sum of any two numbers?
  2. Program to print the product two numbers ‘x’ and ‘y’.

 

Control Statements - Selection

In the last couple 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 with help you branch into separate logical sequences based on decisions you make.

Our first selection statement is the "if" statement.  It has three primary forms:  a single decision, an either/or decision, and multi-case decision.

Eg.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 brackets
        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 between 1 and 10.", myInt);
        }
        else if (myInt > 10 && myInt <= 20) {
            Console.WriteLine("Your number {0} is between 11 and 20.", myInt);
        }
        else if (myInt > 20 && myInt <= 30) {
            Console.WriteLine("Your number {0} is between 21 and 30.", myInt);
        }
        else {
            Console.WriteLine("Your number {0} is greater than 30.", myInt);
        }
    }
}

The statements in Eg.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.  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 the enter or return key.  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 integer, "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 the "myInt" variable, which is an integer 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 }.  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 in 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.

The second "if" statement is much like the first, except it does not have a block.  Therefore, if it's boolean expression evaluates to true, the first statement after the boolean expression will be executed.  When the boolean expression evaluates the 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. My personal recommendation is to make it a habit to always put your if statements within a block, regardless of whether or not you only have only one statement to execute.  This will help avoid mistakes where you later decide to add a statement and forget to add the curly braces.

Most of the time, you'll want to make an either/or kind of decision.  The third "if" statement in Eg.3.1 presents this idea.  When the boolean expression evaluates to true, the statement(s) immediately following the "if" statement are executed.  However, when the boolean expression evaluates to false, the statements 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" statement of Eg.3.1.  You begin 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, just like the "if" statement.  The rules are the same, when the boolean expression for the "else if" statement evaluates to true, the block immediately following the boolean expression is executed.  This can go on until all cases have been evaluated, but the entire "if/else if" sequence must end with a final "else" part.  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 evaluate to true.  The primary difference between the two OR forms are 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.

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.

Similar to the if/else if/else form of the "if" statement is the "switch" statement.  

 

 

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

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

Eg.3.2 shows a couple switch statements.  The "switch" statement begins with the "switch" keyword followed by the switch expression.  The switch expression must evaluate to one of the following types:  sbyte, byte, short, ushort, int, uint, long, ulong, char, string, or enum type.  (enum types will be covered in another lesson on advanced types)  In the first "switch" statement in listing 3-2, the switch expression evaluates to an int type.

Following the switch expression is the switch block, 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 these choices, the statements immediately following the matching choice are executed, up to and including either a "break" or "goto" statement.

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 it's statements are executed.  Although use of the default label is optional, It is 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 "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.  The "break" statement is optional for the "default" label, as the same behavior will occur without it.  The exception to this is if you place a "goto" statement in the switch block.

The second "switch" statement in eg 3.2 shows the use of the "goto" statement.  The "goto" statement causes program execution to jump to the label following the "goto" keyword.  During execution, if the user types in "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, break out of the switch statement and end the program.  

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. 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.

 

Exercises

1. Program to find weather a number ‘x’ is divisible by another number ‘y’?

Control Statements – Loops

 

 

 

 

 

In the last lesson, you learned how to create a simple loop by using the "goto" statement.  I advised you that this is not the best way to perform loops in C#.  The information in this lesson will teach you the proper way to execute iterative logic with the various C# looping statements.

Our first statement is the while loop.

While Loop

Eg.4.1.  The While Loop:  WhileLoop.cs

using System;

class WhileLoop {

    public static void Main() {
        int myInt = 0;

        while (myInt < 10) {
            Console.Write("{0} ", myInt);
            myInt++;

        }
        Console.WriteLine();
    }
}

Listing 4-1 shows a simple while loop.  It begins with the keyword "while", followed by a boolean expression.  All control statements use boolean expressions.  This means that the expression must evaluate to either a true or false value.  In this case we are checking the myInt variable to see if it is less than (<) 10.  Since myInt was initialized to 0, the boolean expression will return true the first time it is evaluated.  When the boolean expression evaluates to true, the block immediately following the boolean expression will be executed.

Within the while block we print the number and a space to the console.  Then we increment (++) myInt to the next integer.  Once the statements in the while block have executed, the boolean expression is evaluated again.  This sequence will continue until the boolean expression evaluates to false.  Once the boolean expression is evaluated as false, program control will jump to the first statement following the while block. In this case, we will write the numbers 0 through 9 to the console, exit the while block, and print a new line to the console.

Similar to "while" loop is the "do loop" 

Do..While Loop

Eg.4.2. The Do Loop:  DoLoop.cs

using System;

class DoLoop {

    public static void Main() {
        string myChoice;

        do {
            // Print A Menu
            Console.WriteLine("My Address Book\n");

            Console.WriteLine("A - Add New Address");
            Console.WriteLine("D - Delete Address");
            Console.WriteLine("M - Modify Address");
            Console.WriteLine("V - View Addresses");
            Console.WriteLine("Q - Quit\n");

            Console.WriteLine("Choice (A,D,M,V,or Q): ");

            // Retrieve the user's choice
            myChoice = Console.ReadLine();

            // Make a decision based on the user's choice
            switch(myChoice) {
                case "A":
                case "a":
                    Console.WriteLine("You wish to add an address.");
                    break;
                case "D":
                case "d":
                    Console.WriteLine("You wish to delete an address.");
                    break;
                case "M":
                case "m":
                    Console.WriteLine("You wish to modify an address.");
                    break;
                case "V":
                case "v":
                    Console.WriteLine("You wish to view the address list.");
                    break;
                case "Q":
                case "q":
                    Console.WriteLine("Bye.");
                    break;
                default:
                    Console.WriteLine("{0} is not a valid choice", myChoice);
            }

            // Pause to allow the user to see the results
            Console.Write("Press any key to continue...");

            Console.ReadLine();

            Console.WriteLine();

        } while (myChoice != "Q" && myChoice != "q");

// Keep going until the user wants to quit
    }
}

Eg.4.2, shows a "do" loop in action.  The syntax of the "do" loop is "do { <statements> } while (<boolean expression>);".  The statements can be any valid C# programming statements you like.  The boolean expression is the same as all other's we've encountered so far.  It returns either true or false.

One reason you may want to use a "do" loop instead of a "while" loop is to present a message or menu such as the one in Eg.4.2 and then retrieve input from a user.  Since the boolean expression is evaluated at the end of the loop, the "do" loop guarantees that the statement's inside the loop will execute at least 1 time.  However, since a "while" loop evaluates it's boolean expression at the beginning, there is generally no guarantee that the statements inside the loop will be executed, unless you program it explicitly to do so.

We'll do a quick review of Eg.4.2.  In the Main() method, we declare the variable "myChoice" of type string.  Then we print a series of statement to the console.  This is a menu of choices for the user.  We must get input from the user, which is in the form of a Console.ReadLine() method which returns the user's value into the myChoice variable.  We must take the user's input and process it.  A very efficient way to do this is with a "switch" statement.  Notice that we've placed matching upper and lower case letters together to obtain the same functionality.  This is the only legal way to have automatic fall through between cases.  If you were to place any statements between two cases, you would not be able to fall through.  Another point is that we used the "default:" case - a very good habit to make.

For Loop

Eg.4.3.  The For Loop:  ForLoop.cs

using System;

class ForLoop {

    public static void Main() {

        for (int i=0; i < 20; i++) {
            if (i == 10)
                break;
            if (i % 2 == 0)
                continue;
            Console.Write("{0} ", i);
        }
        Console.WriteLine();
    }
}

Eg.4.3 shows the "for" loop.  For loops are good for when you know exactly how many times you want to perform the statements within the loop.  This example produces the same results as the "while" loop in Eg.4.1.  The contents within the "for" loop parenthesis holds three sections separated by semicolons "(<initializer list>; <boolean expression>, <post-loop action list>)".

The initializer list is a comma separated list of expressions.  These expressions are evaluated only once during the lifetime of the "for" loop.  This is in the beginning, before loop execution.  As shown in Eg.4.3, this section is commonly used to initialize an integer to be used as a counter.

Once the initializer list has been evaluated, the "for" loop gives control to its second section, the boolean expression.  There is only one boolean expression, but it can be as complicated as you like as long as the result is true or false.  The boolean expression is commonly used to verify the status of a counter variable.

 

When the boolean expression evaluates to true, the statements within the curly braces of the "for" loop are executed.  Normally, these statements execute from the opening curly brace to the closing curly brace without interruption.  However, in Eg.4.3, we've made a couple exceptions.  We have a couple "if" statements that disrupt the flow of control within the "for" block.

The first "if" statement checks to see if "i" is equal to 10.  Now you see another use of the "break" statement.  It's behavior is similar to the selection statements.  It simply breaks out of the loop at that point and transfers control to the first statement following the end of the "for" block.  

The second "if" statement uses the remainder operator to see if "i" is a multiple of 2.  This will evaluate to true when "i" is divided by 2 with a remainder equal to zero, (0).  When true, the "continue" statement is executed.  Control will skip over the remaining statements in the loop and transfer back to the post-loop action list.  By arranging the statements within a block properly, you can conditionally execute them based upon whatever condition you need. 

When program control encounters either a continue statement or end of block, right curly brace, it transfers to the third section within the "for" loop parentheses, the post-loop action list.  This is a comma separated list of actions you would like to see occur after the statements in the "for" block have been executed.  Eg.4.3 is a typical action, incrementing the counter.  Once this is complete, control transfers to the boolean expression for evaluation.  The loop will continue as long as the boolean expression is true.  When the boolean expression becomes false, control is transferred to the first statement following the "for" block.

 

ForEach Loop

Eg.4.4.  The ForEach Loop:  ForEachLoop.cs

using System;

class ForEachLoop {

    public static void Main() {
        string[] names = {"Srinivas", "Theophilus", "Sravan", "Rajesh"};

        foreach (string person in names) {
            Console.WriteLine("{0} ", person);
        }
    }
}

The "foreach" loop allows you to iterate through a collection.  An array, as used in Eg.4.4, is one such collection (there are others in "System.Collections").  The first thing we've done inside the Main() method is declare and initialize the "names" array with 4 strings.  

Within the "foreach" parentheses is an expression composed of two elements divided by the keyword "in".  The right-hand side is the collection you want to use to access each element.  The left-hand side holds a variable with a type identifier compatible with whatever type the collection returns.  

Every time through this loop the collection is queried for a new value.  As long as the collection can return a value, this value will be put into the read-only variable and the expression will return true, thus causing the statements in the "foreach" block to be executed.  When the collection has been fully traversed, the expression will evaluate to false and control will transfer to the first executable statement following the end of the "foreach" block.

In the case of Eg.4.4, we've used a string variable named "person" to hold each element of the names array.  As long as there are names in the array that have not been returned, we will use the Console.WriteLine() method to print each value of the person variable to the screen.

By now you know how to perform iterative logic with the "while", "do", "for", and "foreach" loops.  You are aware of some common reasoning of when to use each of these.  Finally, you know how to transfer control from the block of a loop based on the conditions you set.

Exercises

  1. Program to find weather a number is prime or not
  2. Program to find the sum of n natural numbers
  3. Program to find the factorial of a number
  4. Program to find the sum of squares of n natural numbers
  5. Program to print your name 10 times

 

Methods

 

Previously, all of our functionality for each program resided in the Main() method.  While this was adequate for the simple programs we used to learn earlier concepts, there is a better way to organize your program, using methods.  Methods are extremely useful because they allow you to separate your logic into different units.

The structure of a method is as follows:

 attributes   modifiers   return-type   method-name   ( parameters )

{ statements }

We will discuss attributes and modifiers a little later.  The return-type can be any C# type.  It can be assigned to a variable for use later in the program.  The method name is a unique identifier for what you wish to call a method.  To promote understanding of your code, a method name should be meaningful and associated with the task the method performs.  Parameters allow you to pass information to and from a method.  They are surrounded by parenthesis.  Statements within the curly braces carry out the functionality of the method.

Eg.5.1.  One Simple Method:  OneMethod.cs

using System;

class OneMethod {

    public static void Main() {
        string myChoice;
        OneMethod om = new OneMethod();

        do {
            myChoice = om.getChoice();

            // Make a decision based on the user's choice
            switch(myChoice) {
                case "A":
                case "a":
                    Console.WriteLine("You wish to add an address.");
                    break;
                case "D":
                case "d":
                    Console.WriteLine("You wish to delete an address.");
                    break;
                case "M":
                case "m":
                    Console.WriteLine("You wish to modify an address.");
                    break;
                case "V":
                case "v":
                    Console.WriteLine("You wish to view the address list.");
                    break;
                case "Q":
                case "q":
                    Console.WriteLine("Bye.");
                    break;
                default:
                    Console.WriteLine("{0} is not a valid choice", myChoice);
            }

            // Pause to allow the user to see the results
            Console.Write("Press any key to continue...");

            Console.ReadLine();

            Console.WriteLine();


        } while (myChoice != "Q" && myChoice != "q"); // Keep going until the user wants to quit
    }

    string getChoice() {
        string myChoice;

        // Print A Menu
        Console.WriteLine("My Address Book\n");

        Console.WriteLine("A - Add New Address");
        Console.WriteLine("D - Delete Address");
        Console.WriteLine("M - Modify Address");
        Console.WriteLine("V - View Addresses");
        Console.WriteLine("Q - Quit\n");

        Console.WriteLine("Choice (A,D,M,V,or Q): ");

        // Retrieve the user's choice
        myChoice = Console.ReadLine();

        return myChoice; 
    }
}

 

 

 

The program in Eg.5.1 is similar to the DoLoop program from Lesson 4, except for one difference.  Instead of printing the menu and accepting input in the Main() method, this functionality has been moved to a new method called getChoice().  The return type is a string.  This string is used in the switch statement in main.  The method name "getChoice" describes what happens when it is invoked.  Since the parenthesis are empty, no information will be transferred to or from the getChoice() method.

Within the method block we first declare the variable "myChoice".  Although this is the same name and type as the "myChoice" variable in Main(), they are both unique variables.  They are local variables and they are visible only in the block they are declared.  In other words, the "myChoice" in getChoice() knows nothing about the existence of the "myChoice" in Main(), and visa-versa.

The getChoice() method prints a menu to the console and gets the user's input.  The "return" statement sends the data from the "myChoice" variable back to the caller, Main(), of getChoice().  Notice that the type returned by the "return" statement must be the same as the return-type in the function declaration.  In this case it is a string.

In the Main() method we must instantiate a new "OneMethod" object before we can use getChoice().  This is because of the way getChoice() is declared.  Since we did not specify a "static" modifier (as for Main()), getChoice() becomes an instance method.  The difference between instance methods and static methods is that multiple instances of a class can be created (or instantiated) and each instance has it's own separate getChoice() method.  However, when a method is static, there are no instances of that method, and you can invoke only that one definition of the static method.

 

So, as stated, getChoice() is not static and therefore, we must instantiate a new object to use it.  This is done with the declaration "OneMethod om = new OneMethod()".  On the left hand side of the declaration is the object reference "om" which is of type OneMethod.  The distinction of "om" being a reference is important.  It is not an object itself, but it is a variable that can refer (or point ) to an object of type OneMethod.  On the right hand side of the declaration is an assignment of a new OneMethod object to the reference "om".  The keyword "new" is a C# operator that creates a new instance of an object on the heap.  What is happening here is that a new OneMethod instance is being created on the heap and then being assigned to the om reference.  Now that we have an instance of the OneMethod object referenced by om, we can manipulate that instance through the om reference.

Methods, fields, and other class members can be accessed, identified, or manipulated through the "." (dot) operator.  Since we want to call getChoice(), we do so by using the dot operator through the om reference:  "om.getChoice()".  The program then executes the statements in the getChoice() block and returns.  To capture the value getChoice() returns, we use the "=" (assignment) operator.  The returned string is placed into Main()'s local myChoice variable.  From there, the rest of the program executes as you expect, using concepts from earlier lessons.

Eg.5.2. Method Parameters:  MethodParams.cs

using System;

class Address {
    public string name;
    public string address;
}

class MethodParams {

    public static void Main() {
        string myChoice;
        MethodParams mp = new MethodParams();

        do {
            // show menu and get input from user
            myChoice = mp.getChoice();

            // Make a decision based on the user's choice
            mp.makeDecision(myChoice);

            // Pause to allow the user to see the results
            Console.Write("Press any key to continue...");
            Console.ReadLine();
            Console.WriteLine();

        } while (myChoice != "Q" && myChoice != "q");

// Keep going until the user wants to quit
    }

    // show menu and get user's choice
    string getChoice() {
        string myChoice;

        // Print A Menu
        Console.WriteLine("My Address Book\n");

        Console.WriteLine("A - Add New Address");
        Console.WriteLine("D - Delete Address");
        Console.WriteLine("M - Modify Address");
        Console.WriteLine("V - View Addresses");
        Console.WriteLine("Q - Quit\n");

        Console.WriteLine("Choice (A,D,M,V,or Q): ");

        // Retrieve the user's choice
        myChoice = Console.ReadLine();

        return myChoice; 
    }

    // make decision
    void makeDecision(string myChoice) {
        Address addr = new Address();

        switch(myChoice) {
            case "A":
            case "a":
                addr.name = "Joe";
                addr.address = "C# Station";
                this.addAddress(ref addr);
                break;
            case "D":
            case "d":
                addr.name = "Robert";
                this.deleteAddress(addr.name);
                break;
            case "M":
            case "m":
                addr.name = "Matt";
                this.modifyAddress(out addr);
                Console.WriteLine("Name is now {0}.", addr.name);
                break;
            case "V":
            case "v":
                this.viewAddresses("Cheryl", "Joe", "Matt", "Robert");
                break;
            case "Q":
            case "q":
                Console.WriteLine("Bye.");
                break;
            default:
                Console.WriteLine("{0} is not a valid choice", myChoice);
        }
    }

    // insert an address
    void addAddress(ref Address addr) {
        Console.WriteLine("Name: {0}, Address: {1} added.", addr.name, addr.address); 
    }

    // remove an address
    void deleteAddress(string name) {
        Console.WriteLine("You wish to delete {0}'s address.", name); 
    }

    // change an address
    void modifyAddress(out Address addr) {
        //Console.WriteLine("Name: {0}.", addr.name);

// causes error!
        addr = new Address();
        addr.name = "Joe";
        addr.address = "C# Station";
    }

    // show addresses
    void viewAddresses(params string[] names) {
        foreach (string name in names) {
            Console.WriteLine("Name: {0}", name);
        }
    }
}

 

Eg.5.2 is a modification of Eg.5.1, modularizing the program and adding more implementation to show parameter passing.  There are 4 kinds of parameters a C# method can handle:  out, ref, params, and value.  To help illustrate usage of parameters, we created an Address class with two string fields.

In Main() we call getChoice() to get the user's input and put that string in the myChoice variable.  Then we use myChoice as an argument to makeDecision().  In the declaration of makeDecision() you'll notice it's one parameter is declared as a string with the name myChoice.  Again, this is a new myChoice, separate from the caller's argument and local only to this method.  Since makeDecision()'s myChoice parameter does not have any other modifiers, it is considered a "value" parameter.  The actual value of the argument is copied on the stack.  Variables given by value parameters are local and any changes to that local variable do not affect the value of the variable used in the caller's argument.  In other words, value parameters are input only.

The switch statement in makeDecision() calls a method for each case.  These method calls are different that the ones we used in Main().  Instead of using the "mp" reference, they use the "this" keyword.  "this" is a reference to the current object.  We know the current object has been instantiated because makeDecision() is not a static method.  Therefore, we can use the "this" reference to call methods within the same instance.

The addAddress() method takes a "ref" parameter.  This means the parameter is passed in as a reference.  The reference is copied on the stack when it is passed to the method.  This reference still refers to the same object on the heap as the original reference used in the caller's argument.  This means any changes to the local reference's object also changes the caller reference's object.  You can think of this as a way to have an input/output parameter.

modifyAddress() has an "out" parameter.  "out" parameters are only passed back to the calling function.  When the method is called, there is only an unassigned reference placed on the stack.  Because of definite assignment rules, you cannot use this variable until it has a valid value assigned.  The first line in modifyAddress() is commented out on purpose to illustrate this point.  Uncomment it and compile to see what happens.  Once assigned and the program returns, the value of the "out" parameter will be copied into the caller's argument variable.  You must assign a value to an "out" variable before your method returns.

A very useful addition to the C# language is the "params" parameter.  This must be a single dimension or jagged array.  In makeDecision() we pass in four comma delimited string arguments.  The number of arguments is variable.  In viewAddresses() we use a foreach loop to print each of these strings.  The "params" parameter is considered an input only parameter and any changes affect the local copy only.

 

Classes

 

Since the beginning of this reference, 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.  Constructors do not have return values and always have the same name as the class.  Eg.7.1 is an example of a class.

 

Eg.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(); 

    }
}

Eg.7.1 shows two classes.  The top class, "OutputClass", has a constructor, instance method, and a destructor.  It also had a field named "myString".  Constructors are used to initialize a class' data members.  In this case, the OutputClass constructor accepts a string argument.  This string is copied to the class field myString.

Constructor's 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 was included in class OutputClass from Eg.7.1.  This default constructor is followed by an initializer.  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 it is 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.

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 are 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 a class member is static, you can access it simply by using the syntax <classname>.<static class member>.  The instance names are "oc1" and "oc2".  

Suppose OutputClass had the following static method:

    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.  There is only ever one copy of a static class member.

Another type of constructor is the static constructor.  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.

OutputClass also has a destructor.  Destructors look just like constructors, except they have a tilde, "~", in front of them.  Destructors are places where you could put code to release any resources your class was holding during it's lifetime.  They are normally called when the C# garbage collector decides to clean your object from memory.

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.

In summary, you can declare normal 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.

 

Class Inheritance

 

Inheritance is one of the primary concepts of object-oriented programming.  It allows you to reuse existing code.  Through effective employment of reuse, you can save time in your programming.

Eg.8.1.  Inheritance:  BaseClass.cs

using System;

public class ParentClass
{
    public ParentClass()
    {
        Console.WriteLine("Parent Constructor.");
    }
    public void print()
    {
        Console.WriteLine("I'm a Parent Class.");
    }
}

public class ChildClass : ParentClass
{
    public ChildClass()
    {
        Console.WriteLine("Child Constructor.");
    }
    public static void Main()
    {
        ChildClass child = new ChildClass();
        child.print();
    }
}


Output:

Parent Constructor.
Child Constructor.
I'm a Parent Class.

Eg.8.1 shows two classes.  The top class is named ParentClass and the main class is called ChildClass.  What we want to do is create a child class, using existing code from ParentClass.

First we must declare our intention to use ParentClass as the base class of ChildClass.  This is accomplished through the ChildClass declaration "public class ChildClass : ParentClass".  The base class is specified by adding a colon, ":", after the derived class identifier and then specifying the base class name.

C# supports single class inheritance only.  Therefore, you can specify only one base class to inherit from.

ChildClass has exactly the same capabilities as ParentClass.  Because of this, you can also say ChildClass "is" a ParentClass.  This is shown in the Main() method of ChildClass when the print() method is called.  Child class does not have it's own print() method, so it uses the ParentClass print() method.  You can see the results in the 3rd line of output.

Base classes are automatically instantiated before derived classes.  Notice the output from Listing 8-1.  The ParentClass constructor executed before the ChildClass constructor.

Eg.8.2.  Derived Class Communicating with Base Class:  BaseTalk.cs

using System;

public class Parent
{
    string parentString;

    public Parent()
    {
        Console.WriteLine("Parent Constructor.");
    }

    public Parent(string myString)
    {
        parentString = myString;
        Console.WriteLine(parentString);
    }

    public void print()
    {
        Console.WriteLine("I'm a Parent Class.");
    }
}

public class Child : Parent
{
    public Child() : base("From Derived")
    {
        Console.WriteLine("Child Constructor.");
    }

    public void print()
    {
        base.print();
        Console.WriteLine("I'm a Child Class.");
    }

    public static void Main()
    {
        Child child = new Child();
        child.print();
        ((Parent)child).print();
    }
}


Output:

From Derived
Child Constructor.
I'm a Parent Class.
I'm a Child Class.
I'm a Parent Class.

Derived classes can communicate with base classes during instantiation.  Eg.8.2 shows how this is done at the child constructor declaration.  The colon, ":", and keyword base call the base class constructor with the matching parameter list.  The first line of output shows the base class constructor being called with the string "From Derived".

Sometimes you may want to create your own implementation of a method that exists in a base class.  The Child class does this by declaring it's own print() method.  The Child print() method hides the Parent print method.  The effect is the Parent print method will not be called, unless we do something special to make sure it's called.

Inside the Child print() method, we explicitly call the Parent print() method.  This is done by prefixing the method name with "base.".  Using the "base" keyword, you can access any of a base class' public or protected class members.  The output from the Child print() method is on output lines 3 and 4.

Another way to access base class members is through an explicit cast.  This is done in the last statement of the Child class Main() method.  Remember that a derived class is a specialization of it's base class.  This fact allows us to perform a conversion on the derived class, making it an instance of it's base class.  The last line of output from Eg.8.2 shows the Parent print() method was indeed executed.

In summary, you know how to create a derived/base class relationship.  You can control instantiation of your base class and call it's methods either implicitly or explicitly.  You also understand that a derived class is a specialization of it's base class.

Interfaces

Interfaces define a set of functionality that classes can implement; however, interfaces contain no implementation, except for static methods and static fields. An interface specifies a contract that a class implementing the interface must follow. Interfaces can contain static or virtual methods, static fields, properties, and events. All interface members must be public. Interfaces cannot define constructors. The runtime allows an interface to require that any class that implements it must also implement one or more other interfaces.

To simplify the definition of interfaces, some languages do not require interface characteristics to be applied explicitly because those characteristics already apply implicitly. For example, all interfaces are implicitly abstract, and every member of the interface is implicitly abstract.

Polymorphism

Another primary concept of object-oriented programming is Polymorphism.  It allows you to implement derived class methods through a base class pointer during run-time.  This is handy when you need to assign a group of objects to an array and then invoke each of their methods.  They won't necessarily have to be the same object type.  However, if they're related by inheritance, you can add them to the array as the inherited type.  Then if they all share the same method name, that method of each object can be invoked.  This lesson will show you how to accomplish this.

Eg.9.1.  A Base Class With a Virtual Method:  DrawingObject.cs

using System;

public class DrawingObject
{
    public virtual void Draw()
    {
        Console.WriteLine("I'm just a generic drawing object.");
    }
}

Eg.9.1 shows the DrawingObject class.  This will be the base class for other objects to inherit from.  It has a single method named Draw().  The Draw() method has a virtual modifier.  The virtual modifier indicate to derived classes that they can override this method.  The Draw() method of the DrawingObject class performs a single action of printing the statement, "I'm just a generic drawing object.", to the console.

Eg.9.2.  Derived Classes With Override Methods:  Line.cs, Circle.cs, and Square.cs

using System;

public class Line : DrawingObject
{
    public override void Draw()
    {
        Console.WriteLine("I'm a Line.");
    }
}

public class Circle : DrawingObject
{
    public override void Draw()
    {
        Console.WriteLine("I'm a Circle.");
    }
}

public class Square : DrawingObject
{
    public override void Draw()
    {
        Console.WriteLine("I'm a Square.");
    }
}

Eg.9.2 shows three classes.  These classes inherit the DrawingObject class.  Each class has a Draw() method and each Draw() method has an override modifier.  The override modifier allows a method to override the virtual method of it's base class at run-time.  The override will happen only if the class is referenced through a base class reference.

Eg.9.3.  Program Implementing Polymorphism:  DrawDemo.cs

using System;

public class DrawDemo
{
    public static int Main(string[] args)
    {
        DrawingObject[] dObj = new DrawingObject[4];

        dObj[0] = new Line();
        dObj[1] = new Circle();
        dObj[2] = new Square();
        dObj[3] = new DrawingObject();

        foreach (DrawingObject drawObj in dObj)
        {
            drawObj.Draw();
        }

        return 0;
    }
}

Eg.9.3 shows a program that uses the classes defined in Eg.9.1 and Eg.9.2.  This program implements polymorphism.  In the Main() method of the DrawDemo class, there is an array being created.  The type of object in this array is the DrawingObject class.  The array is named dObj and is being initialized to hold four objects of type DrawingObject.

Next the dObj array is initialized.  Because of their inheritance relationship with the DrawingObject class, the Line, Circle, and Square classes can be assigned to the dObj array.  Without this capability, you would have to create an array for each type.  Inheritance allows derived objects to act like their base class, which saves work.

After the array is initialized, there is a foreach loop that looks at each element of the array.  Within the foreach loop the Draw() method is invoked on each element of the dObj array.  Because of polymorphism, the run-time type of each object is invoked.  The type of the reference object from the dObj array is a DrawingObject.  However, that doesn't matter because the derived classes override the virtual Draw() method of the DrawingObject class.  This makes the overriden Draw() methods of the derived classes execute when the Draw() method is called using the DrawingObject base class reference from the dObj array.  Here's what the output looks like:

Output:

I'm a Line.
I'm a Circle.
I'm a Square.
I'm just a generic drawing object.

The override Draw() method of each derived class executes as shown in the DrawDemo program.  The last line is from the virtual Draw() method of the DrawingObject class.  This is because the actual run-time type of the fourth array element was a DrawingObject object.

 

Namespaces

In "C# Program Structure", you saw the "using System;" directive in the Simple Hello program.  This directive allowed you to use members of the System namespace. 

Namespaces are C# program elements designed to help you organize your programs.  They also provide assistance in avoiding name clashes between two sets of code.  Implementing Namespaces in your own code is a good habit because it is likely to save you from problems later when you want to reuse some of your code.

Eg.6.1.  The C# Namespace:  NamespaceCSS.cs

// Namespace Declaration
using System;

// The C# Namespace
namespace csharp_ns {

    // Program start class
    class NamespaceCSS {

        // Main begins program execution.
        public static void Main() {

            // Write to console
            Console.WriteLine("This is the new C# Namespace."); 

        }
    }
}


Eg.6.1 shows how to create a namespace.  We declare the new namespace by putting the word "namespace" in front of "csharp_ns".  Curly braces surround the members inside the "csharp_ns" namespace.

Eg.6.2. Nested Namespace 1:  NestedNamespace1.cs

// Namespace Declaration
using System;

// The C# Namespace
namespace csharp_ns {

    namespace inner {

        // Program start class
        class NamespaceCSS {

            // Main begins program execution.
            public static void Main() {

                // Write to console
                Console.WriteLine("This is the new C# inner Namespace."); 

            }
        }
    }
}

Namespaces allow you to create a system to organize your code.  A good way to organize your namespaces is via a hierarchical system.  You put the more general names at the top of the hierarchy and get more specific as you go down.  This hierarchical system can be represented by nested namespaces. 

Eg.6.2 shows how to create a nested namespace.  By placing code in different sub-namespaces, you can keep your code organized. 

Eg.6.3. Nested Namespace 2:  NestedNamespace2.cs

// Namespace Declaration
using System;

// The C# Namespace
namespace csharp_ns.inner {

    // Program start class
    class NamespaceCSS {

        // Main begins program execution.
        public static void Main() {

            // Write to console
            Console.WriteLine("This is the new C# Namespace."); 

        }
    }
}

Eg.6.3 shows another way of writing nested namespaces.  It specifies the nested namespace with the dot operator between "csharp_ns" and "inner".  The result is exactly the same as Eg.6.2.  However, Eg.6.3 is easier to write. 

Eg.6.4. Calling Namespace Members:  NamespaceCall.cs

// Namespace Declaration
using System;

namespace csharp_ns {

    // nested namespace
    namespace inner {
        class myExample1 {
            public static void myPrint1() {
                Console.WriteLine("First Example of calling another namespace member.");
            }
        }
    }

    // Program start class
    class NamespaceCalling {

        // Main begins program execution.
        public static void Main() {

            // Write to console
            inner.myExample1.myPrint1(); 
            csharp_ns.inner.myExample2.myPrint2(); 

        }
    }
}

// same namespace as nested namespace above
namespace csharp_ns.inner {
    class myExample2 {
        public static void myPrint2() {
            Console.WriteLine("Second Example of calling another namespace member.");
        }
    }
}

Eg.6.4 provides an example of how to call namespace members with fully qualified names.  A fully qualified name contains every language element from the namespace name down to the method call.  At the top of the listing there is a nested namespace "inner" within the "csharp-ns" namespace with class "myExample1" and method "myPrint1".  Main() calls this method with the fully qualified name of "inner.myExample1.myPrint()".  Since Main() and the tutorial namespace are located in the same namespace, using "csharp_ns" in the fully qualified name is unnecessary.

At the bottom of Eg.6.4 is an addition to the "csharp_ns.inner" namespace.  The classes "myExample1" and "myExample2" both belong to the same namespace.  Additionally, they could be written in separate files and still belong to the same namespace.  In Main(), the "myPrint2" method is called with the fully qualified name "csharp_station.tutorial.myExample2.myPrint2()".  It must use "csharp_ns" in the fully qualified name, because "myExample2" is declared outside of it's bounding braces.  

Notice that I used different names for the two classes "myExample1" and "myExample2".  This was necessary because every namespace member of the same type must have a unique name.  Remember, they are both in the same namespace and you wouldn't want any ambiguity about which class to use.  The methods "myPrint1" and "myPrint2" have different names only because it would make the lesson a little easier to follow.  They could have had the same name with no effect, because their classes are different, thus avoiding any ambiguity.

Eg.6.5.  The using Directive:  UsingDirective.cs

// Namespace Declaration
using System;
using csharp_ns.inner;

// Program start class
class UsingDirective {

    // Main begins program execution.
    public static void Main() {

        // Call namespace member
        myExample.myPrint(); 

    }
}


// C# Namespace
namespace csharp_ns.inner {
    class myExample {
        public static void myPrint() {
            Console.WriteLine("Example of using a using directive.");
        }
    }
}

If you would like to call methods without typing their fully qualified name, you can implement the "using" directive.  In Eg.6.5, we show two "using" directives.  The first, "using System", is the same "using" directive you have seen in every program in this tutorial.  It allows you to type the method names of members of the "System" namespace without typing the word "System" every time.  In myPrint(), "Console" is a class member of the "System" namespace with the method "WriteLine".  It's fully qualified name is "System.Console.WriteLine(...)".

Similarly, the using directive "using csharp_ns.inner" allows us to implement members of the "csharp_ns.inner" namespace without typing the fully qualified name.  This is why we can type "myExample.myPrint()".  Without the "using" directive, we need to type "csharp_ns.inner.myExample.myPrint()" every time we wanted to implement that method.

 

Eg.6.6.  The Alias Directive:  AliasDirective.cs

// Namespace Declaration
using System;
using csTut = csharp_ns.inner.myExample; // alias

// Program start class
class AliasDirective {

    // Main begins program execution.
    public static void Main() {

        // Call namespace member
        csTut.myPrint();
        myPrint();

    }

    // Potentially ambiguous method.
    static void myPrint() {
        Console.WriteLine("Not a member of csharp_ns.inner.myExample.");
    }
}


// C# Namespace
namespace csharp_ns.inner {
    class myExample {
        public static void myPrint() {
            Console.WriteLine("This is a member of csharp_ns.inner.myExample.");
        }
    }
}

Sometimes you may encounter a long namespace and wish to have it shorter.  This could improve readability and still avoid name clashes with similarly named methods.  Listing 6-6 shows how to create an Alias with the alias directive "using csTut = csharp_ns.inner.myExample".  Now the expression "csTut" can be used anywhere, in this file, in place of "csharp_ns.inner.myExample".  We use it in Main().

Also in Main() is a call to the "AliasDirective" classes' "myPrint" method.  This is the same name as the "myExample" classes' "myPrint" method.  The reason both of these methods can be called in the same method call is because the "myExample" classes' "myPrint" method is qualified with the "csTut" alias.  This lets the compiler know exactly which method is to be executed.  Had we mistakenly omitted "csTut" from the method call, the compiler would have set up the "AliasDirective" classes' "myPrint" method to run twice.

 

 

 

 

 

On another note, what would happen if we had not created the alias directive and instead added a "using csharp_ns.inner.myExample" in it's place and then called myPrint().  The compiler would have generated an error because it could not figure out whether to call "csharp_ns.inner.myExample.myPrint()" or "AliasDirective.myPrint()".  This is one very good reason to develop the habit of using namespaces.  It will help avoid conflicts in your code.

So far, all we've shown in our namespaces are classes.  However, namespaces can hold other types as follows:

Classes, Structures, Interfaces, Enumerations, Delegates

 

 

Properties

Properties are a new language feature introduced with C#. They provide the opportunity to protect a field in a class by reading and writing to it through the property. In other languages, this is accomplished by programs implementing specialized getter and setter methods. C# properties enable this type of protection while also letting you access the property just like it was a field. To get an appreciation for what properties accomplish, let's take a look at how to provide field encapsulation by traditional methods.

Eg.10.1. An Example of Traditional Class Field Access: Accessors.cs

using System;

public class PropertyHolder

{

private int someProperty = 0;

public int getSomeProperty()

{

return someProperty;

}

public void setSomeProperty(int propValue)

{

someProperty = propValue;

}

}

public class PropertyTester

{

public static int Main(string[] args)

{

PropertyHolder propHold = new PropertyHolder();

propHold.setSomeProperty(5);

Console.WriteLine("Property Value: {0}", propHold.getSomeProperty());

return 0;

}

}

Eg 10-1 shows the traditional method of accessing class fields. The PropertyHolder class has the field we're interested in accessing. It has two methods, getSomeProperty and setSomeProperty. The getSomeProperty method returns the value of the someProperty field. The setSomeProperty method sets the value of the someProperty field.

The PropertyTester class uses the methods of the PropertyHolder class to get the value of the someProperty field in the PropertyHolder class. The Main method instantiates a new PropertyHolder object, propHold. Next it sets the someMethod of propHold to the value 5 by using the setSomeProperty method. Then the program prints out the property value with a Console.WriteLine method call. The argument used to obtain the value of the property is a call to the getSomeProperty method of the propHold object. It prints out "Property Value: 5" to the console.

This method of accessing information in a field has been good because it supports the object-oriented concept of encapsulation. If the implementation of someProperty changed from an int type to a byte type, this would still work. Now the same thing can be accomplished much smoother with properties.

Eg.10.2. Accessing Class Fields With Properties: Properties.cs

using System;

public class PropertyHolder

{

private int someProperty = 0;

public int SomeProperty

{

get

{

return someProperty;

}

set

{

someProperty = value;

}

}

}

public class PropertyTester

{

public static int Main(string[] args)

{

PropertyHolder propHold = new PropertyHolder();

propHold.SomeProperty = 5;

Console.WriteLine("Property Value: {0}", propHold.SomeProperty);

return 0;

}

}

Eg.10.2 shows how to create and use a property. The PropertyHolder class has the "SomeProperty" property implementation. Notice that the first letter of the first word is capitalized. That's the only difference between the names of the property "SomeProperty" and the field "someProperty". The property has two accessors, get and set. The get accessor returns the value of the someProperty field. The set accessor sets the value of the someProperty field with the contents of "value". The "value" shown in the set accessor is a C# reserved word. It's normally an error to use the "value" keyword in any other context.

The PropertyTester class uses the SomeProperty property in the PropertyHolder class. The first line of the Main method creates a PropertyHolder object named propHold. Next the value of the someProperty field of the propHold object is set to 5 by using the SomeProperty property. It's that simple -- just assign the value to the property as if it were a field.

After that, the Console.WriteLine method prints the value of the someProperty field of the propHold object. It does this by using the SomeProperty property of the propHold object. Again, it's that simple -- just use the property as if it were a field itself.

Properties can be made read-only. This is accomplished by having only a get accessor in the property implementation.

Eg.10.3. Read-Only Property: ReadOnlyProperty.cs

using System;

public class PropertyHolder

{

private int someProperty = 0;

public PropertyHolder(int propVal)

{

someProperty = propVal;

}

public int SomeProperty

{

get

{

return someProperty;

}

}

}

public class PropertyTester

{

public static int Main(string[] args)

{

PropertyHolder propHold = new PropertyHolder(5);

Console.WriteLine("Property Value: {0}", propHold.SomeProperty);

return 0;

}

}

Eg.10.3 shows how to implement a read-only property. The PropertyHolder class has a SomeProperty property that only implements a get accessor. It leaves out the set accessor. This particular PropertyHolder class has a constructor which accepts an integer parameter.

The Main method of the PropertyTester class creates a new PropertyHolder object named propHold. The instantiation of the propHold object uses the constructor of the PropertyHolder that takes an int parameter. In this case, it's set to 5. This initializes the someProperty field of the propHold object to 5.

Since the SomeProperty property of the PropertyHolder class is read-only, there is no other way to set the value of the someProperty field. If you inserted "propHold.SomeProperty = 7" into the listing, the program would not compile, because SomeProperty is read-only. When the SomeProperty property is used in the Console.WriteLine method, it works fine. This is because it's a read operation which only invokes the get accessor of the SomeProperty property.

 

Eg.10.4. Write-Only Property: WriteOnlyProperty.cs

using System;

public class PropertyHolder

{

private int someProperty = 0;

public int SomeProperty

{

set

{

someProperty = value;

Console.WriteLine("someProperty is equal to {0}", someProperty);

}

}

}

public class PropertyTester

{

public static int Main(string[] args)

{

PropertyHolder propHold = new PropertyHolder();

propHold.SomeProperty = 5;

return 0;

}

}

Eg.10.4 shows how to create and use a write-only property. This time the get accessor is removed from the SomeProperty property of the PropertyHolder class. The set accessor has been added, with a bit more logic. It prints out the value of the someProperty field after it's been modified.

The Main method of the PropertyTester class instantiates the PropertyTester class with a default constructor. Then it uses the SomeProperty property of the propHold object to set the someProperty field of the propHold object to 5. This invokes the set accessor of the propHold object, which sets the value of it's someProperty field to 5 and then prints "someProperty is equal to 5" to the console.

 

Delegates

 

The runtime supports objects called delegates that serve the same purpose as function pointers in C++. Because they are type-safe, secure, managed objects, you get all of the advantages of pointers without any of their disadvantages. For example, delegates will always point to a valid object and cannot corrupt memory of other objects. Besides their use as the equivalent of function pointers, delegates are also used for event handling and callbacks in the .NET Framework.

Each instance of a delegate forwards calls to a method on a particular object. The object and method are chosen when the delegate instance is constructed. Therefore, the definition of a delegate is simply the signature of the method to which it forwards its calls. The implementations of the methods on a delegate are provided by the runtime, not by user code. Developers cannot specify additional members on a delegate.

Two delegate classes are provided by the .NET Framework: Delegate and MulticastDelegate. All delegates have an invocation list, or linked list of delegates that are executed when the invoke method is called. A delegate that derives from the System.Delegate class contains an invocation list with one method, while a delegate that derives from the System.MulticastDelegate contains an invocation list with multiple methods. The MulticastDelegate class contains two static methods to add and remove method references from an invocation list: Combine and Remove.

When you declare a delegate, you tell the compiler the type of each parameter and of its return value. These types correspond to the method that the delegate will ultimately call. For example, the following code defines a delegate that takes no parameters and returns no value:

public delegate void MyDelegate();

You would next declare an instance of the delegate. The delegate constructor takes the name of the method it will call. The following code declares an instance of MyDelegate and makes it call Method1 of Myclass.

MyDelegate TheDelegate = new MyDelegate(MyClass.Method1);

 

You can now call MyDelegate, and it will, in turn, call MyClass.Method1, like this:

TheDelegate();

 

Delegates are particularly suited for event handling. An event is sent by an object or event source in response to an action either performed by a user or some sort of program logic. The event receiver must then act in response to the raised event and perform an action. It is the event delegate's job to act as an intermediary between these two objects. With this in mind, the event delegate is declared with a reference to the method it will call. Once the delegate is invoked, it takes two parameters: the source that raised the event and the data that the target method will need to take as a parameter. The following code shows the declaration for an event delegate called EventHandler:

public delegate void EventHandler(object sender, EventArgs e);

 

You next register the event delegate as an event with the following code:

public event EventHandler Event;

 

Finally you pass an EventArgs object to the OnEvent method. The event delegate then takes a this pointer and the event object. The following code shows the OnEvent method that will be called when the previously registered event is raised:

protected virtual void OnEvent(EventArgs e) {

//Invokes the delegates.

Event(this, e);

}

Once invoked, the delegate will take any data passed to it and call the methods that it references. For a more detailed discussion, see the section entitled Events Overview for Component Developers.

 

Enumerations

 

An enumeration (enum) is a special form of value type, which inherits from System.Enum and supplies an alternate name for an underlying primitive type. An enum type has a name, an underlying type, and a set of fields. The underlying type must be one of the built-in signed or unsigned integer types (such as Int16, Int32, or Int64). The fields are static literal fields, each of which represents a constant. Each field is assigned a specific value of the underlying type by the language compiler. Multiple fields can be assigned the same value. When this occurs, the compiler marks exactly one of the enum values as a "primary" enum value for the value, for the purposes of reflection and string conversion.

You can assign a value of the underlying type to an enum and vice versa (no cast is requiredby the runtime). You can create an instance of an enum, and you can call the methods of System.Enum as well as any methods defined on the enum's underlying type. However, some language compilers might not allow you to pass an enum as a parameter when an instance of the underlying type is required (or vice versa).

The following additional restrictions apply to enumerations:

 

Cross-Language Interoperability

 

Before the Common Language Runtime was available, objects compiled by different language compilers could communicate only if they followed an established binary standard. .NET Framework objects automatically have the ability to communicate and interact with each other, even if they are written in different languages. Your objects can call methods on other objects, inherit implementation from other objects, and pass instances of a class to another class's methods, regardless of the language they are implemented in. In addition, the runtime’s language interoperability enables any debugger to understand your multi-language application and step through it. Exception handling reveals another aspect of language interoperability: the runtime enables exceptions to be handled the same way across languages. Your code can throw an exception in one language and that exception can be caught and understood by an object written in another language.

In some scenarios, language interoperability is particularly desirable. For example, if you are writing components for a distributed Web application, it would be helpful to know that no matter what language you choose to write your components in, they can interact closely with each other and with components supplied by other developers.

Or, suppose you have developed several components for your server application in one language, but you realize that another language is better suited for implementing the functionality that the remaining component provides. Or, maybe an existing corporate code base is in one language and you are asked to enhance it, but you are much more comfortable using a different programming language or you would like to leverage your development staff's experience with another language. It would be convenient to be able to implement the remaining components in the language that is best suited for the job and/or most efficient for you to use.

Language interoperability is also significant with respect to class library development. Because the runtime enables and supports language interoperability, you can develop class libraries that can be called from any language.

 

Preprocessor

While the compiler does not have a separate preprocessor, the directives described in this section are processed as if there was one; these directives are used to aid in conditional compilation. Unlike C and C++ directives, you cannot use these directives to create macros.

A preprocessor directive must be the only instruction on a line.

#if lets you begin a conditional directive, testing a symbol or symbols to see if they evaluate to true. If they do evaluate to true, the compiler evaluates all the code between the #if and the next directive.

#else lets you create a compound conditional directive, such that, if none of the expressions in the preceding #if or (optional) #elif directives did not evaluate to true, the compiler will evaluate all code between #else and the subsequent #endif.

#elif lets you create a compound conditional directive. The #elif expression will be evaluated if neither the preceding #if nor any preceding (optional) #elif directive expressions evaluate to true. If a #elif expression evaluates to true, the compiler evaluates all the code between the #elif and the next directive.

#endif specifies the end of a conditional directive, which began with the #if directive.

#define lets you define a symbol, such that, by using the symbol as the expression passed to the #if directive, the expression will evaluate to true.

#undef lets you undefine a symbol, such that, by using the symbol as the expression in a #if directive, the expression will evaluate to false.

An Example on Preprocessor Statements

#define DEBUG

#define VC_V6

using System;

public class MyClass

{

   public static void Main()

   {

      #if (DEBUG && !VC_V6)

         Console.WriteLine("DEBUG is defined");

      #elif (!DEBUG && VC_V6)

         Console.WriteLine("VC_V6 is defined");

      #elif (DEBUG && VC_V6)

         Console.WriteLine("DEBUG and VC_V6 are defined");

      #else

         Console.WriteLine("DEBUG and VC_V6 are not defined");

      #endif

   }

}

 

 

SECURITY

 

Security is an important aspect, especially so since today’s code comes from multiple sources, including the Internet. Therefore, addressing security needs was an important design goal for NGWS. These needs are addressed on the code level and on the user permission level.

Code-Access Security

Today, code can come to a user’s desk not only via a setup application executed from a company’s network server, but also from Internet via a Web page or an email. Recent experiences have shown that this can be quite dangerous.

NGWS provides an answer to this problem by controlling access to protected resources and operations. Code is trusted to varying degrees, depending on its identity and where it comes from

The following are the most notable functions of code access security:

From reading this list, it is evident that less trusted code will be prevented from calling highly trusted code because permissions of the less trusted code are enforced.

The two important points of code access security are verification of the type safety of managed code, and the permissions that are requested by the code. The minimum requirement for you to benefit from code –access security is to generate type-safe code.

 

Verification 0f Type Safety

The first step for the runtime in enforcing security restrictions on managed code is being able to determine whether the code is type safe. This matters because the runtime must be able to check the permissions of callers reliably.

The runtime checks permissions for all callers in the call stack to circumvent the security hole that is created when less trusted code calls highly trusted code. For this stacked-walking, the managed code must be verifiably type safe-every access to types is performed only in allowed ways.

The good news is that the C# code you write is type safe unless you want to write unsafe code. Both the IL and the metadata are inspected before the okay is given regarding the type safety of code.

 

Permissions

The next step is to work actively with permissions.The benefit from actively requesting permissions is that you know when you have proper permissions to perform your code’s actions ,or how to degrade gracefully when you don’t get them .Additionally ,you can prevent your code from getting extra permissions it wouldn’t need. Minimal permissions guarantee that your code will run on tightly restricted systems where code that requests too much permission without need will fail.

Here is the list of permissions:

  1. Required – Permissions that your code needs to run properly.
  2. Optional – Permissions that are not mandatory for the proper execution of your code, but that would be good to have.
  3. Refused – Permissions that you want to ensure your code is never granted – even if the security policy would allow it. You can use this to restrict potential vulnerabilities.

The interesting question is which permissions can be requested by code, which permissions are granted by the code’s identity. Only the first two belong to code-access security; the latter is tied to role-based security.

Therefore, the two kinds of code-access security permissions are

  1. Standard permissions
  2. Identity permissions

 

Standard Permissions

Securing access to resources that are exposed by the NGWS framework is taken care of be the code-access permissions. With those permissions, you gain either access to a protected resource, or the irght to perform a protected operation. Your code can demand any permission at runtime, and the runtime decides whether you code gets that permission.

 

EnvironmentPermission

This class defines access permissions to environment variables. Two types of access are possible: read-only access to the value of an environment variable, and write access. Write access includes permissions to create and delete environment variables.

File DialogPermission

Controls access to files based on the system file dialog.The user must authorize the file access via that dialoge

FileIOPermission

Three different typed of file I/O access may be specified:read,write and append.Read access includes access to file information;write access includes delete and overwrite;and append access limits you to appending-you are not allowed to read other bits

IsolatedStorage Permission

Controls access to the isolated storage(per user).Restrictions include allowed usage,storage quota size,expiration of data,and data retaining.

Reflection Permission

Controls the capability to read the type information of nonpublic members of types.In addition it controls the use of Reflection.Emit

Registry Permission

Reading creating and writing in the Registry are controlled with this permission. Each type of access must be specified separately for a list of values and keys.

Security Permission

Security Permission is a collection of simple permission flags that are used by the security system.You can control the execution of code,override of security checks,invocation of unmanaged code,verification skipping,serialization and more.

UIPermission

Defines the access to various aspects of the user interface,including the use of windows,access to events,as well as the use of the Clipboard.

 

Identity Permissions

Identity permission cannot be requested—they are granted based on evidence form the application code. This kind of permission is a secure way for NGWS t0 determine the identity of managed code, including its origin (possibly a Web site) and its publisher, based ob the signature of the code.

Publisher Identity Permission

The signature on an NGWS component provides proof of the software’s pablisher.

Strong Name Identity Permission

Defines the cryptographically strong name of a component.The strong name key and the simple name part comprise the identity.

Zone Identity Permission

Defines the zone from which the code originates.A URL can belong to only one zone.

Site Identity Permission

Permissions derived based on the Web site from which the code originates.

URL Identity Permission

Permissions derived based on the URL from which the code originates.

 

Role-Based Security

The system of role-based security might be already famniliar for you because the NGWS role-based security system is, to some degree, similar to the one found in COM+. However, there are some differences you need to be aware of, so read on.

The NGWS role-based security is modeled around a principal, which represents either a user, or an agent that is acting on behalf of a given user. An NGWS application makes security decisions based on either the principal’s identity, or its role membership.

So, what is a role? For example, a bank has clerks and managers. A clerk can prepare a loan application, but the manager must approve it. It doesn’t matter which instance of manager (principal) approves it, but he or she must be a member of the manager role.

In more technical terms, a role is a named set of users who share the same privileges. One principal can be a member of multiple roles and, therefore you can use role memberships to determine whether certain requested actions may be performed for a principal.

I have already mentioned briefly that a principal is not necessarily a user, but it can be also an agent. More generally, there are three kinds of principals:

 

Generic principals:

These represent unauthenticated users, as well as the roles available to them.

Windows principals:

Map to Windows users and their groups (roles). Impersonation (accessing a resource on another user’s behalf) is supported.

Custom principals:

Defined by an application. They can extend the basic notion of the identity and the roles that the principal is in. The restriction is that your application must provide an authentication module as well as the types that implement the principal.

How does it work for you in your application? NGWS provides you with the PrincipalPermission class, which provides consistency with code-access security. It enables the runtime to perform authorization in a way similar to code-access security checks, but you can directly access a principal’s identity information and perform role and identity checks in your code when you need to do so.