Jump to content

C# Programming/Objects

From Wikibooks, open books for an open world

Introduction

[edit | edit source]

The .NET framework consists of several languages, all which follow the "object orientated programming" (OOP) approach to software development. This standard defines that all objects support

  • Inheritance - the ability to inherit and extend existing functionality.
  • Encapsulation - the ability to allow the user to only see specific parts, and to interact with it in specific ways.
  • Polymorphism - the ability for an object to be assigned dynamically, but with some predictability as to what can be done with the object.

Objects are synonymous with objects in the real world. Think of any object and think of how it looks and how it is measured and interacted with. When creating OOP languages, the reasoning was that if it mimics the thought process of humans, it would simplify the coding experience.

For example, let's consider a chair, and its dimensions, weight, colour and what is it made out of. In .NET, these values are called "Properties". These are values that define the object's state. Be careful, as there are two ways to expose values: Fields and Properties. The recommended approach is expose Properties and not fields.

So we have a real-world idea of the concept of an object. In terms of practicality for a computer to pass information about, passing around an object within a program would consume a lot of resources. Think of a car, how many properties that has - 100's, 1000's. A computer passing this information about all the time will waste memory, processing time and therefore a bad idea to use. So objects come in two flavours:

  • Reference types
  • Value types

Reference and Value Types

[edit | edit source]

A reference type is like a pointer to the value. Think of it like a piece of paper with a street address on it, and the address leads to your house - your object with hundreds of properties. If you want to find it, go to where the address says! This is exactly what happens inside the computer. The reference is stored as a number, corresponding to somewhere in memory where the object exists. So instead of moving an object around - like building a replica house every time you want to look at it - you just look at the original.

A value type is the exact value itself. Values are great for storing small amounts of information: numbers, dates etc.

There are differences in the way they are processed, so we will leave that until a little later in the article.

As well as querying values, we need a way to interact with the object so that some operation can be performed. Think of files - it's all well and good knowing the length of the file, but how about Read()'ing it? Therefore, we can use something called methods as a way of performing actions on an object.

An example would be a rectangle. The properties of a rectangle are:

  • Length
  • Width

The "functions" (or methods in .NET) would be:

  • Area (= Length*Width)
  • Perimeter (= 2*Length + 2*Width)

Methods vary from Properties because they require some transformation of data to achieve a result. Methods can either return a result (such as Area) or not. Like above with the chair, if you Sit() on the chair, there is no expected reaction, the chair just ... works!

System.Object

[edit | edit source]

To support the first rule of OOP - Inheritance, we define something that all objects will derive from - this is System.Object, also known as Object or object. This object defines some methods that all objects can use should they need to. These methods include:

  • GetHashCode() - retrieve a number unique to that object.
  • GetType() - retrieves information about the object like method names, the objects name etc.
  • ToString() - convert the object to a textual representation - usually for outputting to the screen or file.

Since all objects derive from this class (whether you define it or not), any class will have these three methods ready to use. Since we always inherit from System.Object, or a class that itself inherits from System.Object, we therefore enhance and/or extend its functionality. Like in the real world that humans, cats, dogs, birds, fish are all an improved and specialised version of an "organism".

Object basics

[edit | edit source]

All objects by default are reference types. To support value types, objects must instead inherit from the System.ValueType abstract class, rather than System.Object.

Constructors

[edit | edit source]

When objects are created, they are initialized by the "constructor". The constructor sets up the object, ready for use. Because objects need to be created before being used, the constructor is created implicitly, unless it is defined differently by the developer. There are 3 types of constructor:

  • Copy Constructor
  • Static Constructor
  • Default constructor - takes no parameters.
  • Overloaded constructor - takes parameters.

Overloaded constructors automatically remove the implicit default constructor, so a developer must explicitly define the default constructor, if they want to use it.

A constructor is a special type of method in C# that allows an object to initialize itself when it is created. If a constructor method is used, there is no need to write a separate method to assign initial values to the data members of an object.

Important characteristics of a constructor method:

  1. A constructor method has the same name as the class itself.
  2. A constructor method is usually declared as public.
  3. Constructor method is declared as public because it is used to create objects from outside the class in which it is declared. We can also declare a constructor method as private, but then such a constructor cannot be used to create objects.
  4. Constructor methods do not have a return type (not even void).
  5. C# provides a default constructor to every class. This default constructor initializes the data members to zero. But if we write our own constructor method, then the default constructor is not used.
  6. A constructor method is used to assign initial values to the member variables.
  7. The constructor is called by the new keyword when an object is created.
  8. We can define more than one constructor in a class. This is known as constructor overloading. All the constructor methods have the same name, but their signatures are different, i.e., number and type of parameters are different.
  9. If a constructor is declared, no default constructor is generated.

Copy Constructor

[edit | edit source]

A copy constructor creates an object by copying variables from another object. The copy constructor is called by creating an object of the required type and passing it the object to be copied.

In the following example, we pass a Rectangle object to the Rectangle constructor so that the new object has the same values as the old object.

using System;

namespace CopyConstructor
{
    class Rectangle
    {
        public int length;
        public int breadth;

        public Rectangle(int x, int y)         // constructor fn
        {
            length = x;
            breadth = y;
        }

        public Rectangle(Rectangle r)
        {
            length = r.length;
            breadth = r.breadth;
        }

        public void display()
        {
            Console.WriteLine("Length = " + length);
            Console.WriteLine("Breadth = " + breadth);
        }
    }   // end of class Rectangle

    class Program
    {
        public static void Main()
        {
            Rectangle r1 = new Rectangle(5, 10);
            Console.WriteLine("Values of first object");
            r1.display();

            Rectangle r2 = new Rectangle(r1);
            Console.WriteLine("Values of second object");
            r2.display();

            Console.ReadLine();
        }
    }
}

Static Constructor

[edit | edit source]

A static constructor is first called when the runtime first accesses the class. Static variables are accessible at all times, so the runtime must initialize it on its first access. The example below, when stepping through in a debugger, will show that static MyClass() is only accessed when the MyClass.Number variable is accessed.

C# supports two types of constructors: static constructor and instance constructor. Whereas an instance constructor is called every time an object of that class is created, the static constructor is called only once. A static constructor is called before any object of the class is created, and is usually used to initialize any static data members of a class.

A static constructor is declared by using the keyword static in the constructor definition. This constructor cannot have any parameters or access modifiers. In addition, a class can only have one static constructor. For example:

using System;
using System.Collections.Generic;
using System.Text;

namespace StaticConstructors
{
    class Program
    {
        static void Main(string[] args)
        {
            int i = 0;
            int j = 0;
            Console.WriteLine("Static Number = " + MyClass.Number);
        }
    }

    class MyClass
    {
        private static int _number;
        public static int Number { get { return _number; } }
        static MyClass()
        {
            Random r = new Random();
            _number = r.Next();
        }
    }
}

Default Constructor

[edit | edit source]

The default constructor takes no parameters and is implicitly defined, if no other constructors exist. The code sample below show the before, and after result of creating a class.

// Created by the developer
class MyClass
{
}

// Created by the compiler
class MyClass : System.Object
{
     public MyClass() : base()
     {
     }
}

Overloaded Constructors

[edit | edit source]

To initialize objects in various forms, the constructors allow customization of the object by passing in parameters.

 class MyClass
    {
        private int _number;
        public int Number { get { return _number; } }
        
        public MyClass()
        {
            Random randomNumber = new Random();
            _number = randomNumber.Next();
        }
  
        public MyClass(int seed)
        {
            Random randomNumber = new Random(seed);
            _number = randomNumber.Next();
        }
   }

Calling other constructors

[edit | edit source]

To minimise code, if another constructor implements the functionality better, you can instruct the constructor to call an overloaded (or default) constructor with specific parameters.

 class MyClass
    {
        private int _number;
        public int Number { get { return _number; } }
        
        public MyClass() : 
             this ( DateTime.Now.Milliseconds ) // Call the other constructor passing in a value.
        {
            
        }
  
        public MyClass(int seed)
        {
            Random r = new Random(seed);
            _number = r.Next();
        }
   }

Base classes constructors can also be called instead of constructors in the current instance

 class MyException : Exception
    {
        private int _number;
        public int Number { get { return _number; } }
        
        public MyException ( int errorNumber, string message, Exception innerException)
                 : base( message, innerException )
        {
             _number = errorNumber;
        }
   }

Destructors

[edit | edit source]

As well as being "constructed", objects can also perform cleanup when they are cleared up by the garbage collector. As with constructors, the destructor also uses the same name as the class, but is preceded by the tilde (~) sign. However, the garbage collector only runs when either directly invoked, or has reason to reclaim memory, therefore the destructor may not get the chance to clean up resources for a long time. In this case, look into use of the Dispose() method, from the IDisposable interface.

Destructors are recognised via the use of the ~ symbol in front of a constructor with no access modifier. For example:

 class MyException : Exception
    {
        private int _number;
        public int Number { get { return _number; } }
        
        public MyException ( int errorNumber, string message, Exception innerException)
                : base( message, innerException )
        {
             _number = errorNumber;
        }

        ~MyException()
        {
        }
   }