Home > C# .NET > C# .NET Tutorial 3: Classes

C# .NET Tutorial 3: Classes

Hey welcome back to the third tutorial to this series on C#. To be able to follow this tutorial I am assuming that you have a basic grasp of the C# syntax and most importantly that you have good knowledge of the object oriented approach. I also recommend that you go through my first and second tutorial. In this tutorial I am going to discuss how classes are constructed.

Inheritance

In the .NET framework each class has a method, ToString, which performs the same task for each class. Other classes support the same operators, such as comparing two instances of a class for equality. This consistency is achieved through inheritance and interfacing. Let’s start with inheritance. Through inheritance you can create classes from existing ones and inherit their functionality plus adding your (new) functionality. Let’s get dirty straight away. In this example I will create a custom exception class that inherits from System.ApplicationException:

class CustomException : System.ApplicationException // : means that it will inherit from another class
{
    public override string Message
    {
        get {return "A custom error occurred in the application";}
    }
}

Because this new custom exception inherits from the base class you can throw and catch this exception as shown here:

try
{
    throw new CustomException();
}
catch (CustomException ce)
{
    Console.WriteLine(ce.Source + " " + ce.Message);
    //note that the Source was inherited from the base class while the Message was overridden in the new
    //custom exception we created
}

Another benefit of inheritance is that you can use classes that inherit from the same base class interchangeably. For example the System.Drawing.Brush is the base class for five other classes, which are HatchBrush, LinearGradientBrush, PathGradientBrush, SolidBrush, and TextureBrush. The Graphics.DrawRectangle method requires an instance of a Brush as one of the parameters. Any one instance of the five brushes mentioned can be used as a parameter.

Interface

An interface provides sort of an agreement or contract that defines a set of members that all classes that implement that interface must provide. For example the IComparable interface defines a CompareTo method, which enables two instances of the class to be compared for equality. Every class that implements the IComparable interface needs to have the CompareTo method. For a class to implement a particular interface you use the following:

class ExampleClass : IExampleInterface
{
}

Note that Visual Studio (or Visual C# Express Edition) can generate the methods that you need to implement from the Interface. This is done by right clicking the Interface declaration, then click on Implement Interface, and then on Implement Interface again.

The most commonly used interfaces in .NET are the following:

  • IComparable – implemented by types whose values can be ordered such as int and string.
  • IDisposable – contains methods that are used to dispose of the objects.
  • IConvertible – enables a class to be converted to a base type such as boolean, double, string, etc.
  • ICloneable – supports copying of objects.
  • IEquatable – allows you to compare two instances of a class for equality.
  • IFormattable – enables you to convert an object to a formatted string.

Let us create an interface now:

interface IShape
{
    double Area(); //computes the area of a shape
    double Volume(); //computes the volume of a shape
    string Name { get; }
}

This is an interface for shape classes. Each shape must have a name and it must have methods to compute the area and volume.

In Visual Studio you can create a class and then extract an interface from it. Right-click the class, click Refactor, and then click Extract Interface.

Classes can also implement multiple interfaces.

Partial Classes

Partial classes are new in .NET 2.0. They allow you to split a class into multiple source files. The Windows form is an exmple of a partial class in .NET. The code generated by the form designer (while designing a windows form) is “hidden” in a partial class named  form.Designer.cs (where form is the name of your windows form).

Generics

Generics were also introduced in .NET 2.0 and are part of the .NET type system that allows you to define a type while leaving some of the details unspecified. Instead of specifying the types of parameters or member classes, you can allow the code that uses your type to specify it. This allows your code to tailor your type to its own specific needs. The .NET framework contains generic classes in the System.Collections.Generic such as the Dictionary, Queue, SortedDictionary, and SortedList. These classes work similar to the non-generic counterparts in System.Collections but they have better performance and type safety.

In the previous .NET versions developers used the Object to cast other classes. Generics offer advantages over the Object class:

  • Improved performance – casting requires boxing and unboxing, which we will cover in future tutorials, that require the processor’s attention therefore it slows performance. Generics do not require these.
  • Reduced run-time errors – the compile will not detect type errors when casting to and from an Object. For example if a string is cast to an Object and then that Object is cast to an integer an error occurs at run-time. Generics allow the compiler to catch this type of error.

To better understand generics let us build an example. We will create two classes, one that uses the Object and the other that uses generics:

class Obj //using Object
{
    public Object a;
    public Object b;

    public Obj(Object a, Object b)
    {
        this.a = a;
        this.b = b;
    }
}

class Gen<A,B> //using generics
{
    public A a;
    public B b;

    public Gen(A a, B b)
    {
        this.a = a;
        this.b = b;
    }
}

Note that generic code is valid only if it will compile for every possible constructed instance of the generic. This means that you are limited to the capabilities of the base Object class. Therefore you can call the ToString method (because this is implemented by all types) but you cannot use the + and > operators.

To understand further how generics are used I will present an application that uses the Obj and Gen classes we created above:

//add 2 strings using the Obj class
Obj o = new Obj("Hello","Blog");
Console.WriteLine((string)o.a + (string)o.b);

//add 2 strings using the Gen class
Gen<string,string> g = new Gen<string,string&gt("Hello", "Blog");
Console.WriteLine(g.a + g.b);

//adding an int and a double with Obj class
Obj o2 = new Obj(1, 1.2905);
Console.WriteLine((int)o2.a + (double)02.b); 

//adding an int and a double with Gen class
Gen<int,double> g2 = new Gen<int,double>(1, 1.2905);
Console.WriteLine(g2.a + g2.b);

As such the two classes do the same thing. However when we use a generic class we do not need to cast and therefore it is much more efficient. With the generic class we also ensure that any type errors occur at run-time.

By now you may be saying: Generics are limited if we can write code based on the capabilities of the Object base class. Well that is where constraints come into play. Constraints place requirements on the types that the consuming code can substitute for the generic.

Generics support four types of constraints:

  • Interface – allow only types that implement a certain interface.
  • Base class – allow only types that inherit from a particular base class.
  • Constructor – requires types that use your generic to implement a parameterless constructor.
  • Reference or value type – require types to be either a reference or value type.

To apply generics we use the where clause. In the following example we will create a generic that will allow types that implement the IComparable interface:

class CompareGeneric<T> where T : IComparable
{
    public T t1;
    public T t2;

    public CompareGeneric (T t1, T t2)
    {
        this.t1 = t1;
        this.t2 = t2;
    }

    public T Max()
    {
        if (t2.CompareTo(t1) < 0)
        {
            return t1;
        }
        else
        {
            return t2;
        }
    }
}

This code will only work because of the statement where T : IComparable. If we remove it the compiler will return an error indicating that generic type T does not contain a definition for CompareTo.

Events

Most applications are not linear. They respond to events such as a user clicking a button or when an incoming message is received on a server, etc.

An event is a message sent by an object to indicate the occurrence of an action. The action can be caused by user interaction, or it could be triggered by some other program logic. The object that raises the event is called the event sender. The object that handles the event is the event receiver.

The event sender class does not know which object or method will receive the event. We need an intermediary to act as a pointer between the sender and the receiver. The .NET framework defines a special type that provides the functionality of a function pointer. This is called a delegate.

A delegate is a class that can hold a reference to a method. A delegate has a signature and can hold references to methods with the same signature. It is equivalent to a type-safe function pointer or callback. The following shows a delegate function declaration:

public delegate void ClickEventHandler(object sender, EventArgs e);

The standard signature for an event handler delegate defines a method that does not return a value (void), whose first parameter is of type Object (referring to the instance that raised the event), and the second parameter is derived from the EventArgs and holds the event data.

To associate the event with a method to handle the event, add an instance of the delegate to the event. The event handler is called whenever the event occurs.

Here is an example to make things clearer. We will see how to respond to an event:

  • Create a method to respond to the event. It must match the Delegate signature:
    private void button_click(object sender, EventArgs e)
    {
        //put code here
    }
  • Add the event handler as an instance of the delegate:
    this.button1.click += new System.EventHandler(this.button_click); //System.EventHandler is the delegate
  • When the event occurs that method will be called.

To raise an event you must do the following steps:

  • Create a delegate:
    public delegate void MyEventHandler(object sender, EventArgs e);
  • Create an event member:
    public event MyEventHandler MyEvent;
  • Invoke the delegate within a method when you need to raise the event:
    MyEventHandler handler = MyEvent;
    EventArgs e = new EventArgs();
    
    if (handler != null)
    {
        //Invokes the delegate
        handler(this, e);
    }
    //C# checks whether handler is null

Attributes

Attributes describe a type, method, or property in a way that can be programmatically queried using a technique called Reflection.  Some common uses of attributes are to:

  • specify which security privileges a class requires
  • specify security privileges to refuse to reduce security risk
  • declare the assembly by providing a title, description, and copyright notice
  • declare capabilities, such as supporting serialization.

Attribute types derive from the System.Attribute base class and are specified using [ and ]. The following shows how to add  assembly attributes:

[assembly: AssemblyTitle("blog")]
[assembly: AssemblyDescription("This is a blog")]
[assembly: AssemblyCompany("scerrimark.com")]
[assembly: AssemblyProduct("C# tutorial")]
[assembly: AssemblyTrademark("")]

Visual Studio automatically creates some standard attributes for your assembly when you create a project, including title, company, description, guide, and version.

Attributes can also declare requirements and capabilities. For example to enable a class to be serialized you must add the Serializable attribute:

[Serializable]
class Account
{
}

The following code uses attributes to declare that it needs to read the C:\boot.ini file. Because of this attribute, the run-time will throw an exception if it lacks the specified privilege:

using System;
using System.Security.Permissions;

[assembly: FileIOPermissionAttribute(SecurityAction.RequestMinimum, Read=@"C:\boot.ini"]
namespace DeclarativeExample
{
    class ExampleClass
    {
    }
}

Type Forwarding

Suppose you have created an assembly named MyLibrary which contains a class named MyClass. Let’s say, some of your applications refer to the MyLibrary assembly and utilizes MyClass. In a later phase, you decide to move MyClass from MyLibrary to a newly created assembly called MyAdvancedLibrary. If you ship a new version of MyLibrary (which now doesn’t have MyClass), along with MyAdvancedLibrary, then your existing applications looking for MyClass in MyLibrary will not find the MyClass and end up with errors. The solution is type forwarding.

Type forwarding is an attribute that allows you to move a type from one assembly into another assembly, and to do so in such a way that it is not necessary to recompile clients that consume the first assembly. After a component ships and is being used by client applications, you can use type forwarding to move a type from the component into another assembly and ship the updated component, and the client applications will still work without being recompiled. To move a type from one class library to another:

  • Add a TypeForwardedTo attribute to the source class library assembly.
  • Cut the type definition from the source class library.
  • Paste the definition into the destination class library.
  • Rebuild both libraries.

The following code shows the attribute declaration used to move MyClass to the MyAdvancedLibrary class library.

using System.Runtime.CompileServices;
[assembly: TypeForwardedTo(typeof(MyAdvancedLibrary.MyClass))]

Well for today that’s enough!! So how are you feeling? Are you finding these tutorials good? Well leave a comment below. I ‘ll see you for the next tutorial: Converting between types.


Categories: C# .NET