![]() | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
C# (C-Sharp) Tutorial Object Oriented Programming (OOP)C# is an Object Oriented Programming (OOP) language, similar in nature to C++ and Java. In order to write programs in this language you need to have an understanding of what an object is. The essence of "object-oriented programming" is to treat data and the procedures that act upon the data as a single "object" - a self contained entity with an identity and certain characteristics of its own. Object-oriented programming has four features that benefit the design of applications. These are encapsulation, data-hiding, inheritance and polymorphism.
Learning to ReadEveryday we use items in our home, school or workplace, each of these is an object. One of the first books I remember reading involved a 'boy' named 'Peter', a 'girl' named 'Jane', a 'dog' and a 'ball' that was 'red'. So in terms of software we can use these things to also learn about objects. If we read the sentence again, we can use the nouns to suggest candidate objects to use in a software program. Each of the items in bold are objects, the other items in qutations are information about an object. There is a distinction between what an object is, and what is only information about an object. A 'boy' named 'Peter', a 'girl' named 'Jane', a 'dog' and a 'ball' that was 'red'. An object is often a tangible item, something that can be seen, touched or felt, or something that can be eluded to, conceptualised, thought about. This is a good starting place when thinking about them. Objects can be...
AbstractionSo we have a 'boy' object, a 'girl' object, a 'dog' object and a 'ball' object. Let's first consider the 'boy' object, we do not really want to think of a single item when developing objects, but instead we think about a "type" of object. In this case we do not think about 'Peter', but instead we think about a 'boy'. This "type" of object is defined as a class in C# and other object-oriented programming languages. But what about girls? If we have a boy object, we might also want a girl object. This seems a bit of a silly thing to do as boys and girls, men and women are all people. So maybe we should think of them as 'person' objects.
|
forename surname address age gender date of birth telephone number |
set forename() get forename() set surname() get surname() set address() get address() calculate age(date) set gender() get gender() set date of birth() get date of birth() set telephone number() get telephone number() |
We can use a simple UML object model to show our person object. The name of the object is placed within the topmost area. In the middle area we place the information about the object and finally in the bottom area we place the behaviour of that object.
Most of the behaviour allows us to access the information inside the object, these are known as access methods and allow a piece of information to be either set to a value or to obtain the value of the information. In C# this can also be achieved through the use of properties, which are a simpler way of setting and getting the information.
The behaviour of the object is not limited to accessing the information, but is often involved in manipulating the information in some manner. In the example above, the behaviour calculate age is used to work out the age of the person based on a date.
A class is an abstract representation for some particular type of object. It can be described as a template or blueprint for an object, as opposed to the actual object itself. Thus, objects are an instance of a class - they come into existence at some specific time, persist for some duration, and then disappear when they are no longer needed. Classes are the abstract descriptions used by the system to create objects when called upon to do so.
A class contains attributes (information) and methods (behaviour) that act upon the attributes. So what does a class look like? If we look at our person object, we can use this to create an example class.
class Person { . . . }
We declare our class using the keyword class, followed by the name of our class, which in this case is Person. We then use the curly braces ({}) to show the start and end of the class, which also defines the scope of the data used in the class. What this means is that all attributes and methods associated with the class must be between the two curly braces.
The data located within a class are referred to as variables or attributes. They can be of any data type including other classes.
class Person { // Attributes private string forename; private string surname; . . . }
An attribute is always declared in the following manner:
[access-modifier] data-type attribute-name
The access-modifier is optional. If you do not define one, then the attribute is defaulted to private.
The data-type is the type of object that we want our attribute to be, such as a string or integer.
The attribute-name is whatever we want to refer to the object. Remember that C# is a case sensitive language, so the attributes Forename and forename do not refer to the same object.
Each attribute definition is terminated with a semicolon (;).
Methods are functions that manipulate the data associated with an object or perform some other operation relevant to the object. A method can be thought of as a named sequence of statements. So let us add two methods to our Person class.
class Person { // Attributes private string forename; private string surname; . . . // Methods public void setSurname( string aSurname ) { surname = aSurname; } public string getSurname() { return surname; } }
A method is always declared in the following manner:
[access-modifier] return-type method-name( [parameter-list] ) { method-body-statements; }
The access-modifier is optional. If you do not define one, then the method is defaulted to private.
The return-type is the name of a type and specifies what kind of information the method returns. This can be the name of any type, such as int or string. If you are writing a method that does not return a value, you must use the keyword void in place of the return-type.
The method-name is the name used to call the method. Method names must follow the same identifier rules as variable names. For example, getSurname is a valid method name, whereas get$Surname is not valid. I use camelCase for method names, as well as making them descriptive - such as starting with a verb, as in getSurname.
The parameter-list is optional and describes the types and names of the information that the method accepts. You write the parameters between the left and right brackets as though you're declaring variables: name of the type, followed by the name of the parameter. If the method you're writing has two or more parameters, you must separate them with commas.
The method-body-statements are the lines of code that are run when the method is called. They are enclosed in an opening curly brace ({) and a closing curly brace ( }).
Every class has the same defining layout.
[access-modifier] class [identifier] [:base-class] {
class-body
The access-modifier is optional. If you do not define one, then the method is defaulted to public, because a class cannot be private.
The keyword class identifies the type of the object.
The [identifier] is the name of the class being declared.
The [:base-class] tag allows a class to inherit from another class. Inheritance is discussed later.
The class-body contains all of the code that is associated with an object.
When a class is declared as in the following code, we only say that we want to have an instance of the class in our program. We have not yet created the class in program memory.
Person Paul;
To do this we must instantiate an instance of the class, this is done using the new keyword, as in the following code:
Person Paul = new Person();
You can see that after the new keyword we called a method with the same name as the class. This is called its constructor. By calling this constructor, memory is allocated for the class, and we can now use it in our program, as in the next code sample.
Person Paul = new Person(); String surname = "Smith"; Paul.setSurname( surname );
A constructor is a method that is invoked upon instantiation of a class. In C# these are known as "instance constructors". An instance constructor is a member that implements the actions required to initialize an instance of a class. A constructor is invoked when you use the new operator, or use the various methods of reflection to create an instance of a class.
Every constructor has the same defining layout:
[access-modifier] constructor_name (parameters) { // constructor body }
The [access-modifier] can be private, public, protected or internal.
The constructor_name of a constructor must be the same as the name of the class.
A constructor can take zero or more arguments as parameters. A constructor with zero arguments (that is no-arguments) is known as a default constructor. A class can have more than one constructor, by giving each constructor a different set of parameters. This gives the user the ability to initialise the object in more than one way.
The constructor body can be used to initialise the object into a known starting state.
In, general, a constructor’s access modifier is public, this is because the class that is creating an instance of this class, needs to be able to access the constructor of the class.
public Person() { . . . }
Each class also requires a destructor, this is the code that is called to clean up the object when it has been finished with. However, if you do not provide one, then the Garbage Collector will deal with freeing up the memory for you. Any object that you create in the class, should be deleted from the class as a general rule of thumb.
A destructor is defined pretty much in the same way as a constructor, but has a tilde (~) in front of the method name.
public ~Person() { . . . }
Apart from the constructor and destructor, the class body can contain as many attributes and methods as required. However, if a class is getting too large to work with, think about logically splitting it into smaller classes, or into smaller files using partial classes.
The idea of encapsulation comes from the need to cleanly distinguish between the specification and the implementation of an operation and the need for modularity. Modularity is necessary to structure complex applications designed and implemented by a team of programmers.
Encapsulation is a way of binding information and the behaviour performed upon the information together in a single object. This allows the user of the object, to see and use the interface of the object without needing to know the internal workings of the object.
The idea of encapsulation in programming languages comes from abstract data types. In this view, an object has an interface part and an implementation part. The interface part is the specification of the set of operations that can be performed on the object and is the only visible part of the object.
In encapsulation, there is a single model for data (information) and operations (behaviour), and information can be hidden. No operations, outside those specified in the interface, can be performed.
Encapsulation provides a form of "logical data independence": we can change the implementation of an object without changing any of the programs using the object. Encapsulation is obtained when only the operations are visible and the data and the implementation of the operations are hidden within the object.
In our everyday lives, most of us use a video recorder via either the controls on the front of it or via a remote control. We all know which buttons to press in order to watch a program or record a program, this is done via the interface we have with the video recorder. The manufacturer can change the internal workings of the hardware, but this would not often affect us as a user because the interface we have would be the same. That is a play button, a record button, fast forward, rewind, stop, pause, etc.
The VCR is encapsulated as a single object, which may change internally, but stays the same from the users interface point of view.
The following code demonstrates a class containing some data and a method that acts upon the data (without data hiding):
class Counter { public int Count; public Counter() { Count = 0; } public void incCount() { Count++; } }
Although the member variable can be directly accessed, the data and its method is encapsulated in a class.
You can download a working example of the above code written using C# Express from here.
Data hiding is linked to encapsulation; however data hiding is not encapsulation as it is sometimes described in this way.
Data hiding is simply the means to remove direct access to an object’s information, by providing operations that perform actions on the data. This way any changes to the value of the data must come through the interface to the data, which is an operation. Thus we use access operations or properties.
In our person object, access to the data forename is supplied through the access operations set forename() and get forename().
The following code demonstrates a class containing some data that is hidden:
class Counter { private int Count; public Counter() { Count = 0; } public void setCount( int newVal ) { Count = newVal; } public int getCount() { return Count; } }
This piece of code is also encapsulated, showing that you can have encapsulation without data hiding, but you cannot have data hiding without encapsulation.
You can download a working example of the above code written using C# Express from here.
Inheritance represents the relationship between two objects A and B, such that B 'is a kind of' A. In UML, we show this by using the triangle symbol. We can use the objects that we have already been looking at to expand on this concept. We have a 'person' object and a 'dog' object, so how are they related? A 'dog' is an animal, more specifically a 'dog' is a mammal. A 'person' is also a mammal, so we have found a common parent for the two objects. As we have seen in previous pages, the 'person' and 'dog' objects share an amount of common information and subsequently common behaviour. It is this common data that they both get from the parent object, so we could create an object that has the common data within it.
In the previous diagram, I have tried to show one possible hierarchical structure that will allow the 'person' object and the 'dog' object to share common data logically. However, the diagram shows a flaw in our thinking, 'dogs' can be domestic or wild. A 'wild' dog will not have a name or address, so we need to clarify each type of 'dog' object with a unique identity. So we may have our 'dog' object representing domestic dogs, while we change the object name to 'WildDog' for the other object of type 'dog'.
Finally I have shown that we can inherit from our dog objects into individual breeds of dog if we wished to.
In general when talking about inheritance, we talk about a type of object. An object inherits from a parent, known as its super-type. A child is inherited from an object and is known as a sub-type.
You create a derived class by adding a colon after the name of the derived class, followed by the name of the base class:
public class Terrier : Dog
This code declares a new class, Terrier derived from Dog. You can read the colon as "derives from." The derived class inherits all the members of the base class, both member variables and methods. The derived class is free to implement its own version of a base class method or variable. It does so by marking it with the keyword new. This indicates that the derived class has intentionally hidden and replaced the base class member.
class Dog { private string species = "Dog"; public virtual string getSpecies() { return species; } } class Terrier : Dog { new private string species = "Terrier"; public override string getSpecies() { return species; } }
The species variable above overrides the base class variable of the same name.
Dog myDog = new Dog(); Terrier myTerrier = new Terrier(); Console.WriteLine("The species of myDog is {0}", myDog.getSpecies()); Console.WriteLine("The species of myTerrier is {0}", myTerrier.getSpecies()); // Output: "The species of myDog is Dog" // "The species of myTerrier is Terrier"
The Terrier constructor would invoke the constructor of its parent by placing a colon (:) after the parameter list and then invoking the base class with the keyword base. If the base class has an accessible default constructor, the derived constructor is not required to invoke the base constructor, as the default constructor is called implicitly. If the base class does not have a default constructor, every derived constructor must explicitly invoke one of the base class constructors using the base keyword.
public Terrier() : base()
If a class needs to call a method in the base class then this is achieved using the base keyword followed by the method required in the usual manner using dot notation.
base.getSpecies();
You can download a working example of the above code written using C# Express from here.
Polymorphism comes from the Greek words of poly and morphos. The literal translation means "many forms", which in essence is how we use polymorphism in programming.
The easiest way to think about polymorphism is to use an example. Suppose that we are creating a piece of software such as a drawing/image package. As we draw shapes, we want to keep a list of what we add, this way we can remove them using the undo function. So if we created a collection of shapes, we could put squares, pentagons, or any of the shapes into the collection and the behaviour that they respond to is the same for each object in the collection.
A shape has many forms. Shape is just the generic name for them.
To demonstrate this lets look at some simple shapes. The base class will obviously be named Shape:
abstract class Shape { public Point Location; public Size sz; public virtual void Draw(Graphics g) { } }
The abstract keyword enables you to create classes solely for the purpose of inheritance. An abstract class can not be used to create an object.
The virtual keyword is used to modify a method declaration, and allow it to be overridden in a derived class.
Now let's create a derived class named Square:
class Square : Shape { public override void Draw(Graphics g) { Pen p = new Pen(Color.Black, 3); g.DrawRectangle(p,Location.X,Location.Y,sz.Width,sz.Height); p.Dispose(); } }
We override the Draw method in order to draw our square on the screen.
Similarly let's create a class named Pentagon:
class Pentagon : Shape { public override void Draw(Graphics g) { Pen p = new Pen(Color.Black, 3); Point[] pts = new Point[6]; float a = 0; for (int c = 0; c < 5; c++) { pts[c] = new Point( (int)(Location.X + ((sz.Width / 2) * Math.Cos(a))+ sz.Width/2), (int)(Location.Y + ((sz.Height / 2) * Math.Sin(a)))+ sz.Height/2); a += (float)Math.PI * 2 / 5; } pts[5] = pts[0]; g.DrawPolygon(p, pts); p.Dispose(); } }
So now we have our shapes, we could have a collection class to hold our shapes called ShapeCollection.
class ShapeCollection : CollectionBase { public void Add(Shape s) { List.Add(s); } }
So we give ourselves an instance on the class and add our shapes to it.
ShapeCollection Shapes = new ShapeCollection(); for (int i = 0; i < 10; i++) { Shape s = null; if ((i % 2) == 0) s = new Square(); else s = new Pentagon(); s.Location = new Point(20 + (i * 20), 20 + (i * 20)); s.sz = new Size(20, 20); Shapes.Add(s); }
We can now get each shape to draw itself
foreach (Shape s in Shapes) s.Draw(e.Graphics);
You can download a working example of the above code written using C# Express from here.
Each part of an application, whether it is a class, structure, enumeration or other object can be set so that another part of the application can either see it or not. This is what makes encapsulation and data-hiding work, as we have previously seen and is achieved through the use of access modifiers. The most commonly used modifiers are private, protected and public.
An attribute or method marked private can only be seen from within the class that it is defined within. When using data-hiding, all attributes are marked private. If you mark something private outside of a class or struct, a compile time error will occur.
An attribute or method marked public can be accessed from any class that has access to the class members. There is no restriction on accessing public members. When using data-hiding, the methods used to access the private attributes will be declared public.
An attribute or method marked protected can only be seen from within the class that it is defined within or from within a class that inherits from the class that it is defined within. This is useful when allowing derived classes access to the attributes of the base class.
C# defines a fourth access modifier, internal, which allows members to be accessible from within the same assembly of which it is defined. This is a good method of allowing several classes to share some common data.
Finally a fifth access modifier, protected internal, this can be seen as protected OR internal, and not protected AND internal for which there is no concept.
In general, attributes of a class are designated as private and the methods that act upon the attributes are designated as public.
If you do not give a class an access modifier, it will default to public, because a class cannot be private unless it is an inner class.
If you do not give a method an access modifier, it is defaulted as private.
If you do not give an attribute an access modifier, it is defaulted as private.
Access Modifier | Restrictions |
public | No restrictions. Attributes and methods marked public are visible to any method of any class. |
private | The Attributes and methods in class A which are marked private are accessible only to methods of class A. |
protected | The Attributes and methods in class A which are marked protected are accessible to methods of class A and also to methods of classes derived from class A. |
internal | The Attributes and methods in class A which are marked internal are accessible to methods of any class in A's assembly. |
protected internal | The members in class A which are marked protected internal are accessible to methods of class A, to methods of classes derived from class A, and also to any class in A's assembly. |
Properties allow users to access class variables as if they were accessing member fields directly, while actually implementing that access through a class method.
The user wants direct access to the variables of the object and does not want to work with methods. The class designer, however, wants to hide the internal variables of his class in class members, and provide indirect access through a method. By decoupling the class variables from the methods that access those variables, the designer is free to change the internal state of the object as needed.
The code below shows how to create a private variable with its associated properties.
//private member variables private int hour; // create a property public int Hour { get { return hour; } set { hour = value; } }
We would access the properties in the following manner:
// Get the current value of hour to local variable iHour int iHour = aClass.Hour; // Increment iHour iHour++; // Write iHour back to hour aClass.Hour = iHour;
You can download a working example of the above code written using C# Express from here.
When do you make an object inherit from another object and when do you make an object become part of another object? So far we have looked at inheritance, but object oriented programming also includes another method for relationships between classes, that is when one object forms part of another object.
Aggregation and composition are very similar in nature. These relationships form a whole-part relationship that you can use to decompose objects into more manageable entities. The difference between aggregation and composition is that any object that can exist and be used independently uses aggregation, and an object that has no meaning outside of the relationship uses composition.
For example, a class named Car would have an engine. More importantly, we can recognize objects that fall into this relationship by the use of the term "is a part of", whereas inheritance uses the term "is a kind of". So back to our example, "an engine is a part of a car".
In UML, an object relationship that is formed by aggregation is drawn using an empty diamond. An object relationship that is formed using composition is drawn using a filled diamond.
The following UML diagram illustrates the concepts of aggregation and composition.
In the diagram above, the battery and the engine have no meaning outside of the car, as the car cannot work without either of them, so the relationship is formed using composition. However, a car can work without doors, so the relationship is formed using aggregation.
From the previous diagram, we can create a possible small code sample that shows the two forms of relationship.
namespace MyCars { public class Car { // Aggregation uses instances of objects created outside of // this class protected Door FrontRight; protected Door FrontLeft; protected Door RearRight; protected Door RearLeft; // inner classes used to create objects that are intrinsically // linked to the class Car protected class Engine { public int horsePower; } protected class Battery { public int voltage; } // Composition uses instances of objects that are created as // part of this object protected Engine theEngine; protected Battery theBattery; public Car() { theEngine = new Engine(); theBattery = new Battery(); } } public class Door { public int position; } }
By giving each of the members protected access modifiers, each class that inherits from Car has access to each of the members of the class Car.
namespace MyCars { // Inherit from class Car public class FordCapri : Car { public FordCapri() { theEngine.horsePower = 2000; } } }
You can download a working example of the above code written using C# Express from here.
By now you should be comfortable with what an object is and what a simple object looks like. Below is a summary of some of the concepts covered in this section.
C# is an object oriented programming language (oop).
Data and the procedures that act upon the data are encapsulated into a single object.
An object is a tangible item, something that can be seen, touched or felt, or something that can be eluded to, conceptualised, thought about.
Each object must have a unique identity.
An object has an interface that can be seen by other objects.
An object can be diagrammed using UML.
A class is an abstract representation for some particular type of object.
A class contains attributes (information) and methods (behaviour) that act upon the attributes.
Methods are functions that manipulate the data associated with an object or perform some other operation relevant to the object. A method can be thought of as a named sequence of statements.
A constructor is a method that is invoked upon instantiation of a class.
Encapsulation is a way of binding information and the behaviour performed upon the information together in a single object.
Data hiding is simply the means to remove direct access to an object's information, by providing operations that perform actions on the data.
Inheritance represents the relationship between two objects A and B, such that B 'is a kind of' A. This can be diagrammed using UML.
The class inherited from is called the 'base' class.
Polymorphism allows derived classes to take on many forms of the base class.
An access-modifier determines what can be seen by another part of the program.
Properties allow users to access class variables as if they were accessing member fields directly, while actually implementing that access through a class method.
Aggregation is an association between two objects such that 'object B is a part of object A', but object can also be its own object.
Composition is Aggregation but the object B can only exist in object A.
Term | Description |
Abstraction | The process of establishing the decomposition of a problem into simpler and more understood primitives. |
Access Methods | Methods that give access to attributes of an object. |
Access Modifier | Control the extent to which the class or part of is available to other classes. |
Aggregation | An association between two objects such that 'object B is a part of object A', but object can also be its own object. |
Attribute | A piece of data held by a class. |
Class | An abstract representation for a particular type of object. |
Composition | An association between two objects such that 'object B is a part of object A', but object B can only exist in object A. |
Constructor | A method invoked upon instantiation of a class. Memory for the class is allocated at this time. |
Data Hiding | A method of removing direct access to an object's information. |
Destructor | A method used to clean up an object when it is finished with. |
Encapsulation | A way of binding information and the behaviour perform upon the information together in a single object. |
Inheritance | The relationship between two objects A and B, such that B 'is a kind of' A. |
Interface | The part of an object that can be seen by other objects. |
Method | A function to manipulate the attributes of a class. |
Object | A software construct encapsulating attributes and methods. |
Object-oriented programming | A type of programming in which programmers define not only the data type of a data structure, but also the types of operations (methods) that can be applied to the data structure. |
Polymorphism | The ability to appear in many forms. |
Properties | Allow users to access class variables as if they were accessing member fields directly, while actually implementing that access through a class method. |
UML | Unified Modelling Language (see www.UML.org) |
Suggest candidate objects for use in a software program from a requirement.
Describe the relationship between an object and a class.
Learn the structures of a class, attributes and methods.
Understand the concepts of Encapsulation, Data-hiding, Inheritance and polymorphism.
Be able to decide which access modifier to give each member of an object.
Recognise properties used in code.
Understand when to use Aggregation and when to use Composition.
<<Back | Contents | Next>> |