Methods, classes, objects and organizing everything into files

Now that we have quite a solid knowledge of writing code in C#, it is time that we learned how to structure that code.

Until now we have written all of our code inside the Main method of the Program class in our console application. In real applications the code will be structured into other methods and other objects.

New static method

Let us consider an application that is supposed to calculate an age of a person given their birthday. In such application the functionality to calculate the age based on a birthday could be encapsulated in a dedicated method. Since the Main method itself is a static method and therefore does not require an instance of a Program class to be accessed, let us create another static method to calculate the age. The full code (the contents of the Program class) would be as follows:

static void Main(string[] args)
{
    DateTime birthday = new DateTime(2000, 2, 1);

    Console.WriteLine(GetAge(birthday));

    Console.ReadLine();
}

static int GetAge(DateTime birthday)
{
    DateTime now = DateTime.Now;

    int age = now.Year - birthday.Year;

    if (birthday.AddYears(age) > now)
        age--;

    return age;
}

Again, you can see that both the Main and the GetAge methods are static. The Main method does not return anything. The GetAge method returns an integer - we specified this by the “int” keyword before the name of the method. In the parentheses after the name of the method we also specified a required parameter of type DateTime. The value of this parameter can be accessed from inside the method as a variable named “birthday”.

The body of the GetAge method calculates the age from the given birthday and then, at the end, ends the execution by returning the result.

You can see how the Main method uses this GetAge method. It creates a new birthday, passes it to the GetAge method and writes out the result into the console.

Method overloads

You can create more than one method of the same name inside the same class. The only thing you have to make sure of is that these methods take different parameters. To illustrate how this could be useful, let us look at the previous example.

In the previous example the main method first creates a DateTime based on a year number, month number, and a day number. This DateTime is then passed to the GetAge method. Let us suppose now that we would also require a method to calculate age based on a separate year number, month number, and a day number. We could create another version of the GetAge method to support this scenario. This is called overloading a method. Such an implementation can be seen in the following example:

static void Main(string[] args)
{
    DateTime birthday = new DateTime(2000, 2, 1);
 
    // Using the GetAge(DateTime birthday) overload
    Console.WriteLine(GetAge(birthday));
 
    // Using the GetAge(int year, int month, int day)
    Console.WriteLine(GetAge(2000, 2, 1));
 
    Console.ReadLine();
}
 
static int GetAge(DateTime birthday)
{
    DateTime now = DateTime.Now;
 
    int age = now.Year - birthday.Year;
 
    if (birthday.AddYears(age) > now)
        age--;
 
    return age;
}
 
static int GetAge(int year, int month, int day)
{
    DateTime birthday = new DateTime(year, month, day);
 
    return GetAge(birthday);
}

The new method overload takes three integer parameters, constructs its own birthday DateTime and then, even though it could just repeat the code that is inside the previous GetAge overload, it chooses to call that overload instead. This demonstrates another concept – code reuse. In a perfect world a single functionality is implemented at no more than one place in the entire application.

Looking at the implementation of the Main method, please note that there is nothing special you need to do to indicate which method overload you would like to use. The .NET Framework will decide this itself by the number and types of parameters you are passing to it. That is the reason why you can have as many overloads of one method as you like as long as they all differ in the number and types of their parameters. They cannot differ by only a return type for example.

New static class

When structuring the application into separate methods is not enough, we can group some of these methods inside a new class.

The following example shows the full code (now the contents of the entire namespace) in which a new static class is created:

class Program
{
    static void Main(string[] args)
    {
        DateTime birthday = new DateTime(2000, 2, 1);
 
        Console.WriteLine(Tools.GetAge(birthday));
 
        Console.WriteLine(Tools.GetAge(2000, 2, 1));
 
        Console.ReadLine();
    }
}
 
static class Tools
{
    public static int GetAge(DateTime birthday)
    {
        DateTime now = DateTime.Now;
 
        int age = now.Year - birthday.Year;
 
        if (birthday.AddYears(age) > now)
            age--;
 
        return age;
    }
 
    public static int GetAge(int year, int month, int day)
    {
        DateTime birthday = new DateTime(year, month, day);
 
        return GetAge(birthday);
    }
}

New static class Tools has been created. Please note how the methods of this new class are accessed from the main method of the Program class. As they are now outside the class from where we call them, they need to be prefixed by the class name.

Also, in order to access methods of another class, they need to be marked as public in that class. If no access modifier keyword (such as public) is used, they are assumed to be private and therefore accessible only within the class were they have been declared.

New instance class

In the previous example we have created a new static class. We have explicitly declared that class to be static by using the “static” keyword.

As we have already discussed, when the “static” keyword is used in a method declaration, that method is called directly on the class, not on its instance. The “static” keyword used in the class declaration, however, has a slightly different meaning. It prohibits anyone from making an object instance out of this class.

We made the Tools class in the previous example static as it would make no sense to create an object instance from it. Let us now consider an example where we would expect object instances to be created:

class Program
{
    static void Main(string[] args)
    {
        // Instantiate person object
        Person person = new Person("Joe");
 
        // Write out its property
        Console.WriteLine(person.Name);
 
        Console.ReadLine();
    }
}

class Person
{
    // Private data field
    string name;

    // Public read-write property
    public string Name
    {
        get
        {
            return name;
        }
        set
        {
            name = value;
        }
    }

    // Public constructor
    public Person(string name)
    {
        // Using "this" to access the data field
        this.name = name;
    }
}

This example declares a new class called Person. The class is not prefixed with the “static” keyword – it is expected that instances of this class will be created.

The class itself stores its data in something that we call a data field. It is a common object oriented programming practice that these data fields are private (again, without the presence of access modifier, it is assumed to be private). Only the class itself can then read or write into its own data fields.

To allow a certain access to some of the data outside the class, properties should be used. Since properties are meant to allow access from outside the class, they are generally public. Our example provides both read and write access to the underlying name data field.

To allow a read-only access to the underlying name data field, we would just leave out the set block in the property definition. To keep the content of the name data field only for the internal use of the Person class, we would leave out the declaration of the Name property altogether.

The last part of the newly created Person class is called “constructor”. It looks almost as a method as it can take parameters. It does not have a return type however and its name must match the name of the class. The constructor can process its parameters as it sees fit. In our example it saves the name parameter inside the name data field.

Please note that when the name of the parameter and the name of the data field are identical, we can use the “this” keyword to specify that we mean to use the data field. Unless the “this” keyword is used and a parameter (or perhaps a variable) of the same name exists, they take precedence.

To understand what “constructor” really is, let us take a look at how the object is instantiated in the Main method. You can see a new reference variable of type Person is being declared. The new variable is also immediately initialized with an instance of the Person object.

The Person object is instantiated using the “new” keyword. That is the most usual way to create an instance of an object. When the instance is being created with the help of the “new” keyword, it allows for something that looks like parameters to be passed inside it. This is in fact a call to a constructor.

Now that you know how the constructor is used, it comes as no surprise that the constructor code is the first thing that gets executed when the object is instantiated. Since the constructor can take parameters, it allows the object to be preconfigured (or constructed) depending on the need of the code that is instantiating it. In our example the Person object will already have its data field name filled with “Joe”.

The last thing to note in this example is how the Main method writes out the name of the person to the console. To access the name, as it is accessing it from outside the Person class itself, it needs to use the property that we have declared.

Object inheritance

Object inheritance is quite a large topic. The content of this website expects that, with your previous knowledge of object oriented programming, you understand at least its basic concepts.

Every new class can inherit its functionality from another class. In fact, every class that we have created in all of our previous examples so far inherits from the “System.Object” class. It is a fixed convention in the C# language that all object classes inherit from this class.

Overriding a method

If a class we are inheriting from declares a method in a way that it can be overridden by its successor, we can do so by using the “overwrite” keyword. The “System.Object” already provides such a method called “ToString”. We have already seen the use of this method in the previous examples. The method simply tries to represent the object in the form of a string. Since the “System.Object” class allows us to override this method and since “System.Object” is the predecessor of every object, we can change its functionality by overwriting it in any class we create.

Let us go back to the example of our Person class and on top of everything that is in there already, let us add a ToString method override. We will then use that override in the Main method. The modified example is as follows:

class Program
{
    static void Main(string[] args)
    {
        Person person = new Person("Joe");
 
        Console.WriteLine(person.ToString());
        Console.WriteLine(person);
 
        Console.ReadLine();
    }
}
 
class Person
{
    string name;
 
    public string Name
    {
        get
        {
            return name;
        }
        set
        {
            name = value;
        }
    }
 
    public Person(string name)
    {
        this.name = name;
    }
 
    public override string ToString()
    {
        return "A person named " + name;
    }
}

Please notice that in order to use the ToString method we can either call the ToString method directly as in the first Console.WriteLine call or we can have C# use it implicitly.

Inheriting from your own predecessor

In the example above we have inherited from the “System.Object” class. We can also create our own class from which another class can inherit. The following example shows a Teacher class inheriting from a Person class:

class Program
{
    static void Main(string[] args)
    {
        Teacher teacher = new Teacher("Joe", 1);
 
        Console.WriteLine(teacher.Name);
         Console.WriteLine(teacher.QualificationLevel);

        Console.ReadLine();
    }
}

class Teacher : Person
{
    int qualificationLevel;
 
    public int QualificationLevel
    {
        get
        {
            return qualificationLevel;
        }
    }
 
    public Teacher(string name, int qualificationLevel)
        : base(name)
    {
        this.qualificationLevel = qualificationLevel;
    }
}
 
class Person
{
    string name;
 
    public string Name
    {
        get
        {
            return name;
        }
        set
        {
            name = value;
        }
    }
 
    public Person(string name)
    {
        this.name = name;
    }
}

You can specify that a class inherits from a different class right in its declaration. This is done by using the “: PredecessorClass” notation. In our example this notation is: “: Person”.

The Teacher class has all the functionality of a Person class. On top of that it adds a field to store a qualification level. There is a new property to access the qualification level from outside with a read-only access. The Teacher class also has a constructor that takes the name of the Teacher and their qualification level. The Teacher constructor first calls a constructor of the Person class internally and have it process the name parameter. This is done by the “: base(parameters)” notation. In our example “: base(name)” is used.

If you look inside the Main method, you can see a new Teacher class being instantiated into an object. Since the Teacher inherits all the functionality of Person, the code can also use the public Name property declared in the Person class.

Overriding virtual and providing new methods

In order to be able to override a method, it has to be marked as virtual by using a “virtual” keyword in its predecessor.

The following example shows how to override a method when it has been marked as virtual:

class Program
{
    static void Main(string[] args)
    {
        Person person = new Person();
        Teacher teacher = new Teacher();
        Console.WriteLine(person.GetHelloMessage());
        Console.WriteLine(teacher.GetHelloMessage());

        Console.ReadLine();
    }
}

class Teacher : Person
{
    public override string GetHelloMessage()
    {
        return "Hello from Teacher";
    }
}
 
class Person
{
    public virtual string GetHelloMessage()
    {
        return "Hello from Person";
    }
}

This example will write out a different hello message from each of the objects on the console.

If the predecessor does not mark a method as virtual, it cannot be overridden. The only option to provide a different implementation of such a method is to create a new method of the same name. This in effect hides the implementation of the predecessor and allows the new class to specify a new implementation. To make sure this feature is not used by accident, you should specify a “new” keyword with every method that you want to provide a new implementation for. If you do not do that, your code will compile but you will get a compiler warning.

The following example shows how to provide a new implementation when the predecessor has not marked the method as virtual:

class Program
{
    static void Main(string[] args)
    {
        Person person = new Person();
        Teacher teacher = new Teacher();
        Console.WriteLine(person.GetHelloMessage());
        Console.WriteLine(teacher.GetHelloMessage());

        Console.ReadLine();
    }
}

class Teacher : Person
{
    public new string GetHelloMessage()
    {
        return "Hello from Teacher";
    }
}
 
class Person
{
    public string GetHelloMessage()
    {
        return "Hello from Person";
    }
}

The result on the console will be identical to the one of the previous example.

Abstract classes

In the previous examples we were able to create an instance from both Person and Teacher. Sometimes it is beneficial to create a class that is meant to be used as predecessor for other objects but it should never be used to create an instance of itself.

These classes usually provide public methods, properties, etc. but they themselves provide no implementation for them. These public methods, properties, etc. constitute something other classes have to implement and they in a sense specify a unified way how other outside objects can interact with them. This is also called an object contract.

All of the objects inheriting from this class, also called an abstract class, share its contract. This is many times invaluable for certain object oriented designs.

The following example shows an abstract Printer class with an abstract Print method. The HpPrinter and CanonPrinter then inherit from this abstract class and provide their own implementations of a Print method:

class Program
{
    static void Main(string[] args)
    {
        List<Printer> printers = new List<Printer>();

        printers.Add(new HpPrinter());
        printers.Add(new CanonPrinter());

        foreach (Printer printer in printers)
        {
            printer.Print("Text to print");
        }
    }
}

class HpPrinter : Printer
{
    public override void Print(string textToPrint)
    {
        // Printer-specific code to print the text on this HP printer
    }
}

class CanonPrinter : Printer
{
    public override void Print(string textToPrint)
    {
        // Printer-specific code to print the text on this Canon printer
    }
}

abstract class Printer
{
    public abstract void Print(string textToPrint);
}

The example creates a list of printers, fills it with concrete objects of specific printer types and then simulates sending a text to print on these printers. Thanks to implicit casting that we have already discussed before and now this common contract that the abstract Printer class provides, calling the Print method on all printers can be done as easily as calling it through a Printer-typed variable reference in a “foreach” loop.

Organizing classes into files

In all of the previous examples we put all the code inside the only default code file that had been automatically created for us – the “Program.cs” file. In real world applications this is something we want to avoid.

To structure our application better, it is usually best to stick to one rule of thumb: “Put every class into its separate file and name the file exactly the same as your class.”

This would mean that the “Program.cs” file was meant only for our Program class and its Main method. All other classes like the “Tools” class or the ”Person” class from our previous examples should be placed into separate “Tools.cs” and “Person.cs” files.

To create a new file inside your project, right-click on the project name inside the Solution Explorer in Visual Studio. This shows up a context menu where you select “Add” and then “Class”. In the new window, type the name of the class and click “Add”. A new code file will be created and Visual Studio automatically opens it up for you. It will also get pre-populated by the code needed to start you off with your new class.

Continue to: Naming conventions

Go up to: Basics


Should you have any questions or found a mistake that needs correcting, feel free to send an email to: info [at] mycsharp [dot] net


Advertisements :