Classes 101 in C#

Classes are the heart of any application in an object-oriented language. This chapter is broken into several sections. The first section describes the parts of C# you’ll use often, and the later sections describe features you won’t use as often, depending on what kind of code you’re writing.

1. A Simple Class

A C# class can be very simple:

class VerySimple {

int simpleValue = 0;

}

class Test {

public static void Main()

{

VerySimple vs = new VerySimple();

}

}

This class is a container for a single integer. Because the integer is declared without speci­fying how accessible it is, it’s private to the VerySimple class and can’t be referenced outside the class. The private modifier could be specified to state this explicitly.

The integer simpleValue is a member of the class; there can be many different types of members.

In the Main() function, the system creates the instance in heap memory and returns a reference to the instance. A reference is simply a way to refer to an instance.1

There’s no need to specify when an instance is no longer needed. In the preceding example, as soon as the Main() function completes, the reference to the instance will no longer exist. If the reference hasn’t been stored elsewhere, the instance will then be available for reclamation by the garbage collector. The garbage collector will reclaim the memory that was allocated when necessary.

This is all very nice, but this class doesn’t do anything useful because the integer isn’t accessible. Here’s a more useful example:

using System;

class Point {

// constructor public Point(int x, int y)

{

this.x = x;

this.y = y;

}

// member fields public int x; public int y;

}

class Test {

public static void Main()

{

Point myPoint = new Point(10, 15);

Console.WriteLine(“myPoint.x {0}”, myPoint.x);

Console.WriteLine(“myPoint.y {0}”, myPoint.y);

}

}

This example has a class named Point, with two integers in the class named x and y. These members are public, which means their values can be accessed by any code that uses the class.

In addition to the data members, the class has a constructor, which is a special function that’s called to help construct an instance of the class. The constructor takes two integer parameters.

In this constructor, a special variable called this is used; the this variable is available within all member functions and always refers to the current instance.

In member functions, the compiler searches local variables and parameters for a name before searching instance members. When referring to an instance variable with the same name as a parameter, you must use the this.<name> syntax.

In this constructor, x by itself refers to the parameter named x, and this.x refers to the integer member named x.

In addition to the Point class, you’ll see a Test class that contains a Main function that’s called to start the program. The Main function creates an instance of the Point class, which will allocate memory for the object and then call the constructor for the class. The constructor will set the values for x and y.

The remainder of the lines of Main() print the values of x and y.

In this example, the data fields are accessed directly. This is usually a bad idea; it means that users of the class depend upon the names of fields, which constrains the modifications that can be made later.

In C#, rather than writing a member function to access a private value, you’d use a property, which gives the benefits of a member function while retaining the user model of a field. See Chapter 19 for more information.

2. Member Functions

The constructor in the previous example is an example of a member function; it’s a piece of code that’s called on an instance of the object. Constructors can be called automatically only when an instance of an object is created with new.

You can declare other member functions as follows:

using System;

class Point {

public Point(int x, int y)

{

this.x = x;

this.y = y;

}

// accessor functions

public int GetX() {return(x);} public int GetY() {return(y);}

// variables now private int x; int y;

}

class Test {

public static void Main()

{

Point myPoint = new Point(10, 15);

Console.WriteLine(“myPoint.X {0}”, myPoint.GetX());

Console.WriteLine(“myPoint.Y {0}”, myPoint.GetY());

}

}

3. ref and out Parameters

Having to call two member functions to get the values may not always be convenient, so it’d be nice to be able to get both values with a single function call. There’s only one return value, however.

One solution is to use reference (or ref) parameters so you can modify the values of the parameters passed into the member function:

// error

using System;

class Point {

public Point(int x, int y)

{

this.x = x;

this.y = y;

}

// get both values in one function call

public void GetPoint(ref int x, ref int y)

{

x = this.x;

y = this.y;

}

int x;

int y;

}

class Test {

public static void Main()

{

Point myPoint = new Point(10, 15);

int x;

int y;

// illegal

myPoint.GetPoint(ref x, ref y);

Console.WriteLine(“myPoint({0}, {1})”, x, y);

}

}

In this code, the parameters have been declared using the ref keyword, as has the call to the function.

This code should work; however, when compiled, it generates an error message that says that uninitialized values were used for the ref parameters x and y. This means variables were passed into the function before having their values set, and the compiler won’t allow the values of uninitialized variables to be exposed.

You have two ways to get around this. The first is to initialize the variables when they’re declared:

using System;

class Point {

public Point(int x, int y)

{

this.x = x;

this.y = y;

}

public void GetPoint(ref int x, ref int y)

{

x = this.x;

y = this.y;

}

int x; int y;

}

class Test {

public static void Main()

{

Point myPoint = new Point(10, 15);

int x = 0;

int y = 0;

myPoint.GetPoint(ref x, ref y);

Console.WriteLine(“myPoint({0}, {1})”, x, y);

}

}

The code now compiles, but the variables are initialized to zero only to be overwritten in the call to GetPoint(). For C#, another option is to change the definition of the function GetPoint() to use an out parameter rather than a ref parameter:

using System;

class Point {

public Point(int x, int y)

{

this.x = x;

this.y = y;

}

public void GetPoint(out int x, out int y)

{

x = this.x;

y = this.y;

}

int x; int y;

}

class Test {

public static void Main()

{

Point myPoint = new Point(10, 15);

int x;

int y;

myPoint.GetPoint(out x, out y);

Console.WriteLine(“myPoint({0}, {1})”, x, y);

}

}

Out parameters are like ref parameters except that an uninitialized variable can be passed to them and the call is made with out rather than ref.

4. Overloading

Sometimes it may be useful to have two functions that do the same thing but take different parameters. This is especially common for constructors, when there may be several ways to create a new instance. For example:

class Point {

// create a new point from x and y values public Point(int x, int y)

{

this.x = x;

this.y = y;

}

// create a point from an existing point public Point(Point p)

{

this.x = p.x;

this.y = p.y;

}

int x; int y;

}

class Test {

public static void Main()

{

Point myPoint = new Point(10, 15);

Point mySecondPoint = new Point(myPoint);

}

}

The class has two constructors: one that can be called with x and y values and one that can be called with another point. The Main() function uses both constructors: one to create an instance from an x and y value and another to create an instance from an already-existing instance.

When an overloaded function is called, the compiler chooses the proper function by matching the parameters in the call to the parameters declared for the function.

Source: Gunnerson Eric, Wienholt Nick (2005), A Programmer’s Introduction to C# 2.0, Apress; 3rd edition.

Leave a Reply

Your email address will not be published. Required fields are marked *