C Sharp Notes
C Sharp Notes
Study Notes
This documentation is an early release of the final Study Notes, which may change
substantially prior to final release, and is information of Satish Talim.
This document is provided for informational purposes only and Satish Talim makes
no warranties, either express or implied, in this document. Information in this
document is subject to change without notice.
The entire risk of the use or the results of the use of this document remains with the
user. Complying with all applicable international copyright laws is the responsibility
of the user.
Microsoft, Windows, Visual Basic, and Visual C++ are either registered trademarks
or trademarks of Microsoft Corporation in the U.S.A. and/or other countries.
Other product and company names mentioned herein may be the trademarks of their
respective owners.
Table of Contents
1. Introduction ............................................................................................................... 7
1.1 A New Platform? ....................................................................................................... 7
1.2 System Requirements................................................................................................ 7
1.3 Purpose of these Study Notes ..................................................................................... 7
1.4 Who can use these Study Notes? ................................................................................ 7
1.5 Updates to this document........................................................................................... 7
1.6 Recommended Sites on C# ........................................................................................ 7
1.7 My Workshops on C#................................................................................................. 8
1.8 Satish Talim?............................................................................................................ 8
1.9 Acknowledgements.................................................................................................... 8
2. C# Program Elements............................................................................................... 10
2.1 Overview of the .NET ................................................................................................10
2.2 C# and Java ............................................................................................................11
2.3 Our first C# Program – Hello, world............................................................................11
2.4 Naming Guidelines ...................................................................................................15
2.4.1 Namespaces ......................................................................................................15
2.4.2 Classes..............................................................................................................15
2.4.3 Methods ............................................................................................................15
2.4.4 Method Arguments .............................................................................................16
2.4.5 Interfaces ..........................................................................................................16
2.4.6 Class members...................................................................................................16
2.5 Automatic memory management ...............................................................................16
2.6 Comments ..............................................................................................................16
2.7 Blocks.....................................................................................................................19
2.8 Separation ..............................................................................................................19
2.9 Whitespace .............................................................................................................19
2.10 Keywords (74) .......................................................................................................19
2.11 Constants – const / readonly ...................................................................................20
2.12 Variables ...............................................................................................................20
2.13 Naming constants and variables ...............................................................................21
2.14 Escape sequences ..................................................................................................21
2.15 Statements and Expressions ....................................................................................22
2.15.1 Empty statement ..............................................................................................22
2.16 Types....................................................................................................................22
2.17 Predefined types ....................................................................................................25
2.18 Operators ..............................................................................................................30
2.18.1 checked and unchecked operators.......................................................................33
2.19 Operator overloading ..............................................................................................34
2.20 Program Control .....................................................................................................35
2.20.1 The if statement ...............................................................................................35
2.20.2 The switch statement ........................................................................................35
2.20.3 The while statement..........................................................................................37
2.20.4 The do statement .............................................................................................37
2.20.5 The for statement .............................................................................................37
2.20.6 The foreach statement .....................................................................................38
2.21 Console I/O ...........................................................................................................38
2.21.1 Console Input...................................................................................................38
2.21.2 Console Output.................................................................................................38
2.22 Array types............................................................................................................38
2.23 Calling methods – ref / out ......................................................................................43
Copyright Satish Talim 2001-2002, Study Notes. All Rights Reserved. iii
C# - CONSOLE APPLICATIONS
1. Introduction
1.7 My Workshops on C#
Here are some brief details of the various workshops on the C# language -
• Console Applications
• ADO.NET
• WinForms
• WebForms
• ASP.NET
• Web Services
• Window / Web Controls and Components
1.9 Acknowledgements
These Study Notes would not have been possible without the contribution, support and generous
help of many individuals.
I would like to acknowledge the help, right from my Java days, to my student and friend Amit
Karmakar, who always believes in me and inspires me to go on to greater heights. Amit is based
in Sydney, Australia and is a Web Developer for the Department of Education and Training, in
New South Wales, Australia.
Sunil Kelkar, who has always stood by me and helped me in going through these notes and
suggest changes. Sunil, is an independent consultant and works in Java and C#, in Pune.
I want to thank Rahul Waghmare, who made me look at and explore C# ! Rahul, is a graphics
artist by profession; works in Pune and likes to experiment with various computer languages –
Java, VC++, C#.
To all my students and people on the Internet who provided me with information/feedback, a big
thank-you.
I acknowledge all the authors, whose works I have referred. Here is a partial list.
The C# Programming book from Wrox Press was the first book I read on C#. I have been highly
influenced by this book and it probably reflects in these Study Notes.
Ben Albahari, author of C# Essentials for clarifying my doubt on byte array. The book gives a
very precise and to the point description of every element in the language.
Tom Archer, author of Inside C# for his valuable tips and help in clarifying many of my doubts in
C#. His book helped me understand the internal architecture of C#.
Anders Hejlsberg and Scott Wiltamuth for their excellent work, C# Language Reference.
Ashish Banerjee, Willy Denoyette, Sunil Gudipati, Shafeen Sarangi, Chris Maunder,
Mahesh Chand, Sudhakar Jalli, Pramod Singh and Saurabh Nandu for their excellent
articles on various aspects of C#.
Finally to Microsoft Corporation for giving us this C# language.
2. C# Program Elements
C# (pronounced "C sharp") is a simple, modern, object oriented, and type-safe (a reference
(when not null) is always guaranteed to point to an object that is of the type specified and that
has already been allocated on the heap. Also take note of the fact that a reference can be null)
programming language derived from C and C++. C# aims to combine the high productivity of
Visual Basic (C# is a Rapid Application Development Language like Visual Basic. It supports the
Drag- Drop and Build features of Visual Basic), the elegance of Java and the raw power of C++.
with developers using other .NET languages. This makes the .NET platform language-neutral –
i.e. modules written in C# would also be compatible with those written in VC++ and VB. When
executed, the components use the .NET runtime component for security and memory
management.
The basic premise is quite simple: all .NET programs are compiled to an intermediate language
(IL), rather than to native code which can be understood by the computer's processor. This IL
code is then compiled to native code either when the application is installed, or when the
application is run.
Microsoft has submitted a C# standard for ratification by ECMA (the European Computer
Manufacturers' Association). Once this standard is in place, vendors worldwide will be able to use
it to develop C# compilers that target their own platforms. C# applications cannot run without the
.NET runtime. As present the .NET platform has been released for Win NT/2000. Until the time a
separate runtime environment is released for .NET, you will have to install the Full .NET Software
Development Kit (SDK) on every machine you can to run your programs.
MS plans to release the .NET runtime for other platforms soon. Now if a .NET runtime has been
released for your platform then all the .NET programs will run on your platform. A .NET Platform
for Linux is expected soon. As of today, Windows is the only platform for which an IL runtime has
been developed so far, but this situation is expected to change. Even if Microsoft Corporation
doesn't produce the Macintosh, UNIX, and IBM runtimes, someone else probably will in order to
enjoy the reduced development costs associated with platform independence. A threat to Java?
• The using System; directive references a namespace (similar to a package in Java) called
System that is provided by the .NET runtime. System is the root of the .NET base class
namespace. using is very similar in concept to Java's import keyword. All it does is to tell
the compiler where it can look for unresolved class names. A “using” directive enables
unqualified use of the members of a namespace. This namespace contains the Console
class referred to in the Main method. Namespaces provide a hierarchical means of
organizing the elements of a class library. Unlike Java, in C# you cannot import a single
class, but the whole package. However, the components of a namespace name do not
have to map onto directories (remember in Java, a package has to physically map to a
directory). The “Hello, world” program uses Console.WriteLine as a shorthand for
System.Console.WriteLine. What do these identifiers denote? System is a namespace,
Console is a class defined in that namespace, and WriteLine is a static method (a static
method can access any static member within the class, but it cannot access an instance
member) defined in that class.
• In Main() the first character is a capital M.
• The Main function is a static member of the class Hello. Functions and variables are not
supported at the global level; such elements are always contained within type declarations
(e.g., class and strict declarations). The Main function can have any access modifier and
can be written as static void Main() too. Every method (Main method here) 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 its name with zero or more parameters
between parenthesis. The Main method is a static member of the class Hello. The static
qualifier makes the Main() method a class method, so that it can be invoked on its own,
without the creation of an instance of the class.
• Every C# application must have a method named Main defined in one of its classes. It
doesn't matter which class contains the method—you can have as many classes as you
want in a given application—as long as one class has a method named Main. It is the entry
point of your program, where the program control starts and ends. It is declared inside a
class or struct. It must be static. It can either be void or return an int. The Main method
is the place where you create objects and execute other methods. The Main method can
be written without parameters or with parameters.
• The Main method can be written without parameters or with parameters. The latter form
allows your program to read command-line arguments. There are three ways to declare
the Main method: (a) It can return void as in public static void Main() { ... } (b)
It can also return an int as in public static int Main() { ...; return 0; } (c) It
can also take arguments as in public static int Main(string[] args) { ...;
return 0; }
• The parameter of the Main method is a string array that represents the command-line
arguments used to invoke the program. Notice that, unlike C, C++, this array does not
include the name of the executable (exe) file.
• If you have zero or more than one Main() in a program, you can expect compiler errors.
This is shown in the example ManyMain.cs below:
using System;
class GF {
public GF() {
Console.WriteLine("In GF class");
}
public static void Main(string[] args) {
}
}
class F : GF {
public F() {
Console.WriteLine("In F class");
}
public static void Main(string[] args) {
}
}
class S : F {
public S() {
Console.WriteLine("In S class");
}
public static void Main(string[] args) {
S son = new S();
}
}
This program when compiled gives the error:
Microsoft (R) Visual C# Compiler Version 7.00.9030 [CLR version 1.00.2204.21]
Copyright (C) Microsoft Corp 2000. All rights reserved.
Test.cs(7,22): error CS0017: Program 'Test.exe' has more than one entry point
defined: 'GF.Main(string[])'
Test.cs(16,22): error CS0017: Program 'Test.exe' has more than one entry point
defined: 'F.Main(string[])'
Test.cs(26,22): error CS0017: Program 'Test.exe' has more than one entry point
defined: 'S.Main(string[])'
The designers of C# included a mechanism by which you can define more than one class with a
Main method. Why would you want to do that? One reason is to place test code in your classes.
You can then use the
/main:< className >
switch with the C# compiler to specify which class's Main method is to be used.
To compile this application such that the S.Main method is used as the application's entry point,
you'd use this switch:
csc MultipleMain.cs /main:S
Changing the switch to /main:F would then use the F.Main method.
The /main compiler option specified a class in which to look for a Main method. However, the
Main method has to be defined as follows:
public static void Main(){ }
• The “Hello, world” output is produced using a class library. C# does not itself provide a
class library. Instead, C# uses a common class library that is also used by other languages
such as Visual Basic and Visual C++.
• The program does not contain forward declarations. Forward declarations are never
needed in C# programs, as declaration order is not significant.
• C# programs use “.” as a separator in compound names such as Console.WriteLine.
• The program does not use #include to import program text. Dependencies between
programs are handled symbolically rather than with program text. This system eliminates
barriers between programs written in different languages. For example, the Console class
could be written in C# or in some other language.
Note: Observe the following -
a. Save the above Hello, world program as Hello.cs and compile as csc Hello.cs and run the
program as Hello
b. Compile the above program as csc hello.cs and run the program as hello or Hello
c. Save the above file as hello.cs and compile as csc Hello.cs or csc hello.cs and run the program
as Hello or hello
d. Save the above file as rubbish.cs and compile as csc rubbish.cs and run the program as
rubbish
e. Make the class as public and try a, b and c as above
For a to e above we get the same output i.e. Hello, world
Compiler Errors:
Here is what to expect when the compiler encounters syntax errors in your code. First, you'll see
the name of the current file being compiled, followed by the line number and column position of
the error. Next, you'll see the error code as defined by the compiler—for example, CS0234.
Finally, after the error code, you'll see a short description of the error. Many times, this
description will give you enough to make the error clear. You can also search for the error code in
the .NET Framework SDK Documentation (which is installed with the .NET Framework SDK) for a
more detailed description.
2.4.1 Namespaces
Use your company or product name, and employ mixed casing with an initial capital letter—for
example, JavaTech. If you're in the business of selling component software, create a top-level
namespace that is the same as your company name. Then for each product, create a nested
namespace with its embedded types, which will prevent name collision with other products. An
example of this can be found in the .NET Framework SDK: Microsoft.Win32. This strategy might
result in more long-winded names, but remember that the users of your code need only specify
the using directive to save typing. If your company is called Javatech, and you sell two products—
training and a software development—name your namespaces JavaTech.Training and
JavaTech.SoftwareDevelopment.
2.4.2 Classes
Because objects are supposed to be living, breathing entities that have abilities, name classes by
using nouns that describe the class's problem domain. In cases where the class is more generic
(that is, less specific to the problem domain) than that—for example, a type that represents an
SQL string—use Pascal casing - where the first character is capitalized.
2.4.3 Methods
Use Pascal casing - where the first character is capitalized - on all methods. Methods are meant to
act—they carry out work. Therefore, let the names of your methods depict what they do.
Examples of this are PrintInvoice and OpenDatabase.
2.4.5 Interfaces
Use Pascal casing (where the first character is capitalized) on all interfaces. It's common to prefix
interface names with a capital "I"—for example, IComparable. A common technique is naming
interfaces with adjectives.
2.6 Comments
The first line contains a comment:
// A "Hello World!" program in C#
Text following a comment // up to the end of the line is ignored by the compiler.
// Comments can appear on an independent line or as part of a statement too.
You can also comment a block of text by placing it between the characters /* and */, for
example:
/* A "Hello World!" program in C#.
This program displays the string "Hello World!" on the screen. */
The C-style comment can occur within a line and can span more than one line.
// This line is commented out // is extra.
The /doc option allows you to place documentation comments in an XML file.
This will create the XML file XMLsample.xml. as shown below:
<?xml version="1.0"?>
<doc>
<assembly>
<name>XMLSample</name>
</assembly>
<members>
<member name="T:SomeClass">
<summary>
Class level summary documentation goes here.</summary>
<remarks>
Longer comments can be associated with a type or member
through the remarks tag</remarks>
</member>
<member name="F:SomeClass.myName">
<summary>
Store for the name property</summary>
</member>
<member name="M:SomeClass.SomeMethod(System.String)">
<summary>
Description for SomeMethod.</summary>
<param name="s"> Parameter description for s goes here</param>
<seealso cref="T:System.String">
You can use the cref attribute on any tag to reference
a type or member and the compiler will check that the
reference exists. </seealso>
</member>
<member name="M:SomeClass.SomeOtherMethod">
<summary>
Some other method. </summary>
<returns>
Return results are described through the returns tag.</returns>
<seealso cref="M:SomeClass.SomeMethod(System.String)">
Notice the use of the cref attribute to reference a
specific method </seealso>
</member>
<member name="M:SomeClass.Main(System.String[])">
<summary>
The entry point for the application.
</summary>
<param name="args"> A list of command line arguments</param>
</member>
<member name="P:SomeClass.Name">
<summary>
Name property </summary>
<value>
A value tag is used to describe the property value</value>
</member>
</members>
</doc>
All the members of the assembly are denoted by <member> tags, and you can see how the
compiler has added the full name of the member as a name attribute. The T, F and M prefixes
denote types, fields and members respectively.
2.7 Blocks
C# code is organised into blocks (or sections). You specify the beginning and end of a block using
curly braces.
{ // C# block of code }
Every executable statement in C# will be within one or more blocks. It is a standard C#
programming style to identify different blocks with indentation. Every time you enter a new block,
you should indent your source code by preferably two spaces. When you leave a block, you
should de-indent by two spaces. Blocks define scope (or lifetime and visibility) of program
elements.
2.8 Separation
As in C, C# uses the semicolon to indicate the end of a statement.
2.9 Whitespace
C# is a freeform language. You don't have to indent anything to get it to work properly.
Whitespace characters are space, tab or newline. Appropriate use of white space makes your
programs easier to read and understand.
2.12 Variables
Variables are values that can change as much as needed during the execution of a program. One
reason you need variables in a program is to hold the results of a calculation. Hence, variables
are locations in memory in which values can be stored. They have a name, a type, and a value.
C# allows simple and compound variable declarations.
2.16 Types
A C# program is written by building new types (typically classes) and leveraging existing
types, either those defined in the C# language itself or imported from other libraries. Each type
contains a set of data (typically fields) and function members (typically methods), which combine
to form the modular units that are the key building blocks of a C# program.
Generally, you must vreate instances of a type to use that type. Those data members and
function members that require a type to be instantiated are called instance members. Data
members and function members that can be used on the type itself are called static members.
C# supports two major kinds of types (including both predefined types and user-defined types):
value types and reference types. Value types include simple types such as char, int, float etc.
are structs (simple type actually alias structs found in the System namespace). You can expand
the set of simplentypes by defining your own structs and enums. Reference types include class
types, interface types, delegate types, and array types.
Value types are allocated on the stack and Reference types are allocated on the heap.
Value types differ from reference types in that variables of the value types directly contain their
data, whereas variables of the reference types store references to objects. With reference types,
it is possible for two variables to reference the same object, and thus possible for operations on
one variable to affect the object referenced by the other variable. With value types, the variables
each have their own copy of the data, and it is not possible for operations on one to affect the
other. An example SRMEM.cs clarifies this point:
// SRMEM.cs
// Reference-type declaration
using System;
class PointR {
public int x, y;
}
// Value-type declaration
struct PointV {
public int x, y;
}
class Test {
static void Main() {
PointR a; // Local reference-type variable, uses 4 bytes of
// memory on the stack to hold address
PointV b; // Local value-type variable, uses 8 bytes of
// memory on the stack for x and y
a = new PointR(); // Assigns the reference to address of new
// instance of PointR allocated on the
// heap. The object on the heap uses 8
// bytes of memory for x and y, and an
// additional 8 bytes for core object
// requirements, such as storing the
// object's type & synchronization state
b = new PointV(); // Calls the value-type's default
// constructor. The default constructor
// for both PointR and PointV will set
// each field to its default value, which
// will be 0 for both x and y.
a.x = 7;
b.x = 7;
/*
}
}
At the end of the method the local variables a and b go out of
scope, but the new instance of a PointR remains in memory until
the garbage collector determines it is no longer referenced
*/
of type object. All data types, predefined and user-defined, inherit from the System.Object
class. The object data type is the type to and from which objects are boxed.
Developers can define new value types through enum and struct declarations, and can define new
reference types via class, interface, and delegate declarations.
C# is a strongly "Typed" language. Thus, all operations on variables are performed with
consideration of what the variable's "Type" is. There are rules that define what operations are
legal in order to maintain the integrity of the data you put in a variable. This is enforced at
compile time.
The conversions between types may be implicit or explicit. Implicit numeric conversions can be
performed automatically and are guaranteed to succeed and not lose information. The implicit
numeric conversions are:
temporary information such as synchronisation lock state or whether it has been fixed from
movement by the garbage collector. Each reference to a reference type instance uses four bytes
of storage.
The following sample shows how variables of type object can accept values of any data type and
how variables of type object can use methods on System.Object from the .NET Framework.
Example ObjClass.cs
using System;
public class MyClass1 {
public int i = 10;
}
public class MyClass2 {
public static void Main() {
object a;
a = 1; // an example of boxing
Console.WriteLine(a);
Console.WriteLine(a.GetType());
Console.WriteLine(a.ToString());
Console.WriteLine();
a = new MyClass1 ();
MyClass1 ref_MyClass1;
ref_MyClass1 = (MyClass1)a;
Console.WriteLine(ref_MyClass1.i);
}
}
Equals() is a very important method in Object class. The default implementation of Equals
supports reference equality only, but subclasses can override this method to support value
equality instead. In the case of value types, this method returns true if the two types are identical
and have the same value. The example ObjEquals.cs clarifies this point.
using System;
public class ObjEquals {
static void Main() {
Object o = new Object();
Object o1 = new Object();
String s1 = "Hello";
String s2 = "Hello";
String s3 = "World";
String s4 = s3;
int i1 = 1;
int i2 = 1;
int i3 = 2;
if (o == o1) // false
Console.WriteLine("Same Object Reference (o and o1)");
if (o.Equals(o1)) // false
Console.WriteLine("Same Object Content (o and o1)");
if (s1 == s2) // true
Console.WriteLine("Same Object Reference (s1 and s2)");
if (s1.Equals(s2)) // true
Console.WriteLine("Same Object Content (s1 and s2)");
if (s1 == s3) // false
Console.WriteLine("Same Object Reference (s1 and s3)");
if (s1.Equals(s3)) // false
Console.WriteLine("Same Object Content (s1 and s3)");
if (s3 == s4) // true
Console.WriteLine("Same Object Reference (s3 and s4)");
if (s3.Equals(s4)) // true
Console.WriteLine("Same Object Content (s3 and s4)");
if (i1.Equals(i2)) // true
Console.WriteLine("Same Object Content (i1 and i2)");
if (i1.Equals(i3)) // false
Console.WriteLine("Same Object Content (i1 and i3)");
}
}
The string type represents a string of Unicode characters. string is an alias for System.String
in the NGWS Framework. The string reference type requires a minimum of 20 bytes of memory.
Although string is a reference type, the equality operators (== and !=) are overloaded to
compare the values of string objects, not references. This makes testing for string equality more
intuitive.
string a = "hello";
string b = "hello";
Console.WriteLine( a == b ); // output: True -- same value
Console.WriteLine( (object)a == b ); // False -- different objects
The + operator concatenates strings:
string a = "good " + "morning";
The [] operator accesses individual characters of a string:
char x = "test"[2]; // x = 's';
String literals are of type string and can be written in two forms, quoted and @-quoted. Quoted
string literals are enclosed in double quotation marks ("):
"good morning" // a string literal
and can contain any character literal, including escape sequences:
string a = "\\\u0066\n"; // backslash, letter f, new line
@-quoted string literals start with @ and are enclosed in double quotation marks. For example:
@"good morning" // a string literal
The advantage of @-quoting is that escape sequences are not processed, which makes it easy to
write, for example, a fully qualified file name:
@"c:\Docs\Source\a.txt" // rather than "c:\\Docs\\Source\\a.txt"
To include a double quotation mark in an @-quoted string, double it:
@"""Ahoy!" cried the captain." // "Ahoy!" cried the captain.
The C# object class is very similar to Java's Object.
int n = 5;
string s = n.ToString(); // call object.ToString()
One can declare a string as follows:
string str = "Hello";
If you declare as follows:
string str = new string("Hello");
you will get a compiler warning.
The bool type is used to represent boolean values: values that are either true or false. The bool
keyword is an alias of System.Boolean.
System.Boolean Although Boolean values require only 1 bit (0 or 1), they
occupy 1 byte of storage since this is the minimum chunk addressing on most processor
architectures can work with. The inclusion of bool makes it easier for developers to write self-
documenting code, and helps eliminate the all-too-common C++ coding error in which a
developer mistakenly uses “=” when “==” should have been used. In C#, the example
int i = ...;
F(i);
if (i = 0) // Bug: the test should be (i == 0)
G();
is invalid because the expression i = 0 is of type int, and if statements require an expression of
type bool. No standard conversions exist between bool and other types. In particular, the bool
type is distinct and separate from the integral types, and a bool value cannot be used in place of
an integral value, or vice versa.
The byte type is an Unsigned 8-bit integer. The byte keyword is an alias of System.Byte
System.Byte.
m.Byte
The sbyte type is an Signed 8-bit integer. The sbyte keyword is an alias of System.SByte.
System.SByte
The char type is used to represent Unicode characters. A variable of type char represents a
single 16-bit Unicode character. The char keyword is an alias of System.Char.
ystem.Char Constants of the
char type can be written as character literals, hexadecimal escape sequence, or Unicode
representation. You can also cast the integral character codes. All of the following statements
declare a char variable and initialize it with the character X:
char MyChar = 'X'; // Character literal
char MyChar = '\x0058'; // Hexadecimal
char MyChar = (char)88; // Cast from integral type
char MyChar = '\u0058'; // Unicode
The decimal type is appropriate for calculations in which rounding errors are unacceptable.
Common examples include financial calculations such as tax computations and currency
conversions. The decimal type provides 28 significant digits. The decimal keyword is an alias of
System.Decimal It occupies 16 bytes of memory. If you want a numeric real literal to be treated
System.Decimal.
as decimal, use the suffix m or M, for example:
decimal myMoney = 300.5m;
Without the suffix m, the number is treated as a double, thus generating a compiler error.
The double keyword denotes a simple type that stores 64-bit floating-point values. Precision is
15-16 digits. The double keyword is an alias of System.Double.
System.Double By default, a real numeric literal
on the right-hand side of the assignment operator is treated as double. However, if you want an
integer number to be treated as double, use the suffix d or D, for example:
double x = 3D;
You can use F to denote single precision (i.e. 12.7F). U can be used to denote unsigned literals,
and L longs. The precise decimal type is denoted by an M suffix, as in 12.77M.
The float keyword denotes a simple type that stores 32-bit floating-point values. Precision is 7
digits. The float keyword is an alias of System.Single.
System.Single By default, a real numeric literal on the
right-hand side of the assignment operator is treated as double. Therefore, to initialize a float
variable use the suffix f or F, for example:
float x = 3.5F;
If you don't use the suffix in the previous declaration, you will get a compilation error because
you are attempting to store a double value into a float variable.
The int keyword denotes an integral type. It's a Signed 32-bit integer. The int keyword is an
alias of System.Int32.
System.Int32
The uint keyword denotes an integral type. It's an unsigned 32-bit integer. The int keyword is an
alias of System.UInt32
System.UInt32. You can declare and initialize a variable of the type uint like this
example:
uint myUint = 4294967290;
When an integer literal has no suffix, its type is the first of these types in which its value can be
represented: int, uint, long, ulong. In this example, it is uint. You can also use the suffix u or U,
like this:
uint myUint = 123U;
When you use the suffix U or u, the literal type is determined to be either uint or ulong according
to its size. In this example, it is uint.
The long keyword denotes an integral type. It's a signed 64-bit integer. The long keyword is an
alias of System.Int64.
System.Int64 You can declare and initialize a long variable like this example:
long myLong = 4294967296;
When an integer literal has no suffix, its type is the first of these types in which its value can be
represented: int, uint, long, ulong. In the preceding example, it is of the type long because it
exceeds the range of uint (see Integral Types Table for the storage sizes of integral types). You
can also use the suffix L or l with the long type like this:
long myLong = 4294967296L;
When you use the suffix L or l, the type of the literal integer is determined to be either long or
ulong according to its size. In the case it is long because it less than the range of ulong.
A common use of the suffix is with calling overloaded methods. Consider, for example, the
following overloaded methods that use long and int parameters:
public static void MyMethod(int i) {}
public static void MyMethod(long l) {}
Using the suffix L or l guarantees that the correct type is called, for example:
MyMethod(5); // Calling the method with the int parameter
MyMethod(5L); // Calling the method with the long parameter
You can use the long type with other numeric integral types in the same expression, in which case
the expression is evaluated as long (or bool in the case of relational or Boolean expressions). For
example, the following expression evaluates as long:
898L + 88
The ulong keyword denotes an integral type. It's an unsigned 64-bit integer. The ulong keyword
is an alias of System.UInt64.
System.UInt64
The short keyword denotes an integral type. It's a signed 16-bit integer. The short keyword is
an alias of System.Int16.
System.Int16
The ushort keyword denotes an integral type. It's an unsigned 16-bit integer. The ushort
keyword is an alias of System.UInt16.
System.UInt16
2.18 Operators
Operators are special symbols that are commonly used in expressions. C# has 50 built-in
operators. There are four basic kinds of operators: arithmetic, bitwise, relational and logical. In
addition, many operators can be overloaded by the user, thus changing their meaning when
applied to a user-defined type.
Anyone familiar with the operators of C will have no problem using C#'s. C# does add a few
operators of its own. In general, operators with the same precedence level will be evaluated from
left to right in the given expression. A student has suggested a mnemonic: "Ulcer Addicts Really
Like C A lot" where U is Unary, A is Arithmetic, R is Relational, L is logical, C is Conditional, A lot
is Assignment. The Associativity for Assignment, ==, != and ?: is from Right to Left, for all others
it's Left to Right.
Expressions are constructed from operands and operators. The operators of an expression
indicate which operations to apply to the operands. Examples of operators include +, -, *, /, and
new. Examples of operands include literals, fields, local variables, and expressions.
There are three types of operators:
• Unary operators. The unary operators take one operand and use either prefix notation (such
as –x) or postfix notation (such as x++).
• Binary operators. The binary operators take two operands and all use infix notation (such as x
+ y).
• Ternary operator. Only one ternary operator, ?:, exists. The ternary operator takes three
operands and uses infix notation (c? x: y).
The order of evaluation of operators in an expression is determined by the precedence and
associativity of the operators.
Certain operators can be overloaded. Operator overloading permits user-defined operator
implementations to be specified for operations where one or both of the operands are of a user-
defined class or struct type.
The following table summarizes all operators in order of precedence from highest to lowest:
Category Operators
Primary (x) x.y f(x) a[x] x++ x-- new
typeof sizeof checked unchecked
Unary + - ! ~ ++x --x (T)x
Multiplicative * / %
Additive + -
Equality == !=
Logical XOR ^
Logical OR |
Conditional OR ||
Conditional ?:
When an operand occurs between two operators with the same precedence, the associativity of
the operators controls the order in which the operations are performed:
• Except for the assignment operators, all binary operators are left associative, meaning that
operations are performed from left to right. For example, x + y + z is evaluated as (x + y) + z.
• The assignment operators and the conditional operator (?:) are right associative, meaning
that operations are performed from right to left. For example, x = y = z is evaluated as x = (y
= z).
Precedence and associativity can be controlled using parentheses. For example, x + y * z first
multiplies y by z and then adds the result to x, but (x + y) * z first adds x and y and then
multiplies the result by z.
An example using % operator – P3.cs
// P3.cs
using System;
class P3 {
public static void Main(String[] args) {
int number = 548;
int sum = 0;
while (true) {
sum = sum + number % 10;
number = number / 10;
if (number == 0) break;
}
Console.WriteLine("Sum = " + sum);
}
}
}
}
Output:
Unchecked output value: -2
In addition to the arithmetic operators, integral-type to integral-type casts can cause overflow
(for example, casting a long to an int) and are subject to checked or unchecked execution.
class Test {
static void Main(string[] args) {
switch (args.Length) {
case 0:
Console.WriteLine("No arguments were provided");
break;
case 1:
Console.WriteLine("One arguments was provided");
break;
default:
Console.WriteLine("{0} arguments were provided");
break;
}
}
}
switches on the number of arguments provided.
The switch expression must be an integer type (including char) or a string. The case labels have
to be constants. Unlike Java, you no longer fall through from case to case if you omit the break
and there’s code in the case. You will get a compiler error instead, and have to use a goto to
jump to the next (or previous) case. The end of a case statement must explicitly state where to
go next. If you have adjacent case labels, (i.e. No code in the case) then you can fall through.
Example: 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;
}
}
}
2.20.3 The while statement
A while statement conditionally executes a statement zero or more times – as long as a boolean
test is true.
using System;
class Test {
static int Find(int value, int[] arr) {
int i = 0;
while (arr[i] != value) {
if (++i > arr.Length)
throw new ArgumentException();
}
return i;
}
static void Main() {
Console.WriteLine(Find(3, new int[] {5, 4, 3, 2, 1}));
}
}
uses a while statement to find the first occurrence of a value in an array.
uses a for statement to write out the integer values 1 through 10.
class MyArray {
static void Main() {
int[] arr = new int[5]; // size fixed to 5, can’t be changed
for (int i = 0; i < arr.Length; i++) // index 0 for arrays
arr[i] = i * i;
// we can also do something like this
// int[] arr = new int[] {1,2,3,4,5};
// or
// int[] arr = {1,2,3,4,5};
// any attempt to access an array outside the bounds
// will result in a runtime error
for (int i = 0; i < arr.Length; i++)
Console.WriteLine("arr[{0}] = {1}", i, arr[i]);
}
}
creates a single-dimensional array of int values, initializes the array elements, and then prints
each of them out. The program output is:
arr[0] = 0
arr[1] = 1
arr[2] = 4
arr[3] = 9
arr[4] = 16
The type int[] used in the previous example is an array type. Array types are written using a
non-array-type followed by one or more rank specifiers.
C# supports two types of multidimensional arrays – rectangular and jagged. In rectangular
arrays, every row is the same length. A jagged array is simply an array of one-dimensional
arrays, each of which can be of different length if desired.
The example:
class Test {
static void Main() {
int[] a1; // single-dimensional array of int
int[,] a2; // 2-dimensional array of int
int[,,] a3; // 3-dimensional array of int
int[][] j2; // "jagged" array: array of (array of int)
int[][][] j3; // array of (array of (array of int))
}
}
shows a variety of local variable declarations that use array types with int as the element type.
Arrays are reference types, and so the declaration of an array variable merely sets aside space for
the reference to the array. Array instances are actually created via array initializers and array
creation expressions. The example
class Test {
static void Main() {
int[] a1 = new int[] {1, 2, 3};
int[,] a2 = new int[,] {{1, 2, 3}, {4, 5, 6}};
int[,,] a3 = new int[10, 20, 30];
int[][] j2 = new int[3][];
j2[0] = new int[] {1, 2, 3};
j2[1] = new int[] {1, 2, 3, 4, 5, 6};
j2[2] = new int[] {1, 2, 3, 4, 5, 6, 7, 8, 9};
}
}
shows a variety of array creation expressions. The variables a1, a2 and a3 denote rectangular
arrays, and the variable j2 denotes a jagged array. It should be no surprise that these terms are
based on the shapes of the arrays. Rectangular arrays always have a rectangular shape. Given
the length of each dimension of the array, its rectangular shape is clear. For example, the length
of a3’s three dimensions are 10, 20, and 30 respectively, and it is easy to see that this array
contains 10*20*30 elements.
In contrast, the variable j2 denotes a “jagged” array, or an “array of arrays”. Specifically, j2
denotes an array of an array of int, or a single-dimensional array of type int[]. Each of these
int[] variables can be initialized individually, and this allows the array to take on a jagged
shape. The example gives each of the int[] arrays a different length. Specifically, the length of
j2[0] is 3, the length of j2[1] is 6, and the length of j2[2] is 9.
It is important to note that the element type and number of dimensions are part of an array’s
type, but that the length of each dimension is not part of the array’s type. This split is made clear
in the language syntax, as the length of each dimension is specified in the array creation
expression rather than in the array type. For instance the declaration
int[,,] a3 = new int[10, 20, 30];
has an array type of int[,,] and an array creation expression of new int[10, 20, 30].
For local variable and field declarations, a shorthand form is permitted so that it is not necessary
to re-state the array type. For instance, the example
int[] a1 = new int[] {1, 2, 3};
can be shortened to
int[] a1 = {1, 2, 3};
without any change in program semantics.
It is important to note that the context in which an array initializer such as {1, 2, 3} is used
determines the type of the array being initialized. The example
class Test {
static void Main() {
short[] a = {1, 2, 3};
int[] b = {1, 2, 3};
long[] c = {1, 2, 3};
}
}
shows that the same array initializer can be used for several different array types. Because
context is required to determine the type of an array initializer, it is not possible to use an array
initializer in an expression context. The example
class Test {
static void F(int[] arr) {}
static void Main() {
F({1, 2, 3});
}
}
is not valid because the array initializer {1, 2, 3} is not a valid expression. The example can be
rewritten to explicitly specify the type of array being created, as in
class Test {
static void F(int[] arr) {}
static void Main() {
F(new int[] {1, 2, 3});
}
}
Example RA.cs
// RA.cs
using System;
class RA {
public static int Main() {
int[,] arr2; // 2D rectangular array of ints
arr2 = new int[5,5]; // create 5 by 5 array
for (int i=0; i<5; i++)
for (int j=0; j<5; j++)
arr2[i,j] = i*j;
// create a 2D array with implied size of 3 by 2
int[,] arr3 = new int[,] {
{1,2}, // first row
{4,5}, // second row
{7,8} // third row
};
// write out some elements
Console.WriteLine("{0}", arr2[2,2]);
Console.WriteLine("{0}", arr3[1,1]);
return 0;
}
}
Example JA.cs
//JA.cs
using System;
class JA {
public static void Main() {
int[][] arr4; // 2D jagged array of ints
arr4 = new int[4][]; // four rows in this array
arr4[0] = new int[5]; // first row has 5 elements
arr4[1] = new int[3]; // second row has 3 elements
arr4[2] = new int[4]; // third row has 4 elements
arr4[3] = new int[10]; // last row has 10 elements
arr4[1][1] = 3; // assign an element
}
}
In C#, arrays are actually objects. System.Array is the base type of all array types. You can use
the properties, and other class members that System.Array has. An example of this would be
using the Length property to get the length of an array. The following code assigns the length of
the numbers array, which is 5, to a variable called LengthOfNumbers:
int[] numbers = {1, 2, 3, 4, 5};
int LengthOfNumbers = numbers.Length;
The System.Array class provides many other useful methods/properties, such as methods for
sorting, searching, and copying arrays.
Getting Command-Line Input:
Example: NamedWelcome.cs
// Namespace Declaration
using System;
// Program start class
class NamedWelcome {
The second statement doesn't write anything until its 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 the CSharp Seminar!
int p;
public OutTest() {
outMethod(out p);
}
public static void Main(string[] args) {
OutTest t = new OutTest();
Console.WriteLine(t.p);
}
void outMethod(out int n) {
n = 3;
}
}
We will now use the ref keyword on a string variable and modify the previous RefTest.cs
program and call it RefTest2.cs
using System;
public class RefTest2 {
string s = "Satish";
public RefTest2() {
refMethod(ref s);
}
public static void Main(String[] args) {
RefTest2 t = new RefTest2();
Console.WriteLine(t.s);
}
void refMethod(ref string s) {
s = "Talim";
}
}
The output is Talim, a expected. If we now remove the ref keyword in the above program and
call the new program RefTest3.cs
using System;
public class RefTest3 {
string s = "Satish";
public RefTest3() {
refMethod(s);
}
public static void Main(String[] args) {
RefTest3 t = new RefTest3();
Console.WriteLine(t.s);
}
void refMethod(string s) {
s = "Talim";
}
}
The output is Satish. Therefore, if a parameter is declared for a method without ref or out, the
parameter can have a value associated with it. That value can be changed in the method, but the
changed value will not be retained when control passes back to the calling procedure. By using a
method parameter keyword (ref, out), you can change this behavior.
using System;
class Log {
public Log(string fileName) {
// Open fileName and seek to end.
}
public void WriteEntry(string entry) {
Console.WriteLine(entry);
}
public void WriteEntry(int resourceId) {
Console.WriteLine
("Retrieve string using resource id and write to log");
}
}
2.23.2 Variable Method Parameters - params
You can specify a variable number of method parameters by using the params keyword and by
specifying an array in the method's argument list.
// VarArgsApp.cs
using System;
class MyPoint {
public int x;
public int y;
public MyPoint(int x, int y) {
this.x = x;
this.y = y;
}
}
class Chart {
public void DrawLine(params MyPoint[] p) {
Console.WriteLine("\nThis method would print a line " +
"along the following points:");
for (int i = 0; i < p.GetLength(0); i++) {
Console.WriteLine("{0}, {1}", p[i].x, p[i].y);
}
}
}
class VarArgsApp {
public static void Main() {
MyPoint p1 = new MyPoint(5,10);
MyPoint p2 = new MyPoint(5, 15);
MyPoint p3 = new MyPoint(5, 20);
Chart chart = new Chart();
chart.DrawLine(p1, p2, p3);
}
}
GetLength(0) returns the number of elements in the first dimension of the Array. The DrawLine
method tells the C# compiler that it can take a variable number of Point objects. At run time, the
method then uses a simple for loop to iterate through the Point objects that are passed, printing
each one.
Beta 1 of the .NET framework includes built-in exception handlers for common exception types
such as divide-by-zero exception. Try this code, in the source file DBZ.cs
using System;
public class DBZ {
public static void Main(string[] args) {
double a = 15.0;
double b = 0.0;
double result = a/b;
Console.WriteLine("Result is {0}", result);
}
}
You will get the output:
Result is Infinity
Modifying the program to DBZ2.cs as shown below, makes no difference to the output:
using System;
public class DBZ2 {
public static void Main(string[] args) {
double a = 15.0;
double b = 0.0;
double result = a/b;
try {
Console.WriteLine("Result is {0}", result);
} catch (Exception e) {
Console.WriteLine("Exception occured: " + e);
}
}
}
A catch block which will catch any exception is called a general catch clause. These blocks don’t
specify an exception variable, and can be written like this:
try {
// code which may fail
} catch {
// handle error
}
A try block can also have a finally block associated with it. If a finally block exists, it will be
executed before the try block is completed, and after any possible exceptions are caught in the
catch clause. This will happen no matter how control leaves the try, whether it is due to normal
termination, to an exception occurring, a break or continue (or goto) statement, or a return. A
finally block can occur with or without catch blocks. It is an error to transfer control out of a
finally block using break, continue, return or goto.
The point to note for Java programmers is: Not only is the argument to catch optional, but the
entire catch clause itself is optional. Also, C# has no throws keyword. By not requiring explicit
exception declarations in method signatures, C# values short-term programmer convenience over
program safety and correctness.
public void ReadFile() {
try {
// code which may fail
} finally {
// close the file
}
}
CoreException is C#’s equivalent of Error in Java and you are not expected to handle it. You
can make up your own exception classes by deriving from one of the various exception classes
existing. See MyException.cs below:
using System;
class VowelException : Exception {}
class BlankException : Exception {}
class ExitException : Exception {}
class MyException {
public static void Main(String[] args) {
bool finished = false;
do {
try {
processUserInput();
} catch (VowelException x) {
Console.WriteLine("VowelException occured: " + x);
} catch (BlankException y) {
Console.WriteLine("BlankException occured: " + y);
} catch (ExitException z) {
Console.WriteLine("ExitException occured: " + z);
// Using StackTrace property of Exception class
Console.WriteLine("Trace: " + z.StackTrace);
finished = true;
} finally {
Console.WriteLine("This is the finally clause.");
}
} while(!finished);
}
2.25 Namespaces
Namespaces are a great way of categorizing your types and classes to avoid name collisions.
When a language develops, many third party components are available. All these parties try to
give meaningful names to their classes like the Math class, or InterestCalculator etc. We as
developers end up in misery due to this. Just consider an example, I am developer who is writing
up a shopping cart class. I am using third party components. I purchase two such components,
one calculates discount rates for retail customers and other calculates discount rates for
wholesale customers. The problem arises when both these components have a class called
Discount. Now in my shopping cart class how do I use both these classes unambiguously?
eg.
Discount d1 = new Discount();
//Which component does the compiler here refer to? Reseller discount or Wholesaler Discount?
int discount = d1.Cal(45.78) ;
Some compilers will complain others might compile and use any of the components as they wish!
This problem has been identified and solved by Microsoft on the .NET Platform with the use of
Namespaces. The relationship between Namespaces and classes can directly be compared to the
relationship between Files and Folders. Files actually contain data, while Folders are used to just
manage and logically arrange Files. Folders can also contain many files and sub folders within
them.
In the same way, Classes actually contain data and Namespaces are used to logically arrange
classes. Namespaces can also contain many other Namespaces and classes. This concept may
seem similar to the Package - Class relationship used in Java. However, one point of caution, you
DO NOT need to create folders to store classes into a Namespace. Just defining the Namespace
keyword above the class definition is enough.
Again you might say that there are two companies whose abbreviated names might be XYZ and if
both companies develop the Discount class then, even if they used namespaces their full names
would be XYZ.Discount class? Which will lead to the problem that we faced earlier! This is very
true since Namespaces only help to extend the name of a class to make it unique. Hence,
Microsoft is encouraging companies to use their full names to develop components and not
abbreviations, also if possible, the department names as well as the team names should be used .
Therefore, a company like American Business Company should use its full name.
AmericanBusinessCompany.It.DotNet.Discount instead of using ABC.Discount. This will help to
solve the naming conflict.
C# programs are organized using namespaces. Namespaces provide a way to group classes, by
providing an extra level of naming beyond the class name. To use namespaces all you have to do
is place the namespace definition above your class definition. The Dot '.' is used to denote and
access classes within the namespace.
Earlier, we presented a “Hello, world” program. We’ll now rewrite this program (as HelloMsg.cs) in
two pieces: a HelloMessage component that provides messages and a console application that
displays messages.
First, we’ll provide a HelloMessage class in a namespace. What should we call this namespace?
By convention, developers put all of their classes in a namespace that represents their company
or organization. We’ll put our class in a namespace named JavaTech.CSharp.Introduction.
namespace JavaTech.CSharp.Introduction {
public class HelloMessage {
public string GetMessage() {
return "Hello, world";
}
}
}
Namespaces are hierarchical, and the name JavaTech.CSharp.Introduction is actually
shorthand for defining a namespace named JavaTech that contains a namespace named CSharp
that itself contains a namespace named Introduction, as in:
namespace JavaTech {
namespace Csharp {
namespace Introduction
{....}
}
}
Next, we’ll write a console application that uses the HelloMessage class. We could just use the
fully qualified name for the class – JavaTech.CSharp.Introduction.HelloMessage – but this
name is quite long and unwieldy. An easier way is to use a “using” directive, which makes it
possible to use all of the types in a namespace without qualification.
using JavaTech.CSharp.Introduction;
class Hello {
static void Main() {
HelloMessage m = new HelloMessage();
System.Console.WriteLine(m.GetMessage());
}
}
Note that the two occurrences of HelloMessage are shorthand for
JavaTech.CSharp.Introduction.HelloMessage.
C# also enables the definition and use of aliases. Such aliases can be useful in situation in which
name collisions occur between two libraries, or when a small number of types from a much larger
namespace are being used. Our example can be rewritten using aliases as:
using MessageSource = JavaTech.CSharp.Introduction.HelloMessage;
class Hello {
static void Main() {
MessageSource m = new MessageSource();
System.Console.WriteLine(m.GetMessage());
}
}
Namespaces are often related to assemblies (which we will cover later).
Namespace elements cannot be explicitly declared as private or protected. Only public
members are allowed in a namespace. Internal is the default. The public keyword must
be explicitly specified.
The following sample generates error CS1527:
namespace bad {
private class foo1 {} // CS1527
protected class foo2 {} // CS1527
class foo3 { // allowed. This is internal
static void Main() {}
}
}
Inside a namespace, the compiler only accepts classes, structs, unions, enums, interfaces, and
delegates.
The following sample generates error CS0116:
namespace x {
int xx; // CS0116
}
Even if you do not explicitly declare a namespace, a default namespace is created. This unnamed
namespace, sometimes called the global namespace, is present in every file. Any identifier in the
global namespace is available for use in a named namespace. Namespaces implicitly have public
access and this is not modifiable. An example of this will make it clear. Create a program, NSP.cs,
compile and run:
// NSP.cs
using System;
namespace Test {
public class Tst {
public static void Main(string[] args) {
GTst t = new GTst();
}
}
}
class GTst { // this is in the global namespace
public GTst() {
Console.WriteLine("GTst called...");
}
}
The using keyword has two uses:
• Create an alias for a namespace (a using alias).
• Permit the use of types in a namespace, such that, you do not have to qualify the use of a
type in that namespace (a using directive).
Java programmers should note that, we could use namespace first, followed by using or vice-
versa.
The only purpose of the using command in this context is to save you typing and make your code
simpler. It does not, for example, cause any other code or libraries to be added to your project. If
your code uses base classes, you need to ensure separately that the compiler knows which
assemblies to look in for the classes (/r switch in the compiler).
Although you can't specify a class in a using directive, the following variant of the using directive
does enable you to create aliases for classes:
using alias = class
Using this form of the using directive, you can write code like the following:
using output = System.Console;
class HelloWorld {
public static void Main() {
output.WriteLine("Hello, World");
}
}
This gives you the flexibility to apply meaningful aliases to classes that are nested several layers
deep in the .NET hierarchy, thus making your code a little easier to both write and maintain.
The using keyword cannot be declared inside a class.
// NB.cs
using System;
namespace A {
public class NB {
public NB() {
Console.WriteLine("Using A.NB class");
}
static void Main() {
}
}
}
// Test.cs
using System;
using A;
public class Test {
public static void Main(string[] args) {
NA o1 = new NA();
NB o2 = new NB();
}
}
Both NA.cs and NB.cs compile. However, when I compile Test.cs, I get an error:
Test.cs(3,7): error CS0234: The type or namespace name 'A' does not exist in the
class or namespace ''
To overcome this error use the .NET BETA 1 instructions for DLLs -
csc /t:library na.cs
csc /t:library nb.cs
csc test.cs /r:na.dll /r:nb.dll
• All types – including value types – can be treated like objects and derive from Object. It’s
possible to call Object methods on any value, even values of “primitive” types such as int.
• All the types have fixed sizes, and will be the same size on any system.
• In C#, each of the predefined types is a shorthand (alias) for a system-provided type.
• The decimal type is appropriate for calculations in which rounding errors are unacceptable.
Common examples include financial calculations such as tax computations and currency
conversions. The decimal type provides 28 significant digits.
• Equals() is a very important method in Object class. The default implementation of Equals
supports reference equality only, but subclasses can override this method to support value
equality instead. In the case of value types, this method returns true if the two types are
identical and have the same value.
• string is a reference type; the equality operators (== and !=) are overloaded to compare the
values of string objects, not references.
• The + operator concatenates strings. The [] operator accesses individual characters of a string
• String literals are of type string and can be written in two forms, quoted and @-quoted.
• If you declare a string as string str = new string("Hello"); you will get a compiler
warning.
• In C#, many operators can be overloaded by the user.
• Integer arithmetic overflow either throws an OverflowException or discards the most
significant bits of the result. Integer division by zero always throws a DivideByZeroException.
• Floating-point arithmetic overflow or division by zero never throws an exception, because
floating-point types are based on IEEE 754 and so have provisions for representing infinity
and NaN (Not a Number).
• Decimal arithmetic overflow always throws an OverflowException. Decimal division by zero
always throws a DivideByZeroException.
• When integer overflow occurs, what happens depends on the execution context, which can be
checked or unchecked. In a checked context, an OverflowException is thrown. In an
unchecked context, the most significant bits of the result are discarded and execution
continues.
• The switch expression must be an integer type (including char) or a string. The case labels
have to be constants. Unlike Java, you no longer fall through from case to case if you omit the
break and there’s code in the case. You will get a compiler error instead, and have to use a
goto to jump to the next (or previous) case. If you have adjacent case labels, (i.e. No code in
the case) then you can fall through.
• A foreach statement lets you iterate over the elements in arrays and collections.
• C# supports two types of multidimensional arrays – rectangular and jagged. In rectangular
arrays, every row is the same length. A jagged array is simply an array of one-dimensional
arrays, each of which can be of different length if desired.
• C# provides two ways of passing a value type into a method and modify it using the ref and
out keywords. The ref keyword tells the C# compiler that the arguments being passed point
to the same memory as the variables in the calling code. That way, if the called method
modifies these values and then returns, the calling code's variables will have been modified.
When you use the ref keyword, you must initialize the passed arguments before calling the
method.
• The only difference between the ref keyword and the out keyword is that the out keyword
doesn't require the calling code to initialize the passed arguments first.
• You can specify a variable number of method parameters by using the params keyword and by
specifying an array in the method's argument list.
• In C#, what you throw has to be a System.Exception object, or something derived from
System.Exception. Beta 1 of the .NET framework includes built-in exception handlers for
common exception types such as divide-by-zero exception.
• A catch block which will catch any exception is called a general catch clause. These blocks
don’t specify an exception variable.
• If a finally block exists, it will be executed before the try block is completed, and after any
possible exceptions are caught in the catch clause. A finally block can occur with or without
catch blocks.
• The argument to catch is optional, but the entire catch clause itself is optional. Also, C# has
no throws keyword.
• Namespace elements cannot be explicitly declared as private or protected. Only public
members are allowed in a namespace (even internal and default are allowed).
• Even if you do not explicitly declare a namespace, a default namespace is created. This
unnamed namespace, sometimes called the global namespace, is present in every file. Any
identifier in the global namespace is available for use in a named namespace. Namespaces
implicitly have public access and this is not modifiable.
• Java programmers should note that, we could use namespace first, followed by using or vice-
versa.
• The using keyword has two uses - Create an alias for a namespace or permit the use of types
in a namespace, such that, you do not have to qualify the use of a type in that namespace.
• We are not allowed to use the same using directive more than once in our program ie. we
cannot say:
using System;
using System;
• You can think of assemblies are something similar to jar files in Java.
Software objects are modeled after real world objects in that, they too, have attributes and
behaviour. A software object maintains its attributes in variables and implements its behaviour
with methods.
Definition: An object is a software bundle of variables and related methods.
You can represent real-world objects in programs using software objects. You might want to
represent a real-world bicycle as a software object within an electronic exercise bike. However,
you can also use software objects to "objectify" abstract concepts. For example, "event" is a
common object used in GUI window systems to represent the event when a user presses a mouse
button or types a key on the keyboard.
Everything that the software object knows (attribute) and can do (behaviour) is expressed by the
variables and methods within that object. A software object that modeled your real-world bicycle
would have variables that indicated the bicycle's current state: its speed is 10 mph, its pedal
cadence is 90 rpm, and its current gear is the 5th gear. These variables and methods are formally
known as instance variables and instance methods to distinguish them from class variables and
class methods. The software bicycle would also have methods to brake, change the pedal cadence
and change gears. (The bike would not have a method for changing the speed of the bicycle as
the bike's speed is really just a side-effect of what gear it's in, how fast the rider is pedaling and
how steep the hill is).
Anything that an object does not know or cannot do is excluded from the object. For example,
your bicycle (probably) doesn't have a name, and it can't run, bark or fetch. Thus, there are no
variables or methods for those states and behaviours.
As you can visualise, the object's variables make up the center or nucleus of the object and the
methods surround and hide the object's nucleus from other objects in the program. Packaging an
object's variables within the protective custody of its methods is called encapsulation. Typically,
encapsulation is used to hide unimportant implementation details from other objects. When you
want to change gears on your bicycle, you don't need to know how the gear mechanism works;
you just need to know which lever to move. Thus, the implementation can change at any time
without changing other parts of the program.
Software objects interact and communicate with each other by sending messages to each other.
When object A wants object B to perform one of its methods, object A sends a message to object
B.
Sometimes the receiving object needs more information so that it knows exactly what to do - for
example, when you want to change gears on your bicycle, you have to indicate which gear you
want. This information is passed along with the message as parameters. The message parameters
are actually the parameters to a method. The method's return type is the object's response to the
message.
Three components comprise a message:
• the object to whom the message is addressed (bicycle)
• the name of the method to perform (change gears)
• any parameters needed by the method (to a higher gear)
These three components are enough information for the receiving object to perform the desired
method. No other information or context is required.
The Benefit of Messages:
Everything an object can do is expressed through its methods, so message passing supports all
possible interactions between objects.
Objects don't need to be in the same process or even on the same machine to send and receive
messages back and forth to each other.
Objects provide the benefit of modularity and information hiding. Classes provide the benefit of
reusability. Bicycle manufacturers reuse the same blueprint repeatedly to build many bicycles.
Software programmers use the same class repeatedly to create many objects.
When you write a C# program, you design and construct a set of classes. Then, when your
program runs, instances of those classes are created and discarded as needed. Your task, as a C#
programmer, is to create the right set of classes to accomplish what your program needs to
accomplish.
The C# environment comes with a library of classes that implement a lot of the basic behaviour
you need - not only for basic programming tasks (classes to provide basic math functions, arrays,
strings, and so on), but also for graphics and networking behaviour. A class library is a set of
classes.
Because each instance of a class can have different values for its variables, each variable is called
an instance variable.
Class declarations are used to define new reference types. C# supports single inheritance only,
but a class may implement multiple interfaces.
Class members can include constants, fields, methods, properties, indexers, events, operators,
constructors, destructors, and nested type declaration.
The differentiation between classes and objects is often the source of some confusion. In the real
world, it's obvious that classes are not themselves the objects that they describe - a blueprint of a
bicycle is not a bicycle. However, it's a little more difficult to differentiate classes and objects in
software. This is partially because software objects are electronic models of real-world objects or
abstract concepts in the first place. People use the term "object" inconsistently and use it to refer
to both classes and instances.
So far, a class's methods and variables don't exist yet. You must create an instance from the
class before you can call the methods and before the variables can have any values. In
comparison, an object's methods and variables actually exist and you can use it. You can send
the object a message and it will respond by performing the method and perhaps modifying the
values of the variables.
An instance of a class is another word for an actual object. If the class is the general
representation of an object, an instance is its concrete representation.
However, subclasses are not limited to the state and behaviours provided to them by their
superclass. Subclasses can add variables and methods to the ones they inherited from the
superclass. Tandem bicycles have two seats and two sets of handle bars; some mountain bikes
have an extra set of gears with a lower gear ratio.
Subclasses can also override inherited methods and provide specialised implementations for those
methods. For example, if you had a mountain bike with an extra set of gears, you would override
the "change gears" method so that the rider could actually use those new gears.
You are not limited to just one layer of inheritance - the inheritance tree, or class hierarchy, can
be as deep as needed. Methods and variables are inherited down through the levels. The further
down in the hierarchy a class appears, the more specialised its behaviour.
At the top of the C# class hierarchy is the class Object; all classes inherit from this one
superclass. Object is the most general class in the hierarchy; it defines methods inherited by all
the classes in the C# class hierarchy.
You can think of a class hierarchy as defining very abstract concepts at the top of the hierarchy
and those ideas becoming more concrete the farther down the chain of superclasses you go.
What if your class defines entirely new behaviour, and isn't really a subclass of another class?
Your class can also inherit directly from Object, which still allows it to fit neatly into the C# class
hierarchy. In fact, if you create a class definition that doesn't indicate its superclass in the first
line, C# automatically assumes that you are inheriting from Object. The MotorCycle class you
created, inherited from Object.
Each C# class can have only one superclass i.e. single inheritance.
The Benefit of Inheritance:
• Subclasses provide specialised behaviours from the basis of common elements provided by
the superclass. With inheritance, programmers can reuse the code in the superclass many
times.
• Programmers can implement superclasses that define "generic" behaviours (called abstract
classes). The essence of the superclass is defined and may be partially implemented but
much of the class is left undefined and non- implemented. Other programmers fill in the
details with specialised subclasses.
//Account.cs
using System;
class Account {
private double balance; // data member
public Account(){
balance = 0.0;
}
public Account(double amt){
balance = amt;
}
public bool Deposit(double amt){
//deposit cash
if (amt <= 0.0)
return false;
else {
balance += amt;
return true;
}
}
public bool Withdraw(double amt){
//withdraw cash
if ((balance-amt) < 0.0)
return false;
else
balance -=amt;
return true;
}
public double QueryBalance(){
return balance;
}
}
public class Test {
public static void Main(String[] args){
Account a1 = new Account();
Account a2 = new Account(3000.00);
}
}
4.1.1 Class modifiers
A class-declaration may optionally include a sequence of class modifiers:
class-modifier:
new
public
protected
internal
private
abstract
sealed
It is an error for the same modifier to appear multiple times in a class declaration.
The new modifier is only permitted on nested classes. It specifies that the class hides an inherited
member by the same name.
The public, protected, internal, and private modifiers control the accessibility of the class.
Depending on the context in which the class declaration occurs, some of these modifiers may not
be permitted.
The abstract and sealed modifiers are discussed in the following sections.
The sealed modifier is primarily used to prevent unintended derivation, but it also enables certain
run-time optimizations.
The sealed modifier cannot be used on methods.
Example:
using System;
sealed class MyPoint {
public MyPoint(int x,int y) {
this.x =x;
this.y =y;
}
private int X;
public int x {
get {
return this.X;
}
set {
this.X =value;
}
}
private int Y;
public int y {
get {
return this.Y;
}
set {
this.Y = value;
}
}
}
class SealedApp {
public static void Main() {
MyPoint pt = new MyPoint(6,16);
Console.WriteLine("x = {0}, y = {1}", pt.x, pt.y);
}
}
Note that we used the private access modifier on the internal class members X and Y. Using the
protected modifier would result in a warning from the compiler because of the fact that protected
members are visible to derived classes and, as you now know, sealed classes don't have any
derived classes.
}
}
is in error because A depends on B.C (its direct base class), which depends on B (its immediately
enclosing class), which circularly depends on A.
Note that a class does not depend on the classes that are nested within it. In the example
class A {
class B: A {}
}
B depends on A (because A is both its direct base class and its immediately enclosing class), but A
does not depend on B (since B is neither a base class nor an enclosing class of A). Thus, the
example is valid.
It is not possible to derive from a sealed class. In the example
sealed class A {}
class B: A {} // Error, cannot derive from a sealed class
class B is in error because it attempts to derive from the sealed class A.
File Assembly2.cs:
// compile with /target:exe /reference:Assembly1.dll
public class TestAccess {
public static void Main() {
// error, BaseClass not visible outside assembly
BaseClass myBase = new BaseClass();
}
}
class-body:
{ class-member-declarationsopt }
• Constructors and destructors must have the same name as the immediately enclosing class.
All other members must have names that differ from the name of the immediately enclosing
class.
• The name of a constant, field, property, event, or type must differ from the names of all other
members declared in the same class.
• The name of a method must differ from the names of all other non-methods declared in the
same class. In addition, the signature of a method must differ from the signatures of all other
methods declared in the same class.
• The signature of an indexer must differ from the signatures of all other indexers declared in
the same class.
• The signature of an operator must differ from the signatures of all other operators declared in
the same class.
The inherited members of a class are specifically not part of the declaration space of a class.
Thus, a derived class is allowed to declare a member with the same name or signature as an
inherited member (which in effect hides the inherited member).
4.1.3.2 Signature
Methods, constructors, indexers, and operators are characterized by their signatures:
The signature of a method consists of the name of the method and the number, modifiers, and
types of its formal parameters. The signature of a method specifically does not include the return
type.
The signature of a constructor consists of the number, modifiers, and types of its formal
parameters.
The signature of an indexer consists of the number and types of its formal parameters. The
signature of an indexer specifically does not include the element type.
The signature of an operator consists of the name of the operator and the number and types of its
formal parameters. The signature of an operator specifically does not include the result type.
4.1.3.3 Constructors
A constructor is a method, which is called when an object of a class type is constructed, and is
usually used for initialisation. A constructor method has several characteristics:
• It has the same name as the class name
• It has no return type
• It does not return any value.
Java Programmers should note that: If you attempt to prefix a constructor with a type, the
compiler will emit an error stating that you cannot define members with the same names as the
enclosing type.
In the example VarTest.cs:
class VarTest {
public VarTest() {
}
static void Main() {
VarTest t;
}
}
If you compile the above program, the C# compiler will warn you that the variable t has been
declared but is never used in the application.
Constructor Initializers:
All C# object constructors—with the exception of the System.Object constructors—include an
invocation of the base class's constructor immediately before the execution of the first line of the
constructor. These constructor initializers enable you to specify which class and which constructor
you want called. This takes two forms:
• An initializer of the form base(.) enables the current class's base class constructor—that is,
the specific constructor implied by the form of the constructor called—to be called.
• An initializer taking the form this(.) enables the current class to call another constructor
defined within itself. This is useful when you have overloaded multiple constructors and want
to make sure that a default constructor is always called.
using System;
class A {
public A() {
Console.WriteLine("A");
}
}
class B : A {
public B() : base() {
Console.WriteLine("B");
}
}
class BaseDefaultInitializerApp {
public static void Main() {
B b = new B();
}
}
Another example, is when I have two classes: A and B. This time, class A has two constructors,
one that takes no arguments and one that takes an int. Class B has one constructor that takes an
int. The problem arises in the construction of class B. How do I ensure that the desired class A
constructor will be called? By explicitly telling the compiler which constructor I want called in the
initalizer list, as below:
using System;
class A {
public A() {
Console.WriteLine("A");
}
public A(int foo) {
Console.WriteLine("A = {0}", foo);
}
}
class B : A {
public B(int foo) : base(foo) {
Console.WriteLine("B = {0}", foo);
}
}
class DerivedInitializer2App {
public static void Main() {
B b = new B(42);
}
}
You can use overloading to create several constructors for a class; which one will get called
depends on the arguments you give to new.
It's to be noted that:
• Constructors are not inherited. Thus, a class has no other constructors than those that are
actually declared in the class. If a class contains no constructor declarations, a default
constructor is automatically provided. The default constructor simply invokes the
parameterless constructor of the direct base class. If the direct base class does not have an
accessible parameterless constructor, an error occurs.
• If you write a class with only one constructor with parameters and use it while creating an
object, then a default constructor need not be given.
• If you write a class with only one constructor with parameters and try to create an object
using a default constructor (not given by you) then the program does not compile. As in Java,
it's advisable to write a default constructor for every class you write.
• Constructors are usually public, but can also be private or protected. When a class declares
only private constructors, it is not possible for other classes to derive from the class or create
instances of the class (an exception being classes nested within the class). Private
constructors are commonly used in classes that contain only static members. For example:
public class Trig {
private Trig() {} // Prevent instantiation
public const double PI = 3.14159265358979323846;
public static double Sin(double x) {...}
public static double Cos(double x) {...}
public static double Tan(double x) {...}
}
The Trig class provides a grouping of related methods and constants, but is not intended to be
instantiated. It therefore declares a single private constructor. Note that at least one private
constructor must be declared to suppress the automatic generation of a default constructor
(which always has public access).
In the Tree.cs program below – Java programmers should note that the name of the program can
be anything you want (in any case too). All classes can be public or otherwise. Also note that in
the program there can be only one and only one Main() method.
using System;
public class GF {
public GF() {
Console.WriteLine("In GF");
}
}
public class F : GF { // : is similar to extends in Java
public F() {
Console.WriteLine("In F");
}
}
public class S : F {
public S() {
Console.WriteLine("In S");
}
public static void Main(String[] args){
S son = new S();
}
}
4.1.3.4 Calling Base Class Constructors
public class CheckAccount : Account {
public CheckAccount(double amt) : base(amt) {
}
}
Here, if a CheckAccount is created with a double being passed in to the constructor, that double is
passed on to the base class constructor. The compiler sees base(amt) as meaning, “Pass amt
through to the base class constructor which takes one double as an argument. In this case,
Account has a suitable constructor, so the compiler can find and use it.
static B() {
Console.WriteLine("Init B");
}
public static void G() {
Console.WriteLine("B.G");
}
}
is guaranteed to produce the output:
1
Init A
Init B
B.G
2
because the static constructor for the class A must execute before the static constructor of the
class B, which derives from it.
In the example SRO.cs below, the screen resolution fields are static and read-only, and there’s a
static constructor.
using System;
class GraphicsPackage {
public static readonly int ScreenWidth;
public static readonly int ScreenHeight;
static GraphicsPackage() (
// Code would be here to
// calculate resolution.
ScreenWidth = 1024;
ScreenHeight = 768;
}
}
class ReadOnlyApp {
public static void Main() {
Console.WriteLine("Width = {0}, Height = {1}",
GraphicsPackage.ScreenWidth,
GraphicsPackage.ScreenHeight);
}
}
4.1.3.6 Destructors
Destructors are also called a finalizer. C# destructors are very similar to Java’s finalize() method,
and have all the same disadvantages.
Destructors implement the actions required to destruct instances of a class. Destructors are
declared using destructor-declarations:
destructor-declaration:
attributesopt ~ identifier ( ) block
A destructor-declaration may include set of attributes.
The identifier of a destructor-declarator must name the class in which the destructor is declared.
If any other name is specified, an error occurs.
The block of a destructor declaration specifies the statements to execute in order to initialize a
new instance of the class. This corresponds exactly to the block of an instance method with a
void return type.
Destructors are not inherited. Thus, a class has no other destructors than those that are actually
declared in the class.
Destructors are invoked automatically, and cannot be invoked explicitly. An instance becomes
eligible for destruction when it is no longer possible for any code to use the instance. Execution of
the destructor or destructors for the instance may occur at any time after the instance becomes
eligible for destruction. When an instance is destructed, the destructors in an inheritance chain
are called in order, from most derived to least derived.
The name of a class destructor is the class name preceded by a tilde (~). They are always public
and have no return value. They do not take any arguments, so there can only ever be one for a
class.
An example:
public class Test {
private int x;
public Test() {
x = 0;
}
~Test() {
// tidy up
}
}
Output
100
55
22
If you remove the new modifier, the program will still compile and run, but you will get the
warning:
SC0108: The keyword new is required on 'MyDerivedC.x' because it hides inherited
member 'MyBaseC.x'.
There is one other way of casting objects: using the as keyword. The advantage to using this
keyword instead of a cast is that if the cast is invalid, you don't have to worry about an exception
being thrown. What will happen instead is that the result will be null. Here's an example:
using System;
class Employee { }
class ContractEmployee : Employee { }
class CastExample5 {
public static void Main () {
Employee e = new Employee();
Console.WriteLine("e = {0}",
e == null ? "null" : e.ToString());
ContractEmployee c = e as ContractEmployee;
Console.WriteLine("c = {0}",
c == null ? "null" : c.ToString());
}
}
If you run this example, you'll see the following result:
c:>CastExample5
e = Employee
c = null
// AM2.cs
using System;
public class Dad {
public bool diamond;
protected bool gold;
protected internal bool silver;
internal bool car;
bool house;
}
class Friend {
public static void Main(String[] args) {
Dad d = new Dad();
d.diamond = true;
d.gold = true; // protected inaccessible
d.silver = true;
d.car = true;
d.house = true; // private inaccessible
Console.WriteLine("diamond = {0}, gold = {1}", d.diamond, d.gold);
Console.WriteLine("silver = {0}, car = {1}", d.silver, d.car);
Console.WriteLine("house = {0}", d.house);
}
}
}
}
public class MainClass {
public static int Main() {
// Access to T1 fields:
T1.myPublicInt = 1; // Access is unlimited
T1.myInternalInt = 2; // Accessible only in current project
// T1.myPrivateInt = 3; // Error: inaccessible outside T1
A virtual method is one where the decision on exactly which method to call is delayed until run-
time, allowing the dynamic type of the reference to be used. You declare a method as virtual by
using the virtual modifier in the base class:
public class Account {
Virtual public bool Withdraw(double amt) {
}
}
4.2 Interfaces
The interface keyword declares a reference type that has abstract members.
Interfaces are used to define a contract; a class or struct that implements the interface must
adhere to this contract.
Interfaces can contain methods, properties, indexers, and events as members. They can’t contain
constants, fields (private data members), constructors and destructors or any type of static
member.
All the members of an interface are public by definition, and the compiler will give you an error if
you try to specify any other modifiers on interface members. The static and public modifiers are
not permitted on interface methods.
C# interfaces are very much similar to Java interfaces. In C# we say : instead of implements, as
in Java.
The example
interface Iexample {
string this[int index] { get; set; }
event EventHandler E;
void F(int value);
string P { get; set; }
}
public delegate void EventHandler(object sender, Event e);
shows an interface that contains an indexer, an event E, a method F, and a property P.
Interfaces may employ multiple inheritance. In the example below, the interface IComboBox
inherits from both ITextBox and IListBox.
interface Icontrol {
void Paint();
}
interface ITextBox: Icontrol {
void SetText(string text);
}
interface IListBox: Icontrol {
void SetItems(string[] items);
}
interface IComboBox: ITextBox, IListBox {}
Classes and structs can implement multiple interfaces. In the example below, the class EditBox
derives from the class Control and implements both IControl and IDataBound.
interface IdataBound {
void Bind(Binder b);
}
public class EditBox: Control, IControl, IdataBound {
public void Paint();
public void Bind(Binder b) {...}
}
In the example above, the Paint method from the IControl interface and the Bind method from
IDataBound interface are implemented using public members on the EditBox class. C# provides
an alternative way of implementing these methods that allows the implementing class to avoid
having these members be public. Interface members can be implemented by using a qualified
name. For example, the EditBox class could instead be implemented by providing
IControl.Paint and IDataBound.Bind methods.
public class EditBox: IControl, IdataBound {
void IControl.Paint();
void IDataBound.Bind(Binder b) {...}
}
Interface members implemented in this way are called “explicit interface member
implementations” because each method explicitly designates the interface method being
implemented.
Explicit interface methods can only be called via the interface. For example, the EditBox’s
implementation of the Paint method can be called only by casting to the IControl interface.
class Test {
static void Main() {
EditBox editbox = new EditBox();
editbox.Paint(); // error: EditBox does not have a Paint method
IControl control = editbox;
control.Paint(); // calls EditBox’s implementation of Paint
}
}
4.3 Structs
A struct in C# is simply a composite data type, consisting of a number of elements (or
members) of other types. The variables which make up a struct are called its members (fields in
C#), and can be accessed using a simple dot notation. It’s quite possible to nest references to
structs, and you can use the dot notation to access all levels within the nested structure. You can
also declare structs nested inside other structs. The list of similarities between classes and structs
is long – structs can implement interfaces, and can have the same kinds of members as classes.
Structs differ from classes in several important ways, however: structs are value types rather
than reference types, and inheritance is not supported for structs. Struct values either are stored
“on the stack” or “in-line”. Careful programmers can enhance performance through judicious use
of structs.
For example, the use of a struct rather than a class for a Point can make a large difference in the
number of allocations. The program below creates and initializes an array of 100 points. With
Point implemented as a class, the program instantiates 101 separate objects – one for the array
and one each for the 100 elements.
class Point {
public int x, y;
public Point() {
x = 0;
y = 0;
}
public Point(int x, int y) {
this.x = x;
this.y = y;
}
}
class Test {
static void Main() {
Point[] points = new Point[100];
for (int i = 0; i < 100; i++)
points[i] = new Point(i, i*i);
}
}
If Point is instead implemented as a struct, as in
struct Point {
public int x, y;
public Point(int x, int y) {
this.x = x;
this.y = y;
}
}
then the test program instantiates just one object, for the array. The Point instances are
allocated in-line within the array. Of course, this optimization can be mis-used. Using structs
instead of classes can also make your programs fatter and slower, as the overhead of passing a
struct instance by value is slower than passing an object instance by reference. There is no
substitute for careful data structure and algorithm design.
If you pass a struct to a function, by default, the entire structure is copied onto the stack.
4.4 Enums
An enum type declaration defines a type name for a related group of symbolic constants (named
integer constants). Enums are typically used when for “multiple choice” scenarios, in which a
runtime decision is made from a number of options that are known at compile-time. Each of the
named constants has a value, which by default starts at zero and increases by one for each
succeeding member. You can give explicit values to any or all of the constants; any that you don’t
specify get a value one more than the proceeding constant. The default type of the constants is
int; you can declare enum which use other integral types like – byte, sbyte, ushort, short,
uint, long, ulong.
enum Weekday : short (Mon, Tue, Wed);
The other example
enum Color {
Red,
Blue,
Green
}
class Shape {
public void Fill(Color color) {
switch(color) {
case Color.Red:
...
break;
case Color.Blue:
...
break;
case Color.Green:
...
break;
default:
break;
}
}
}
shows a Color enum and a method that uses this enum. The signature of the Fill method
makes it clear that the shape can be filled with one of the given colors.
The use of enums is superior to the use of integer constants – as is common in languages without
enums – because the use of enums makes the code more readable and self-documenting. The
self-documenting nature of the code also makes it possible for the development tool to assist with
code writing and other “designer” activities. For example, the use of Color rather than int for a
parameter type enables smart code editors to suggest Color values.
4.5 Properties
In OO languages, the data members are normally private and access to them is through get and
set (or accessor) methods. The drawback here is that one has to code the accessor methods for
each data member and users have to remember to use them to access these data members.
C# has this idea of accessing data members through get and set code built into the language, in
the form of properties. The difference between using get/set methods and properties is that to a
user, using a property looks like they are getting direct access to the data, whereas in fact the
compiler is mapping the call onto the get/set methods.
The success of rapid application development tools like Visual Basic can, to some extent, be
attributed to the inclusion of properties as a first-class element. VB developers can think of a
property as being field-like, and this allows them to focus on their own application logic rather
than on the details of a component they happen to be using. On the face of it, this difference
might not seem like a big deal, but modern component-oriented programs tend to be chockfull of
property reads and writes. Languages with method-like usage of properties (e.g.,
o.SetValue(o.GetValue() + 1);) are clearly at a disadvantage compared to languages that
feature field-like usage of properties (e.g., o.Value++;).
Properties are defined in C# using property declaration syntax. The first part of the syntax looks
quite similar to a field declaration. The second part includes a get accessor and/or a set accessor.
In the example below, the Button class defines a Caption property.
public class Button: Control {
private string caption;
public string Caption {
get {
return caption;
}
set {
caption = value;
Repaint();
}
}
}
Properties that can be both read and written, like the Caption property, include both get and set
accessors. The get accessor is called when the property’s value is read; the set accessor is called
when the property’s value is written. In a set accessor, the new value for the property is given in
an implicit value parameter (the value represents the value passed in from the user).
Declaration of properties is relatively straightforward, but the true value of properties shows itself
is in their usage rather than in their declaration. The Caption property can be read and written in
the same way that fields can be read and written:
Button b = new Button();
b.Caption = "ABC"; // set
string s = b.Caption; // get
b.Caption += "DEF”; // get & set
You can omit either the set or get clause. You don’t have to return the value of a variable in a get
clause, but can use any code you like to calculate or obtain the value of the property. This means
that properties don’t have to be tied to a data member but can represent dynamic data.
Properties can be inherited and you can use the abstract and virtual modifiers with them, so
that derived classes can be required to implement their own versions of property methods. In
addition, the static modifier can be used to create properties that belong to classes as opposed
to individual objects.
4.6 Assignment
Use a generic, higher level IAccount interface and an abstract class BankAccount to collect all
features common to all bank accounts. The BankAccount class has account number and balance
as instance variables. It has the methods for deposit and withdrawal. The SavingsAccount and
BonusSaverAccount are both savings accounts through which you can make deposits and
withdrawals. However, the BonusSaverAccount is designed with incentives to encourage faster
savings accumulation. Both types of accounts earn interest, but the BonusSaverAccount earns
more. Furthermore, there is a financial penalty every time a withdrawal is made from a
BonusSaverAccount.
The SavingsAccount and CurrentAccount classes are derived from BankAccount and
BonusSaverAccount is derived from SavingsAccount.
The CurrentAccount has an overridden withdrawal method that makes use of overdraft protection.
// Assignment.cs
namespace BankSystem {
using System;
public interface IAccount {
// Two abstract methods:
void deposit(double amount);
bool withdrawal(double amount);
}
public abstract class BankAccount : IAccount {
// Fields:
private int account;
private double balance;
// Constructor:
public BankAccount(int accountNum, double initialBal) {
account = accountNum;
balance = initialBal;
}
// Property implementation:
public int Account {
get {
return account;
}
set {
account = value;
}
}
public double Balance {
get {
return balance;
}
set {
balance = value;
}
}
public void deposit(double amount) {
balance += amount;
Console.WriteLine("Deposit into account " + account);
Console.WriteLine("Amount: " + amount);
Console.WriteLine("New Balance: " + balance);
}
public virtual bool withdrawal(double amount) {
bool result = false;
Console.WriteLine("Withdrawal from Account " + account);
Console.WriteLine("Amount: " + amount);
if (amount > balance)
Console.WriteLine("Insufficient funds");
else {
balance -= amount;
Console.WriteLine("New Balance: " + balance);
result = true;
}
return result;
}
}
class SavingsAccount : BankAccount {
protected double rate;
public SavingsAccount (int accountNum, double initialBal,
savings.deposit(1000.00);
bigSavings.deposit(1000.00);
savings.withdrawal(500.00);
bigSavings.withdrawal(500.00);
checking.withdrawal(501.00);
}
}
}
• Destructors are invoked automatically, and cannot be invoked explicitly. The name of a class
destructor is the class name preceded by a tilde (~). They are always public and have no
return value. They do not take any arguments, so there can only ever be one for a class.
• A derived class can hide inherited members by declaring new members with the same name
or signature. Note however that hiding an inherited member does not remove the member—it
merely makes the member inaccessible in the derived class.
• The base keyword is in many ways the equivalent of Java’s super keyword.
• Use the new modifier to explicitly hide a member inherited from a base class.
• A derived class can be used in place of its base class.
• The as keyword can be used instead of a cast. The advantage over a cast is that if the cast is
invalid, you don't have to worry about an exception being thrown. What will happen instead is
that the result will be null.
• When a class-member-declaration does not include any access modifiers, the declaration
defaults to private declared accessibility.
• The access modifiers are associated with members or types (classes etc.) and are not allowed
on namespaces.
• A virtual method is one where the decision on exactly which method to call is delayed until
run-time, allowing the dynamic type of the reference to be used. You declare a method as
virtual by using the virtual modifier in the base class.
• When you override a virtual method in a derived class, you can use the override
override keyword to
signal that you are overriding a virtual method.
• Remember that the virtual keyword must be used on the base class's method, and the
override keyword is used on the derived class's implementation of the method.
• The interface keyword declares a reference type that has abstract members. Interfaces can
contain methods, properties, indexers, and events as members. They can’t contain constants,
fields (private data members), constructors and destructors or any type of static member. All
the members of an interface are public by definition, and the compiler will give you an error if
you try to specify any other modifiers on interface members. The static and public modifiers
are not permitted on interface methods.
• Structs are value types rather than reference types, and inheritance is not supported for
structs.
• An enum type declaration defines a type name for a related group of symbolic constants
(named integer constants). Each of the named constants has a value, which by default starts
at zero and increases by one for each succeeding member. You can give explicit values to any
or all of the constants; any that you don’t specify get a value one more than the proceeding
constant. The default type of the constants is int.
• The difference between using get/set methods and properties is that to a user, using a
property looks like they are getting direct access to the data, whereas in fact the compiler is
mapping the call onto the get/set methods.
• In a property set accessor, the new value for the property is given in an implicit value
parameter (the value represents the value passed in from the user).
• You can omit either the set or get clause in case of a property.
Next, we bind to one of the files in the H:\pune-csharp folder, rename it and copy it, as in
example RC.cs:
using System;
using System.IO;
public class RC {
public static void Main(String[] args) {
File f1 = new File(@"H:\pune-csharp\Satish");
f1.CopyTo(@"H:\pune-csharp\Talim\NewSatish");
}
}
Next we delete the file NewSatish and the folder Talim, as in the example DFF.cs:
using System;
using System.IO;
public class DFF {
public static void Main(String[] args) {
File f1 = new File(@"H:\pune-csharp\Satish");
f1.Delete();
Directory d1 = new Directory(@"H:\pune-csharp\Talim");
d1.Delete(true);
}
}
Here Delete(true) is used to remove directories, subdirectories, and contents; otherwise false.
}
5.3.5 Writing Text Files
For writing text files we use the StreamWriter class. The example WTF.cs explains this:
using System;
using System.IO;
public class WTF {
public static void Main(String[] args) {
StreamWriter sw = new StreamWriter(@"H:\CSHARPTEMP\TEMP.cs", false);
sw.WriteLine("How do you find the C# Workshop?");
sw.WriteLine("We now write some numbers and bool...");
sw.WriteLine(6);
sw.WriteLine(65.34);
sw.WriteLine(true);
sw.Close();
}
}
The StreamWriter constructor takes two parameters: the full name of the file and a boolean that
indicates whether data should be appended to the file. If this is false then the contents of the file
will be overwritten by the StreamWriter. In either case, the file will be opened if it already exists
or created if it does not.
The constructor we use takes three parameters: the full pathname of the file, the mode we are
using to open it and the access required. The mode and access are enumerated values
respectively taken from two further classes in the System.IO namespace: FileMode and
FileAccess. The possible values for mode is Append, Create, CreateNew, Open,
OpenOrCreate and Truncate. For the access, they are Read, ReadWrite and Write.
5.4 Networking
High level access is performed using a set of types that implement a generic request/response
architecture that is extensible to support new protocols. The implementation of this architecture
in the BCL also includes HTTP-specific extensions to make interacting with web servers easy.
Should the application require lower-level access to the network, types exist to support TCP and
UDP. Finally, in situations where direct transport-level access is required, there are types that
provide raw socket access.
5.4.1 HTTP
The HTTP protocol accounts for a large share of all traffic on the Internet; and the .NET
frameworks provide robust support for the HTTP protocol with the HttpWebRequest and
HttpWebResponse classes. These classes are the WebRequest and WebResponse derived
classes returned whenever a URI beginning with "http" or "https" is presented to the Create
method on the WebRequestFactory. In most cases, the WebRequest and WebResponse
classes will provide all that is necessary to make the request, but when access to HTTP-specific
features is required, the request or response can be typecast to HttpWebRequest or
HttpWebResponse.
The HttpWebRequest and HttpWebResponse classes encapsulate a standard HTTP request
and response transaction, and provide access to common HTTP headers through properties.
These classes also support most of the HTTP 1.1 protocol features, including pipelining, chunking,
authentication, pre-authentication, encryption, proxy support, server certificate validation,
connection management, and HTTP extensions. Custom headers and headers not provided
through properties can be accessed by storing them in the Headers property.
The following sample shows how to access HTTP specific properties, in this case turning off the
HTTP Keep-alive behavior and getting the protocol version number from the Web server:
HttpWebRequest HttpWReq =
(HttpWebRequest)WebRequestFactory.Create("http://www.pune-csharp.com");
// Turn off connection keep-alives
HttpWReq.KeepAlive = false;
HttpWebRequest is the default class used by WebRequestFactory and does not need to be
registered before passing an HTTP Uniform Resource Identifier (URI) to the
WebRequestFactory.Create method.
Your application can automatically follow HTTP redirects by setting the AllowAutoRedirect
property true. When the request is redirected, the ResponseURI property of the
HttpWebResponse will contain the actual Web resource that responded to the request. When
AllowAutoRedirect is false, your application must be prepared to handle redirects as an HTTP
protocol error.
Applications receive HTTP protocol errors by catching a WebException with the Status set to
WebStatus.ProtocolError. The Response property contains the WebResponse sent by the
server and it can be examined to find the actual HTTP error encountered.
position to the end of the stream. The Close method closes the StreamReader and releases any
system resources associated with the reader.
The example Snarf.cs below uses the WebRequest and WebResponse classes to retrieve the
contents of a URI and display them to the console.
// Snarf.cs
// Compile with /r:System.Net.dll
// Run Snarf.exe <http-url> to retrieve a web page
using System;
using System.IO;
using System.Net;
using System.Text;
class Snarf {
public static void Main(string[] args) {
URI siteURI = new URI(args[0]);
WebRequest req = WebRequestFactory.Create(siteURI);
WebResponse res = req.GetResponse();
Stream s = res.GetResponseStream();
StreamReader sr = new StreamReader(s, Encoding.ASCII);
string doc = sr.ReadToEnd();
Console.WriteLine(doc);
sr.Close();
}
}