|
Learning C# C# is the new modern language for developing applications for Microsoft's .NET platform. This document will go through the basics of this language and explain how you can use it to write efficient platform neutral applications. The .NET platform is designed to be language neutral, indeed, all .NET code is run under the Common Language Runtime (CLR). C# is just one of the languages that can be used to write classes for the CLR, but so far it is the only language that was written from the outset with .NET in mind. This makes C# as the language of choice for .NET development. What Is C#? The most simple C# program is shown here:
// in Simple.cs
class App
{
public static void Main()
{
}
}
This code does nothing, but will compile fine using the C# compiler csc.exe: Before making this application do more let me explain the code that you already seen. The code defines a .NET class which has a single public method. By public I mean that the method can be called from code outside of the class, in general a method marked as private is only accessible from code in the current class. However Main() is a special case, you can declare it as private and the system will still be able to call it, but in practice you should always declare it as public. The method is also marked as static. Classes are usually used to create objects - instances of the class - and methods are called through objects using data that is specific to an object. However, a static method can be called without an object instance. .NET does not allow you to write global methods, every method has to be part of a class, so static is the only way to call a method without first creating an object. Because static methods are not associated with objects, they cannot call non-static methods or use other members of the class that is non-static. For example:
class App
{
public static void Main()
{
f(); // this will not compile!
}
public void f()
{
}
}
One typical reason for a static method is to create an instance of the class, this is done in C# with the new operator:
class App
{
public static void Main()
{
The two new lines create an instance of the App class and call the non-static method f() through that instance; app is known as a reference. The syntax used here is different to what you would expect in C++. Firstly, the code must explicitly specify the constructor that will be used (in this case the default constructor that is provided by C#). The second difference is that although new is used to create a reference there is no equivalent of the C++ delete operator. The reason is that .NET has a garbage collector that monitors object usage and when all references to an object have been released (or if there is a circular dependency) the garbage collector can release the object. In C++ new has the specific meaning of "create a new instance of this class in the C++ free store". The meaning of new in C# is more wide ranging, it merely says "create a new instance of this class", usually this class will be created on the heap and managed by the .NET garbage collector, but if used to create an instance of a value type (a term that will be explained later) the object will be created on the stack. Note: if you have a class that holds resources that should not be held for a long time (for example an exclusive lock on a file), then you should implement a method on the class to free this resource and explicitly call this method when you are sure that the object will not longer be used. Typically , such a method is called Dispose(). The Main() function has a special meaning, it is the entry point for an application, and every C# application must have a class with a public static Main() method - and only one such class. As in C++, the Main() method may return void or an int and it can take parameters. Main() that accepts command line parameters looks like this:
public static int Main(string[] args)
{
return 0;
}
The args parameter is an array of strings. Arrays in .NET are instances of the System.Array class, and items are accessed through square brackets, as in C++. The size of an array is determined by accessing the Length property. A property gives access to data in the class. For your application to do anything it must use a class library. A class library contains classes that perform various system actions like creating and manipulating windows, managing security, or accessing databases. C# code uses the .NET library that is common to all code that runs on the .NET platform. This means that the same library is available to VB.NET code and through the Managed Extensions for C++. A class library is accessed through the using keyword:
// in Simple.cs
Here, the using line indicates that the code will use classes defined in the System namespace. A namespace is a collection of .NET classes, which are usually related. As you get more proficient with C# you'll create your own namespaces and putting classes in a namespace allows other applications to use your classes. This code uses a class called Console. As the name suggests this gives access to the command line console. This simple example prints all the command line arguments to the console. Namespaces scope class names, using System in System.String means that you indicate that you want to use String from the System namespace as opposed to String defined in another namespace. You use the /reference switch on csc to indicate the namespaces that your code will use. The using keyword allows you to use classes without using the fully qualified name. Note: like C++ and C, each statement must end in a semicolon. However, unlike C++ the class declaration does not have to have a terminating semicolon. C# allows methods to be overloaded. What this means is that a class can have methods with the same name, but that take different parameters. For example, you can write the following for Main():
public static void Main()
{
int i = 1;
double f = 2.0;
Console.WriteLine("Integer");
Console.WriteLine(i);
Console.WriteLine("Floating point");
Console.WriteLine(f);
}
Other than literal strings, this code passes an integer and a double precision floating point number to WriteConsole(). The output of this new program is:
Integer
1
Floating point
2
Notice that there is a new line after each call to WriteLine(). There are two ways to get the string and value on the same line; both are shown in this next code:
public static void Main()
{
int i = 1;
double f = 2.0;
Console.Write("Integer ");
Console.WriteLine(i);
Console.WriteLine("Floating point {0}", f);
}
In the first case I have used the Write() method instead of the WriteLine() method to write the literal string. In the second case I have used another overload of WriteLine() that takes a format string and a variable number of parameters. The format string has placeholders indexed from 0, each one identified by braces ({}). The runtime library will replace the placeholders with the actual values in the variables. The index of the placeholder reflects the order of the parameters to WriteLine(), so the following will print out the integer first, and then the floating point number:
Console.WriteLine(
"Integer {1} Floating point {0}",
f, i);
What about formatting options? Well, the common class library comes with classes to allow you to format numbers, which I will cover later. However, Write() and WriteLine() does support column formatting. The syntax is shown in the following example:
Console.WriteLine("{0,-10}{1,-3}",
"Name","Age");
Console.WriteLine("-------------");
Console.WriteLine("{0,-10}{1,3}",
"Richard", richardsAge);
Console.WriteLine("{0,-10}{1,3}",
"Jenny", jennysAge);
Here, the second number in the braces specifies the width of the column and the justification. -10 means that the Name column is 10 characters wide and items are left justified, a value of 10 will make the items right justified. The output from the lines above will be:
Name Age
-------------
Richard 36
Jenny 8
Classes All code in .NET is run as part of a class, classes group together associated methods that act upon common data. Each instance of a class is called an object and has its own copy of the data, for example:
class Data
{
public int x;
}
class App
{
public static void Main()
{
Data d1 = new Data();
d1.x = 1;
Data d2 = new Data();
d2.x = 2;
Console.WriteLine("d1.x = {0}", d1.x);
Console.WriteLine("d2.x = {0}", d2.x);
}
}
This defines a new class called Data that has a data member called x. Data members like x are known as fields. x is declared as public which means that it can be accessed by code outside the class. Two instances of Data are created and initialized. The output of this program will be:
d1.x = 1
d2.x = 2
The reason is that each instance of the class has its own storage for x. When an instance of Data is created the field is automatically initialized to a default value (for int this is 0), C# allows you to specify an initialization value as part of the field declaration, for example:
class Data
{
public int x = 99;
}
When an instance of Data is created the field x will have a value of 99. If you prefer you can put this initialization code in a constructor:
class Data
{
public int x;
public Data(){x = 99;}
}
Note that C# does not allow constructor initialization lists in the C++ style, but the explicit initialization of fields in their declaration makes this style redundant. C# does support initialization of base objects, which I'll explain later. Classes can have more than one constructor, for example:
class Data
{
public int x;
private Data(){}
public Data(int y){x = y;}
public Data(int y, int z){x = y + z;}
}
Here, three constructors are declared and because the parameterless constructor is declared as private it means that it cannot be called by code outside of the class, thus external code must call one of the other two constructors:
Data d1 = new Data(44);
Data d2 = new Data(22, 33);
C# allows you to call constructors through the this keyword:
public Data(int y) : this(y, 99) {}
Essentially what this code is saying is "call the constructor with two parameters". As you can see, this allows you to provide default values for constructor parameters. If you would like a value to be shared between instances of a class then you should use the static keyword on the field.
class Counted
{
public static int count = 0;
public Counted()
{
count++;
}
public int GetInstanceCount()
{
return count;
}
}
class App
{
public static void Main()
{
Counted d1 = new Counted();
Console.WriteLine("current total {0}",
d1.GetInstanceCount());
Counted d2 = new Counted();
Console.WriteLine("current total {0}",
d2.GetInstanceCount());
Console.WriteLine("total {0}",
Counted.count);
}
}
Notice the change in the syntax static members cannot be accessed directly through instances, instead, they have to be used through the class (in the form <classname>.<staticmembername>). However, static members can be accessed by code in objects, which is why the call to GetInstanceCount() works. The Main() method creates two instances of Counted and the constructor increments count to keep a count of the number of instances that have been created. This instance count is accessed by the non-static method GetInstanceCount() within the class and by code outside of the class through the class name. Public fields can be read or written to, but this is not always desirable. C# allows you to identify a field as being read only by using the readonly keyword:
class Data
{
public readonly int x = 42;
}
The GetInstanceCount() method of Counted is interesting because it gives access to the data member through a method call. Because of what it does such a method is typically called a getter method. A getter method can do more than just return a data member, but it is called explicitly to return the value of a data member. Since such methods are popular, C# supports the notion of properties, here is the Counted class with a property called InstanceCount:
class Counted
{
public static int x = 0;
public Counted()
{
x++;
}
public int InstanceCount // property
{
get{return x;}
}
}
Notice the syntax here. The property is declared without an argument list because it will be accessed as if it is a data member. This property is read only, so it only defines a getter that merely returns the data. Non static properties can be called through class instances for example:
Counted d1 = new Counted();
Console.WriteLine("current total {0}",
d1.InstanceCount);
Counted d2 = new Counted();
Console.WriteLine("current total {0}",
d2.InstanceCount);
If a property is write able then it will have a setter, for example:
private string name;
public string Name
{
get{return name;}
set{name = value;}
}
The as the name suggests, a setter takes a value which is then used to initialize the data member. The syntax for properties does not allow for a parameter list, and instead set has an implicit parameter called value. The property syntax is useful, but it does not allow you to pass a value other than the actual property value. An extension of the property idea is the indexer. These allow you to treat an object as if it is an array and pass an index value. Here is an example of a class with an indexer:
class Colors
{
private string[] colors = new string[3];
public string this[int index]
{
get
{
if (index < colors.Length
&& index >= 0)
return colors[index];
return "invalid";
}
set
{
if (index < colors.Length
&& index >= 0)
colors[index] = value;
}
}
}
Before I explain the indexer syntax I ought to explain the two common library classes System.Array and System.String. As the name suggests, String is used to hold strings and as with all .NET classes the memory that is used is managed by the system. The C# basic data type string (no capital letter) is just another name for System.String (capitalized). As mentioned above, the Array class is used to hold collections of items and arrays are integrated into C# using the [] syntax. The following declaration in the class:
private string[] colors = new string[3];
creates a new array with space for 3 strings. The indexer is applied to the class as a whole, which is the reason why the indexer is called this. As with properties you can make the indexer readable and writeable with getters and setters. I have chosen to use these to check the index parameter to make sure that it is within range. The size of an array is determined by accessing the Length property and in C# all arrays are indexed from 0. Class declarations can be modified with the public and internal modifiers. These affect the accessibility of the class. Classes are deployed as part of assemblies, which for Windows means one or more DLLs. A class that is declared as public can be accessed outside of the assembly whereas a class declared as internal can only be accessed by code within the same assembly, by default classes are assumed to be internal. C# classes support single inheritance, whereby a class can derive from a single base class and, depending on the access specifiers of the base members, it inherits implementation from the base class. All C# classes are derived implicitly from the System.Object class (also known by the C# type object) somewhere in their class hierarchy. You can derive your classes from other classes, for example:
class BaseClass
{
// derived classes have access
protected int x;
}
class DerivedClass : BaseClass
{
public int y;
public DerivedClass()
{
x = 10; y = x*x;
}
}
Here, BaseClass implicitly derives from System.Object. Since BaseClass.x is protected it means that only the derived class has access. This code explicitly initializes this member in the constructor of DerivedClass, it would be more natural to do this through a base class constructor, which C# supports using the base keyword:
class BaseClass
{
protected int x;
private BaseClass(){}
protected BaseClass(int y){x = y;}
}
class DerivedClass : BaseClass
{
public int y;
public DerivedClass() : this(42){}
public DerivedClass(int z) : base(z)
{
y = x*x;
}
}
Here, BaseClass has private and protected constructors and I have used the this() construct to give a default value for the constructor of DerivedClass. The interesting point is the use of base() in the second DerivedClass constructor, this is used to call the constructor of the base class that has a single parameter. Classes can also be declared as abstract. This means that instances of the class cannot be created, instead it identifies methods that must be implemented together and it is designed to be a base class:
public abstract class Named
{
// property
public abstract String Name {get; set;}
// method
public abstract void PrintName();
}
public class B : Named
{
private String name = "empty";
public override String Name
{
get{return name;}
set{name=value;}
}
public override void PrintName()
{
Console.WriteLine("Name is {0}", name);
}
}
A class that derives from Named must implement a property called Name and a method called PrintName(), if B did not provide an implementation of either of these, then it too must have the abstract modifier. abstract classes can contain members that have an implementation, in which case the derived classes will inherit the implementation. However, note that the items that are marked as abstract can only appear in classes that are marked as abstract. abstract classes allow you to define an 'interface' that all derived classes must support, 'interfaces' like this define specific behaviour. However, the .NET restriction that classes can only derive from a single class poses a problem when you want a class to support more than one behaviour and to get round this .NET has a more formal definition of interfaces, as explained later. abstract indicates that a class member does not have an implementation in the abstract class, and that a derived class must provide this implementation, in other words it passes the buck and says that the code is incomplete and that someone else must do something about it. The antithesis of this is the sealed modifier. When this is applied to a class it indicates that the implementation is complete and thus you cannot derive a class from it. You cannot use sealed and abstract on the same class. Similar to C++, C# has a concept of virtual methods. Virtual methods mean that the implementation that is executed is determined at runtime rather than at compile time. A derived class can implement a virtual method, but this will override the implementation inherited from the base class. To indicate that this is the case the derived class's method must be marked with the override modifier. For example:
class A
{
public virtual void Name()
{Console.Write("A ");}
}
class B : A
{
public override void Name()
{Console.Write("B ");}
}
class C
{
public void f()
{
A a = new A();
a.Name();
B b = new B();
b.Name();
A b1 = b;
b1.Name();
}
}
When you call f() you will see "A B B" printed at the console. The reason is that the call to Name() on an A object will output "A" and when called on a B object it will output "B". The variable b1 is an A reference, B.Name() is called because at runtime the CLR sees that the b1 reference is actually created from a B object. If an overridden method in a derived class needs to access the method in the base class it can do so by using the base keyword:
class B1 : A
{
public override void Name()
{
Console.Write("B1 ");
Console.Write("derived from ");
base.Name();
}
}
The output from calling B1.Name() is "B1 derived from A". As you can see the use of override and virtual allows you to hide method implementations inherited from base classes. C# has an associated modifier called new that indicates that the method in the derived class should be treated as a new method rather than an override. Consider this code:
class B2 : A
{
public new void Name()
{Console.Write("B ");}
}
class C1
{
public void f()
{
B2 b = new B2();
b.Name();
A b2 = b;
b2.Name();
}
}
When you call C1.f() you will get the output "B A", the interesting part of this output is that when you cast the B2 instance to an A and call the virtual Name() method you get the implementation in A not B2. In effect, new has turned off the virtualness of the method. This effect only occurs because B2.Name() explicitly has the new modifier. The final important point about virtual is that it cannot be used with abstract. This is in contrast to C++ where this is possible with pure virtual functions. Next Page....Contribute to IDR: To contribute an article to IDR, a click here.
|