Chapter 4. Classes and Objects

One of the most important topics in C# programming — in fact, the cornerstone of .NET development — is classes and objects.

Classes are essentially templates from which you create objects. In C# .NET programming, everything you deal with involves classes and objects. This chapter assumes that you already have a basic grasp of object-oriented programming. It tackles:

  • How to define a class

  • How to create an object from a class

  • The different types of members in a class

  • The root of all objects — System.Object

Classes

Everything you encounter in .NET in based on classes. For example, you have a Windows Forms application containing a default form called Form1. Form1 itself is a class that inherits from the base class System.Windows.Forms.Form, which defines the basic behaviors that a Windows Form should exhibit:

using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;

namespace Project1
{
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
        }
    }
}

Within the Form1 class, you code in your methods. For example, to display a "Hello World" message when the form is loaded, add the following statement in the Form1_Load() method:

public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
        }

        protected override void OnLoad(EventArgs e)
        {
            MessageBox.Show("Hello World!");
        }
    }

The following sections walk you through the basics of defining your own class and the various members you can have in the class.

Defining a Class

You use the class keyword to define a class. The following example is the definition of a class called Contact:

public class Contact
{
    public int ID;
    public string FirstName;
    public string LastName;
    public string Email;
}

This Contact class has four public members — ID, FirstName, LastName, and Email. The syntax of a class definition is:

<access_modifiers> class Class_Name
{
   //---Fields, properties, methods, and events---
}

Using Partial Classes

Instead of defining an entire class by using the class keyword, you can split the definition into multiple classes by using the partial keyword. For example, the Contact class defined in the previous section can be split into two partial classes like this:

public partial class Contact
{
    public int ID;
    public string Email;
}

public partial class Contact
{
    public string FirstName;
    public string LastName;
}

When the application is compiled, the C# compiler will group all the partial classes together and treat them as a single class.

Creating an Instance of a Class (Object Instantiation)

A class works like a template. To do anything useful, you need to use the template to create an actual object so that you can work with it. The process of creating an object from a class is known as instantiation.

To instantiate the Contact class defined earlier, you first create a variable of type Contact:

Contact contact1;

At this stage, contact1 is of type Contact, but it does not actually contain the object data yet. For it to contain the object data, you need to use the new keyword to create a new instance of the Contact class, a process is known as object instantiation:

contact1 = new Contact();

Alternatively, you can combine those two steps into one, like this:

Contact contact1 = new Contact();

Once an object is instantiated, you can set the various members of the object. Here's an example:

contact1.ID = 12;
            contact1.FirstName = "Wei-Meng";
            contact1.LastName = "Lee";
            contact1.Email = "[email protected]";

You can also assign an object to an object, like the following:

Contact contact1 = new Contact();
            Contact contact2 = contact1;

In these statements, contact2 and contact1 are now both pointing to the same object. Any changes made to one object will be reflected in the other object, as the following example shows:

Contact contact1 = new Contact();
            Contact contact2 = contact1;

            contact1.FirstName = "Wei-Meng";
            contact2.FirstName = "Jackson";

            //---prints out "Jackson"---
            Console.WriteLine(contact1.FirstName);

It prints out "Jackson" because both contact1 and contact2 are pointing to the same object, and when you assign "Jackson" to the FirstName property of contact2, contact1's FirstName property also sees "Jackson".

Anonymous Types (C# 3.0)

C# 3.0 introduces a new feature known as anonymous types. Anonymous types enable you to define data types without having to formally define a class. Consider the following example:

var book1 = new
            {
                ISBN = "978-0-470-17661-0",
                Title="Professional Windows Vista Gadgets Programming",
                Author = "Wei-Meng Lee",
                Publisher="Wrox"
            };

Chapter 3 discusses the new C# 3.0 keyword var.

Here, book1 is an object with 4 properties: ISBN, Title, Author, and Publisher (see Figure 4-1).

Figure 4-1

Figure 4.1. Figure 4-1

In this example, there's no need for you to define a class containing the four properties. Instead, the object is created and its properties initialized with their respective values.

Note

C# anonymous types are immutable, which means all the properties are read-only — their values cannot be changed once they are initialized.

You can use variable names when assigning values to properties in an anonymous type; for example:

var Title = "Professional Windows Vista Gadgets Programming";
            var Author = "Wei-Meng Lee";
            var Publisher = "Wrox";

            var book1 = new
            {
                ISBN = "978-0-470-17661-0",
                Title,
                Author,
                Publisher
            };

In this case, the names of the properties will assume the names of the variables, as shown in Figure 4-2.

Figure 4-2

Figure 4.2. Figure 4-2

However, you cannot create anonymous types with literals, as the following example demonstrates:

//---error---
            var book1 = new
            {
                "978-0-470-17661-0",
                "Professional Windows Vista Gadgets Programming",
                "Wei-Meng Lee",
                "Wrox"
            };

When assigning a literal value to a property in an anonymous type, you must use an identifier, like this:

var book1 = new
            {
                ISBN = "978-0-470-17661-0",
                Title="Professional Windows Vista Gadgets Programming",
                Author = "Wei-Meng Lee",
                Publisher="Wrox"
            };

So, how are anonymous types useful for your application? Well, they enable you to shape your data from one type to another. You will look into more about this in Chapter 14, which tackles LINQ.

Class Members

Variables and functions defined in a class are known as a class's members. The Contact class definition, for instance, has four members that you can access once an object is instantiated:

public class Contact
{
    public int ID;
    public string FirstName;
    public string LastName;
    public string Email;
}

Members of a class are classified into two types:

Type

Description

Data

Members that store the data needed by your object so that they can be used by functions to perform their work. For example, you can store a person's name using the FirstName and LastName members.

Function

Code blocks within a class. Function members allow the class to perform its work. For example, a function contained within a class (such as the Contact class) can validate the email of a person (stored in the Email member) to see if it is a valid email address.

Data members can be further grouped into instance members and static members.

Instance Members

By default, all data members are instance members unless they are constants or prefixed with the static keyword (more on this in the next section). The variables defined in the Contact class are instance members:

public int ID;
    public string FirstName;
    public string LastName;
    public string Email;

Instance members can be accessed only through an instance of a class and each instance of the class (object) has its own copy of the data. Consider the following example:

Contact contact1 = new Contact();
            contact1.ID = 12;
            contact1.FirstName = "Wei-Meng";
            contact1.LastName = "Lee";
            contact1.Email = "[email protected]";

            Contact contact2 = new Contact();
            contact2.ID = 35;
            contact2.FirstName = "Jason";
            contact2.LastName = "Will";
            contact2.Email = "[email protected]";

The objects contact1 and contact2 each contain information for a different user. Each object maintains its own copy of the ID, FirstName, LastName, and Email data members.

Static Members

Static data members belong to the class rather than to each instance of the class. You use the static keyword to define them. For example, here the Contact class has a static member named count:

public class Contact
{
    public static int count;
    public int ID;
    public string FirstName;
    public string LastName;
    public string Email;
}

The count static member can be used to keep track of the total number of Contact instances, and thus it should not belong to any instances of the Contact class but to the class itself.

To use the count static variable, access it through the Contact class:

Contact.count = 4;
    Console.WriteLine(Contact.count);

You cannot access it via an instance of the class, such as contact1:

//---error---
    contact1.count = 4;

Constants defined within a class are implicitly static, as the following example shows:

public class Contact
{
    public const ushort MAX_EMAIL = 5;
    public static int count;
    public int ID;
    public string FirstName;
    public string LastName;
    public string Email;
}

In this case, you can only access the constant through the class name but not set a value to it:

Console.WriteLine(Contact.MAX_EMAIL);
    Contact.MAX_EMAIL = 4; //---error---

Access Modifiers

Access modifiers are keywords that you can add to members of a class to restrict their access. Consider the following definition of the Contact class:

public class Contact
{
    public const ushort MAX_EMAIL = 5;
    public static int count;
    public int ID;
    public string FirstName;
    public string LastName;
    private string _Email;
}

Unlike the rest of the data members, the _Email data member has been defined with the private keyword. The public keyword indicates that the data member is visible outside the class, while the private keyword indicates that the data member is only visible within the class.

By convention, you can denote a private variable by beginning its name with the underscore (_) character. This is recommended, but not mandatory.

For example, you can access the FirstName data member through an instance of the Contact class:

//---this is OK---
    contact1.FirstName = "Wei-Meng";

But you cannot access the _Email data member outside the class, as the following statement demonstrates:

//---error: _Email is inaccessible---
    contact1._Email = "[email protected]";

C# has four access modifiers — private, public, protected, and internal. The last two are discussed with inheritance in the next chapter.

If a data member is declared without the public keyword, its scope (or access) is private by default. So, _Email can also be declared like this:

public class Contact
{
    public const ushort MAX_EMAIL = 5;
    public static int count;
    public int ID;
    public string FirstName;
    public string LastName;
    string _Email;
}

Function Members

A function member contains executable code that performs work for the class. The following are examples of function members in C#:

  • Methods

  • Properties

  • Events

  • Indexers

  • User-defined operators

  • Constructors

  • Destructors

Events and indexers are covered in detail in Chapters 7 and 13.

Methods

In C#, every function must be associated with a class. A function defined with a class is known as a method. In C#, a method is defined using the following syntax:

[access_modifiers] return_type method_name(parameters)
{
   //---Method body---
}

Here's an example — the ValidateEmail() method defined in the Contact class:

public class Contact
{
    public static ushort MAX_EMAIL;
    public int ID;
    public string FirstName;
    public string LastName;
    public string Email;

    public Boolean ValidateEmail() {
        //---implementation here---
        Boolean valid=true;
        return valid;
    }
}

If the method does not return a value, you need to specify the return type as void, as the following PrintName() method shows:

public class Contact
{
    public static ushort MAX_EMAIL;
    public int ID;
    public string FirstName;
    public string LastName;
    public string Email;

    public Boolean ValidateEmail() {
        //---implementation here---
        //...
        Boolean valid=true;
        return valid;
    }

    public void PrintName()
    {
        Console.WriteLine("{0} {1}", this.FirstName, this.LastName);
    }
}

Passing Arguments into Methods

You can pass values into a method using arguments. The words parameter and argument are often used interchangeably, but they mean different things. A parameter is what you use to define a method. An argument is what you actually use to call a method.

In the following example, x and y are examples of parameters:

public int AddNumbers(int x, int y) {}

When you call the method, you pass in values/variables. In the following example, num1 and num2 are examples of arguments:

Console.WriteLine(AddNumbers(num1, num2));

Consider the method named AddNumbers() with two parameters, x and y:

public int AddNumbers(int x, int y)
        {
            x++;
            y++;
            return x + y;
        }

When you call this method, you also need to pass two integer arguments (num1 and num2), as the following example shows:

int num1 = 4, num2 = 5;
            //---prints out 11---
            Console.WriteLine(AddNumbers(num1, num2));
            Console.WriteLine(num1); //---4---
            Console.WriteLine(num2); //---5---

In C#, all arguments are passed by value by default. In other words, the called method gets a copy of the value of the arguments passed into it. In the preceding example, for instance, even though the value of x and y are both incremented within the method, this does not affect the values of num1 and num2.

If you want to pass in arguments to methods by reference, you need to prefix the parameters with the ref keyword. Values of variables passed in by reference will be modified if there are changes made to them in the method. Consider the following rewrite of the AddNumbers() function:

Because C# functions can only return single values, passing arguments by reference is useful when you need a method to return multiple values.

public int AddNumbers(ref int  x, ref int  y)
        {
            x++;
            y++;
            return x + y;
        }

In this case, the values of variables passed into this function will be modified, as the following example illustrates:

int num1 = 4, num2 = 5;
            //---prints out 11---
            Console.WriteLine(AddNumbers(ref num1, ref num2));
            Console.WriteLine(num1); //---5---
            Console.WriteLine(num2); //---6---

After calling the AddNumbers() function, num1 becomes 5 and num2 becomes 6. Observe that you need to prefix the arguments with the ref keyword when calling the function. In addition, you cannot pass literal values as arguments into a method that requires parameters to be passed in by reference:

//---invalid arguments---
           Console.WriteLine(AddNumbers(4, 5));

Also note that the ref keyword requires that all the variables be initialized first. Here's an example:

public void GetDate(ref int day, ref int month, ref int year)
        {
            day = DateTime.Now.Day;
            month = DateTime.Now.Month;
            year = DateTime.Now.Year;
        }

The GetDate() method takes in three reference parameters and uses them to return the day, month, and year.

If you pass in the day, month and year reference variables without initializing them, an error will occur:

//---Error: day, month, and year not initialized---
            int day, month, year;
            GetDate(ref day, ref month, ref year);

If your intention is to use the variables solely to obtain some return values from the method, you can use the out keyword, which is identical to the ref keyword except that it does not require the variables passed in to be initialized first:

public void GetDate(out int day, out int month, out int year)
        {
            day = DateTime.Now.Day;
            month = DateTime.Now.Month;
            year = DateTime.Now.Year;
        }

Also, the out parameter in a function must be assigned a value before the function returns. If it isn't, a compiler error results.

Like the ref keyword, you need to prefix the arguments with the out keyword when calling the function:

int day, month, year;
            GetDate(out day, out month, out year);

The this Keyword

The this keyword refers to the current instance of an object (in a nonstatic class; discussed later in the section Static Classes). In the earlier section on methods, you saw the use of this :

Console.WriteLine("{0} {1}", this.FirstName, this.LastName);

While the FirstName and LastName variable could be referenced without using the this keyword, prefixing them with it makes your code more readable, indicating that you are referring to an instance member.

However, if instance members have the same names as your parameters, using this allows you to resolve the ambiguity:

public void SetName(string FirstName, string LastName)
    {
       this.FirstName = FirstName;
       this.LastName = LastName;
    }

Another use of the this keyword is to pass the current object as a parameter to another method. For example:

public class AddressBook
{
    public void AddContact(Contact c)
    {
        Console.WriteLine(c.ID);
        Console.WriteLine(c.FirstName);
        Console.WriteLine(c.LastName);
        Console.WriteLine(c.Email);
        //---other implementations here---
        //...
    }
}

The AddContact() method takes in a Contact object and prints out the details of the contact. Suppose that the Contact class has a AddToAddressBook() method that takes in an AddressBook object. This method adds the Contact object into the AddressBook object:

public class Contact
{
    public int ID;
    public string FirstName;
    public string LastName;
    public string Email;

    public void AddToAddressBook(AddressBook addBook)
    {
        addBook.AddContact(this);
    }
}

In this case, you use the this keyword to pass in the current instance of the Contact object into the AddressBook object. To test out that code, use the following statements:

Contact contact1 = new Contact();
            contact1.ID = 12;
            contact1.FirstName = "Wei-Meng";
            contact1.LastName = "Lee";
            contact1.Email = "[email protected]";

            AddressBook addBook1 = new AddressBook();
            contact1.AddToAddressBook(addBook1);

Properties

Properties are function members that provide an easy way to read or write the values of private data members. Recall the Contact class defined earlier:

public class Contact
{
    public int ID;
    public string FirstName;
    public string LastName;
    public string Email;
}

You've seen that you can create a Contact object and set its public data members (ID, FirstName, LastName, and Email) directly, like this:

Contact c = new Contact();
            c.ID = 1234;
            c.FirstName = "Wei-Meng";
            c.LastName = "Lee";
            c.Email = "[email protected]";

However, if the ID of a person has a valid range of values — such as from 1 to 9999 — the following value of 12345 would still be assigned to the ID data member:

c.ID = 12345;

Technically, the assignment is valid, but logically it should not be allowed — the number assigned is beyond the range of values permitted for ID. Of course you can perform some checks before assigning a value to the ID member, but doing so violates the spirit of encapsulation in object-oriented programming — the checks should be done within the class.

A solution to this is to use properties.

The Contact class can be rewritten as follows with its data members converted to properties:

public class Contact
{
    int _ID;
    string _FirstName, _LastName, _Email;
    public int ID
    {
        get
        {
            return _ID;
        }
        set
        {
            _ID = value;
        }
    }
public string FirstName
    {
        get
        {
            return _FirstName;
        }
        set
        {
            _FirstName = value;
        }
    }
    public string LastName
    {
        get
        {
            return _LastName;
        }
        set
        {
            _LastName = value;
        }
    }
    public string Email
    {
        get
        {
            return _Email;
        }
        set
        {
            _Email = value;
        }
    }
}

Note that the public members (ID, FirstName, LastName, and Email) have been replaced by properties with the set and get accessors.

The set accessor sets the value of a property. Using this example, you can instantiate a Contact class and then set the value of the ID property, like this:

Contact c = new Contact();
            c.ID = 1234;

In this case, the set accessor is invoked:

public int ID
    {
        get
        {
            return _ID;
        }
set
        {
            _ID = value;
        }
    }

The value keyword contains the value that is being assigned by the set accessor. You normally assign the value of a property to a private member so that it is not visible to code outside the class, which in this case is _ID.

When you retrieve the value of a property, the get accessor is invoked:

public int ID
    {
        get
        {
            return _ID;
        }
        set
        {
            _ID = value;
        }
    }

The following statement shows an example of retrieving the value of a property:

Console.WriteLine(c.ID);  //---prints out 1234---

The really useful part of properties is the capability for you to perform checking on the value assigned. For example, before the ID property is set, you want to make sure that the value is between 1 and 9999, so you perform the check at the set accessor, like this:

public int ID
    {
        get
        {
            return _ID;
        }
        set
        {
            if (value > 0 && value <= 9999)
            {
                _ID = value;
            }
            else
            {
                _ID = 0;
            };
        }
    }

Using properties, you can now prevent users from setting invalid values.

Read-Only and Write-Only Properties

When a property definition contains the get and set accessors, that property can be read as well as written. To make a property read-only, you simply leave out the set accessor, like this:

public int ID
    {
        get
        {
            return _ID;
        }
    }

You can now read but not write values into the ID property:

Console.WriteLine(c1.ID); //---OK---
c1.ID = 1234;             //---Error---

Likewise, to make a property write-only, simply leave out the get accessor:

public int ID
    {
        set
        {
            _ID = value;
        }
    }

You can now write but not read from the ID property:

Console.WriteLine(c1.ID); //---Error---
c1.ID = 1234;             //---OK---

You can also restrict the visibility of the get and set accessors. For example, the set accessor of a public property could be set to private to allow only members of the class to call the set accessor, but any class could call the get accessor. The following example demonstrates this:

public int ID
    {
        get
        {
            return _ID;
        }
        private set
        {
            _ID = value;
        }
    }

In this code, the set accessor of the ID property is prefixed with the private keyword to restrict its visibility. That means that you now cannot assign a value to the ID property but you can access it:

c.ID = 1234;              //---error---
            Console.WriteLine(c.ID);  //---OK---

You can, however, access the ID property anywhere within the Contact class itself, such as in the Email property:

public string Email
    {
        get
        {
            //...
            this.ID = 1234;
            //...
        }
            //...
    }

Partial Methods (C# 3.0)

Earlier on, you saw that a class definition can be split into one or more class definitions. In C# 3.0, this concept is extended to methods — you can now have partial methods. To see how partial methods works, consider the Contact partial class:

public partial class Contact
{
    //...
    private string _Email;
    public string Email
    {
        get
        {
            return _Email;
        }
        set
        {
            _Email = value;
        }
    }
}

Suppose you that want to allow users of this partial class to optionally log the email address of each contact when its Email property is set. In that case, you can define a partial method — LogEmail() in this example — like this:

public partial class Contact
{
    //...
}

public partial class Contact
{
    //...
private string _Email;
    public string Email
    {
        get
        {
            return _Email;
        }
        set
        {
            _Email = value;
            LogEmail();
        }
    }

    //---partial methods are private---
    partial void LogEmail();
}

The partial method LogEmail() is called when a contact's email is set via the Email property. Note that this method has no implementation. Where is the implementation? It can optionally be implemented in another partial class. For example, if another developer decides to use the Contact partial class, he or she can define another partial class containing the implementation for the LogEmail() method:

public partial class Contact
{
    partial void LogEmail()
    {
        //---code to send email to contact---
        Console.WriteLine("Email set: {0}", _Email);
    }
}

So when you now instantiate an instance of the Contact class, you can set its Email property as follows and a line will be printed in the output window:

Contact contact1 = new Contact();
    contact1.Email = "[email protected]";

What if there is no implementation of the LogEmail() method? Well, in that case the compiler simply removes the call to this method, and there is no change to your code.

Partial methods are useful when you are dealing with generated code. For example, suppose that the Contact class is generated by a code generator. The signature of the partial method is defined in the class, but it is totally up to you to decide if you need to implement it.

Note

A partial method must be declared within a partial class or partial struct.

Partial methods must adhere to the following rules:

  • Must begin with the partial keyword and the method must return void

  • Can have ref but not out parameters

  • They are implicitly private, and therefore they cannot be virtual (virtual methods are discussed in the next chapter)

  • Parameter and type parameter names do not have to be the same in the implementing and defining declarations

Automatic Properties (C# 3.0)

In the Contact class defined in the previous section, apart from the ID property, the properties are actually not doing much except assigning their values to private members:

public string FirstName
    {
        get
        {
            return _FirstName;
        }
        set
        {
            _FirstName = value;
        }
    }
    public string LastName
    {
        get
        {
            return _LastName;
        }
        set
        {
            _LastName = value;
        }
    }
    public string Email
    {
        get
        {
            return _Email;
        }
        set
        {
            _Email = value;
        }
    }

In other words, you are not actually doing any checking before the values are assigned. In C# 3.0, you can shorten those properties that have no filtering (checking) rules by using a feature known as automatic properties. The Contact class can be rewritten as:

public class Contact
{
    int _ID;
    public int ID
    {
        get
        {
            return _ID;
        }
        set
        {
            if (value > 0 && value <= 9999)
            {
                _ID = value;
            }
            else
            {
                _ID = 0;
            };
        }
    }
    public string FirstName {get; set;}
    public string LastName {get; set;}
    public string Email {get; set;}
}

Now there's no need for you to define private members to store the values of the properties. Instead, you just need to use the get and set keywords, and the compiler will automatically create the private members in which to store the properties values. If you decide to add filtering rules to the properties later, you can simply implement the set and get accessor of each property.

To restrict the visibility of the get and set accessor when using the automatic properties feature, you simply prefix the get or set accessor with the private keyword, like this:

public string FirstName {get; private set;}

This statement sets the FirstName property as read-only.

You might be tempted to directly convert these properties (FirstName, LastName, and Email) into public data members. But if you did that and then later decided to convert these public members into properties, you would need to recompile all of the assemblies that were compiled against the old class.

Constructors

Instead of initializing the individual properties of an object after it has been instantiated, it is sometimes useful to initialize them at the time of instantiation. Constructors are class methods that are executed when an object is instantiated.

Using the Contact class as the example, the following constructor initializes the ID property to 9999 every time an object is instantiated:

public class Contact
{
    int _ID;
    public int ID
    {
        get
        {
            return _ID;
        }
        set
        {
            if (value > 0 && value <= 9999)
            {
                _ID = value;
            }
            else
            {
                _ID = 0;
            };
        }
    }
    public string FirstName { get; set; }
    public string LastName { get; set; }
    public string Email { get; set; }

    public Contact()
    {
        this.ID = 9999;
    }
}

The following statement proves that the constructor is called:

Contact c = new Contact();
            //---prints out 9999---
            Console.WriteLine(c.ID);

Constructors have the same name as the class and they do not return any values. In this example, the constructor is defined without any parameters. A constructor that takes in no parameters is called a default constructor. It is invoked when you instantiate an object without any arguments, like this:

Contact c = new Contact();

Note

If you do not define a default constructor in your class, an implicit default constructor is automatically created by the compiler.

You can have as many constructors as you need to, as long as each constructor's signature (parameters) is different. Let's now add two more constructors to the Contact class:

public class Contact
{
    //...
    public Contact()
    {
        this.ID = 9999;
    }

    public Contact(int ID)
    {
        this.ID = ID;
    }

    public Contact(int ID, string FirstName, string LastName,
                   string Email)
    {
        this.ID = ID;
        this.FirstName = FirstName;
        this.LastName = LastName;
        this.Email = Email;
    }
}

When you have multiple methods (constructors in this case) with the same name but different signatures, the methods are known as overloaded. IntelliSense will show the different signatures available when you try to instantiate a Contact object (see Figure 4-3).

Figure 4-3

Figure 4.3. Figure 4-3

You can create instances of the Contact class using the different constructors:

//---first constructor is called---
 Contact c1 = new Contact();

 //---second constructor is called---
 Contact c2 = new Contact(1234);

 //---third constructor is called---
 Contact c3 = new Contact(1234, "Wei-Meng", "Lee", "[email protected]");

Constructor Chaining

Suppose that the Contact class has the following four constructors:

public class Contact
{
    //...
    public Contact()
    {
        this.ID = 9999;
    }

    public Contact(int ID)
    {
        this.ID = ID;
    }

    public Contact(int ID, string FirstName, string LastName)
    {
        this.ID = ID;
        this.FirstName = FirstName;
        this.LastName = LastName;
    }

    public Contact(int ID, string FirstName, string LastName, string Email)
    {
        this.ID = ID;
        this.FirstName = FirstName;
        this.LastName = LastName;
        this.Email = Email;
    }
}

Instead of setting the properties individually in each constructor, each constructor itself sets some of the properties for other constructors. A more efficient way would be for some constructors to call the other constructors to set some of the properties. That would prevent a duplication of code that does the same thing. The Contact class could be rewritten like this:

public class Contact
{
    //...

    //---first constructor---
    public Contact()
    {
        this.ID = 9999;
    }

    //---second constructor---
    public Contact(int ID)
    {
this.ID = ID;
    }

    //---third constructor---
    public Contact(int ID, string FirstName, string LastName)
        : this(ID)
    {
        this.FirstName = FirstName;
        this.LastName = LastName;
    }

    //---fourth constructor---
    public Contact(int ID, string FirstName, string LastName, string Email)
        : this(ID,FirstName, LastName)
    {
        this.Email = Email;
    }
}

In this case, the fourth constructor is calling the third constructor using the this keyword. In addition, it is also passing in the arguments required by the third constructor. The third constructor in turn calls the second constructor. This process of one constructor calling another is call constructor chaining.

To prove that constructor chaining works, use the following statements:

Contact c1 = new Contact(1234, "Wei-Meng", "Lee", "[email protected]");
Console.WriteLine(c1.ID);         //---1234---
Console.WriteLine(c1.FirstName);  //----Wei-Meng---
Console.WriteLine(c1.LastName);   //---Lee---
Console.WriteLine(c1.Email);      //--- [email protected]

To understand the sequence of the constructors that are called, insert the following highlighted statements:

class Contact
{
    //...

    //---first constructor---
    public Contact()
    {
        this.ID = 9999;
        Console.WriteLine("First constructor");
    }

    //---second constructor---
    public Contact(int ID)
    {
        this.ID = ID;
Console.WriteLine("Second constructor");
    }

    //---third constructor---
    public Contact(int ID, string FirstName, string LastName)
        : this(ID)
    {
        this.FirstName = FirstName;
        this.LastName = LastName;
        Console.WriteLine("Third constructor");
    }

    //---fourth constructor---
    public Contact(int ID, string FirstName, string LastName, string Email)
        : this(ID, FirstName, LastName)
    {
        this.Email = Email;
        Console.WriteLine("Fourth constructor");
    }
}

The statement:

Contact c1 = new Contact(1234, "Wei-Meng", "Lee", "[email protected]");

prints the following output:

Second constructor
Third constructor
Fourth constructor

Static Constructors

If your class has static members, it is only sometimes necessary to initialize them before an object is created and used. In that case, you can add static constructors to the class. For example, suppose that the Contact class has a public static member count to record the number of the Contact object created. You can add a static constructor to initialize the static member, like this:

public class Contact
{
    //...
    public static int count;

    static Contact()
    {
        count = 0;
        Console.WriteLine("Static constructor");
    }

    //---first constructor---
public Contact()
    {
        count++;
        Console.WriteLine("First constructor");
    }

    //...
}

When you now create instances of the Contact class, like this:

Contact c1 = new Contact();
            Contact c2 = new Contact();
            Console.WriteLine(Contact.count);

the static constructor is only called once, evident in the following output:

Static constructor
First constructor
First constructor
2

Note the behavior of static constructors:

  • A static constructor does not take access modifiers or have parameters.

  • A static constructor is called automatically to initialize the class before the first instance is created or any static members are referenced.

  • A static constructor cannot be called directly.

  • he user has no control on when the static constructor is executed in the program.

Copy Constructor

The C# language does not provide a copy constructor that allows you to copy the value of an existing object into a new object when it is created. Instead, you have to write your own.

The following copy constructor in the Contact class copies the values of the properties of an existing object (through the otherContact parameter) into the new object:

class Contact
{
    //...
    //---a copy constructor---
    public Contact(Contact otherContact)
    {
        this.ID = otherContact.ID;
        this.FirstName = otherContact.FirstName;
this.LastName = otherContact.LastName;
        this.Email = otherContact.Email;
    }
    //...
}

To use the copy constructor, first create a Contact object:

Contact c1 = new Contact(1234, "Wei-Meng", "Lee",
                                     "[email protected]");

Then, instantiate another Contact object and pass in the first object as the argument:

Contact c2 = new Contact(c1);
            Console.WriteLine(c2.ID);        //---1234---
            Console.WriteLine(c2.FirstName); //----Wei-Meng---
            Console.WriteLine(c2.LastName);  //---Lee---
            Console.WriteLine(c2.Email);     //--- [email protected]

Object Initializers (C# 3.0)

Generally, there are two ways in which you can initialize an object — through its constructor(s) during instantiation or by setting its properties individually after instantiation. Using the Contact class defined in the previous section, here is one example of how to initialize a Contact object using its constructor:

Contact c1 = new Contact(1234, "Wei-Meng", "Lee", "[email protected]");

You can also set an object's properties explicitly:

Contact c1 = new Contact();
            c1.ID = 1234;
            c1.FirstName = "Wei-Meng";
            c1.LastName = "Lee";
            c1.Email = "[email protected]";

In C# 3.0, you have a third way of initializing objects — when they are instantiated. This feature is known as the object initializers. The following statement shows an example:

Contact c1 = new Contact()
               {
                   ID = 1234,
                   FirstName = "Wei-Meng",
                   LastName = "Lee",
                   Email = "[email protected]"
               };

Here, when instantiating a Contact class, you are also setting its properties directly using the {} block. To use the object initializers, you instantiate an object using the new keyword and then enclose the properties that you want to initialize within the {} block. You separate the properties using commas.

Do not confuse the object initializer with a class's constructor(s). You should continue to use the constructor (if it has one) to initialize an object. The following example shows that you use the Contact's constructor to initialize the ID property and then the object initializers to initialize the rest of the properties:

Contact c2 = new Contact(1234)
               {
                   FirstName = "Wei-Meng",
                   LastName = "Lee",
                   Email = "[email protected]"
               };

Destructors

In C#, a constructor is called automatically when an object is instantiated. When you are done with the object, the Common Language Runtime (CLR) will destroy them automatically, so you do not have to worry about cleaning them up. If you are using unmanaged resources, however, you need to free them up manually.

When objects are destroyed and cleaned up by the CLR, the object's destructor is called. A C# destructor is declared by using a tilde (~) followed by the class name:

class Contact : Object
{
    //---constructor---
    public Contact()
    {
        //...
    }

    //---destructor---
    ~Contact()
    {
        //---release unmanaged resources here---
    }
    //...
}

The destructor is a good place for you to place code that frees up unmanaged resources, such as COM objects or database handles. One important point is that you cannot call the destructor explicitly — it will be called automatically by the garbage collector.

To manually dispose of your unmanaged resources without waiting for the garbage collector, you can implement the IDisposable interface and the Dispose() method.

Chapter 5 discusses the concept of interfaces in more detail.

The following shows the Contact class implementing the IDisposable class and implementing the Dispose() method:

class Contact : IDisposable
{
    //...
    ~Contact()
    {
        //---call the Dispose() method---
        Dispose();
     }

    public void Dispose()
    {
        //---release unmanaged resources here---
    }
}

You can now manually dispose of unmanaged resources by calling the Dispose() method directly:

Contact c1 = new Contact();
            //...
            //---done with c1 and want to dispose it---
            c1.Dispose();

There is now a call to the Dispose() method within the destructor, so you must make sure that the code in that method is safe to be called multiple times — manually by the user and also automatically by the garbage collector.

Static Classes

You can also apply the static keyword to class definitions. Consider the following FilesUtil class definition:

public class FilesUtil
{
    public static string ReadFile(string Filename)
    {
        //---implementation---
        return "file content...";
    }

    public static void WriteFile(string Filename, string content)
    {
        //---implementation---
    }
}

Within this class are two static methods — ReadFile() and WriteFile(). Because this class contains only static methods, creating an instance of this class is not very useful, as Figure 4-4 shows.

Figure 4-4

Figure 4.4. Figure 4-4

As shown in Figure 4-4, an instance of the FilesUtil class does not expose any of the static methods defined within it. Hence, if a class contains nothing except static methods and properties, you can simply declare the class as static, like this:

public static class FilesUtil
{
    public static string ReadFile(string Filename)
    {
        //---implementation---
        return "file content...";
    }

    public static void WriteFile(string Filename, string content)
    {
        //---implementation---
    }
}
The following statements show how to use the static class:
//---this is not allowed for static classes---
FilesUtil f = new FilesUtil();

//---these are OK---
Console.WriteLine(FilesUtil.ReadFile(@"C:TextFile.txt"));
FilesUtil.WriteFile(@"C:TextFile.txt", "Some text content to be written");

Use static classes when the methods in a class are not associated with a particular object. You need not create an instance of the static class before you can use it.

System.Object Class

In C#, all classes inherit from the System.Object base class (inheritance is discussed in the next chapter). This means that all classes contain the methods defined in the System.Object class.

All class definitions that do not inherit from other classes by default inherit directly from the System.Object class. The earlier Contact class definition:

public class Contact

for example, is equivalent to:

public class Contact: Object

You can create an instance of the System.Object class if you want, but it is by itself not terribly useful:

Object o = new object();

The System.Object class exposes four instance methods (see Figure 4-5):

Figure 4-5

Figure 4.5. Figure 4-5

  • Equals() — Checks whether the value of the current object is equal to that of another object. By default, the Equals() method checks for reference equality (that is, if two objects are pointing to the same object). You should override this method for your class.

  • GetHashCode() — Returns a hash code for the class. The GetHashCode() method is suitable for use in hashing algorithms and data structures, such as a hash table. There will be more about hashing in Chapter 11

  • GetType() — Returns the type of the current object

  • ToString() — Returns the string representation of an object

In addition, the System.Object class also has two static methods (see Figure 4-6):

Figure 4-6

Figure 4.6. Figure 4-6

  • Equals() — Returns true if the two objects are equal (see next section for more details)

  • ReferenceEquals() — Returns true if two objects are from the same instance

All classes that inherit from System.Object also inherit all the four instance methods, a couple of which you will learn in more details in the following sections.

Testing for Equality

Consider the following three instances of the Contact class, which implicitly inherits from the System.Object class:

Contact c1 = new Contact()
            {
                ID = 1234,
                FirstName = "Wei-Meng",
                LastName = "Lee",
                Email = "[email protected]"
            };

            Contact c2 = new Contact()
            {
                ID = 1234,
                FirstName = "Wei-Meng",
                LastName = "Lee",
                Email = "[email protected]"
            };

            Contact c3 = new Contact()
            {
                ID = 4321,
                FirstName = "Lee",
                LastName = "Wei-Meng",
                Email = "[email protected]"
            };

As you can see, c1 and c2 are identical in data member values, while c3 is different. Now, let's use the following statements to see how the Equals() and ReferenceEquals() methods work:

Console.WriteLine(c1.Equals(c2)); //---False---
            Console.WriteLine(c1.Equals(c3)); //---False---
            c3 = c1;
            Console.WriteLine(c1.Equals(c3)); //---True---

            Console.WriteLine(Object.ReferenceEquals(c1, c2)); //---False---
            Console.WriteLine(Object.ReferenceEquals(c1, c3)); //---True---

The first statement might be a little surprising to you; did I not just mention that you can use the Equals() method to test for value equality?

Console.WriteLine(c1.Equals(c2)); //---False---

In this case, c1 and c2 have the exact same values for the members, so why does the Equals() method return False in this case? It turns out that the Equals() method must be overridden in the Contact class definition. This is because by itself, the System.Object class does not know how to test for the equality of your custom class; the Equals() method is a virtual method and needs to be overridden in derived classes. By default, the Equals() method tests for reference equality.

The second statement is straightforward, as c1 and c3 are two different objects:

Console.WriteLine(c1.Equals(c3)); //---False---

The third and fourth statements assign c1 to c3, which means that c1 and c3 are now two different variables pointing to the same object. Hence, Equals() returns True:

c3 = c1;
            Console.WriteLine(c1.Equals(c3)); //---True---

The fifth and sixth statements test the reference equality of c1 against c2 and then c1 against c3:

Console.WriteLine(Object.ReferenceEquals(c1, c2)); //---False---
            Console.WriteLine(Object.ReferenceEquals(c1, c3)); //---True---

Note

If two objects have reference equality, they also have value equality, but the reverse is not necessarily true.

Implementing Equals

By default the Equals() method tests for reference equality. To ensure that it tests for value equality rather than reference equality, you need to override the Equals() virtual method.

Using the same Contact class used in the previous section, add the methods highlighted in the following code:

public class Contact
{
    public int ID;
    public string FirstName;
    public string LastName;
    public string Email;

    public override bool Equals(object obj)
    {
        //---check for null obj---
        if (obj == null) return false;

        //---see if obj can be cast to Contact---
        Contact c = obj as Contact;
        if ((System.Object)c == null) return false;

        //---check individual fields---
        return (ID == c.ID) && (FirstName == c.FirstName) &&
            (LastName == c.LastName) && (Email == c.Email);
    }

    public  bool Equals(Contact c)
    {
        //---check for null obj---
        if (c == null) return false;

        //---check individual fields---
        return (ID == c.ID) && (FirstName == c.FirstName) &&
            (LastName == c.LastName) && (Email == c.Email);
    }

    public override int GetHashCode()
    {
        return ID;
    }
}

Essentially, you're adding the following:

  • The Equals(object obj) method to override the Equals() virtual method in the System.Object class. This method takes in a generic object (System.Object) as argument.

  • The Equals(Contact c) method to test for value equality. This method is similar to the first method, but it takes in a Contact object as argument.

  • The GetHashCode() method to override the GetHashCode() virtual method in the System.Object class.

Notice that the Equals() methods essentially performs the following to determine if two objects are equal in value:

  • It checks whether the object passed is in null. If it is, it returns false.

  • It checks whether the object passed is a Contact object (the second Equals() method need not check for this). If it isn't, it returns false.

  • Last, it checks to see whether the individual members of the passed-in Contact object are of the same value as the members of the current object. Only when all the members have the same values (which members to test are determined by you) does the Equals() method return true. In this case, all the four members' values must be equal to the passed-in Contact object.

The following statement will now print out True:

Console.WriteLine(c1.Equals(c2)); //---True---

ToString() Method

All objects in C# inherits the ToString() method, which returns a string representation of the object. For example, the DateTime class's ToString() method returns a string containing the date and time, as the following shows:

DateTime dt = new DateTime(2008, 2, 29);
            //---returns 2/29/2008 12:00:00 AM---
            Console.WriteLine(dt.ToString());

For custom classes, you need to override the ToString() method to return the appropriate string. Using the example of the Contact class, an instance of the Contact class's ToString() method simply returns the string "Contact":

Contact c1 = new Contact()
            {
                ID = 1234,
                FirstName = "Wei-Meng",
                LastName = "Lee",
                Email = "[email protected]"
            };

            //---returns "Contact"---
            Console.WriteLine(c1.ToString());

This is because the ToString() method from the Contact class inherits from the System.Object class, which simply returns the name of the class.

To ensure that the ToString() method returns something appropriate, you need to override it:

class Contact
{
    public int ID;
    public string FirstName;
    public string LastName;
    public string Email;

    public override string ToString()
    {
        return ID + "," + FirstName + "," +
            LastName + "," + Email;
    }
    //...
}

In this implementation of the ToString() method, you return the concatenation of the various data members, as evident in the output of the following code:

Contact c1 = new Contact()
            {
                ID = 1234,
                FirstName = "Wei-Meng",
                LastName = "Lee",
                Email = "[email protected]"
            };

            //---returns "1234,Wei-Meng,Lee,[email protected]"---
            Console.WriteLine(c1.ToString());

Attributes

Attributes are descriptive tags that can be used to provide additional information about types (classes), members, and properties. Attributes can be used by .NET to decide how to handle objects while an application is running.

There are two types of attributes:

  • Attributes that are defined in the CLR.

  • Custom attributes that you can define in your code.

CLR Attributes

Consider the following Contact class definition:

class Contact
{
    public string FirstName;
    public string LastName;

    public void PrintName()
    {
        Console.WriteLine("{0} {1}", this.FirstName, this.LastName);
    }

    [Obsolete("This method is obsolete. Please use PrintName()")]
    public void PrintName(string FirstName, string LastName)
    {
        Console.WriteLine("{0} {1}", FirstName, LastName);
    }
}

Here, the PrintName() method is overloaded — once with no parameter and again with two input parameters. Notice that the second PrintName() method is prefixed with the Obsolete attribute:

[Obsolete("This method is obsolete. Please use PrintName()")]

That basically marks the method as one that is not recommended for use. The class will still compile, but when you try to use this method, a warning will appear (see Figure 4-7).

Figure 4-7

Figure 4.7. Figure 4-7

The Obsolete attribute is overloaded — if you pass in true for the second parameter, the message set in the first parameter will be displayed as an error (by default the message is displayed as a warning):

[Obsolete("This method is obsolete. Please use PrintName()", true)]

Figure 4-8 shows the error message displayed when you use the PrintName() method marked with the Obsolete attribute with the second parameter set to true.

Figure 4-8

Figure 4.8. Figure 4-8

Attributes can also be applied to a class. In the following example, the Obsolete attribute is applied to the Contact class:

[Obsolete("This class is obsolete. Please use NewContact")]
class Contact
{
   //...
}

Custom Attributes

You can also define your own custom attributes. To do so, you just need to define a class that inherits directly from System.Attribute. The following Programmer class is one example of a custom attribute:

public class Programmer : System.Attribute
{
    private string _Name;
    public double Version;
    public string Dept { get; set; }
    public Programmer(string Name)
    {
        this._Name = Name;
    }
}

In this attribute, there are:

  • One private member (_Name)

  • One public member (Version)

  • One constructor, which takes in one string argument

Here's how to apply the Programmer attribute to a class:

[Programmer("Wei-Meng Lee", Dept="IT", Version=1.5)]
class Contact
{
   //...
}

You can also apply the Programmer attribute to methods (as the following code shows), properties, structure, and so on:

[Programmer("Wei-Meng Lee", Dept="IT", Version=1.5)]
class Contact
{
    [Programmer("Jason", Dept = "CS", Version = 1.6)]
    public void PrintName()
    {
        Console.WriteLine("{0} {1}", this.FirstName, this.LastName);
    }
    //...
}

Use the AttributeUsage attribute to restrict the use of any attribute to certain types:

[System.AttributeUsage(System.AttributeTargets.Class |
                       System.AttributeTargets.Method |
                       System.AttributeTargets.Property)]
public class Programmer : System.Attribute
{
    private string _Name;
    public double Version;
    public string Dept { get; set; }
    public Programmer(string Name)
    {
        this._Name = Name;
    }
}

In this example, the Programmer attribute can only be used on class definitions, methods, and properties.

Structures

An alternative to using classes is to use a struct (for structure). A struct is a lightweight user-defined type that is very similar to a class, but with some exceptions:

  • Structs do not support inheritance or destructors.

  • A struct is a value type (class is a reference type).

  • A struct cannot declare a default constructor.

Structs implicitly derive from object and unlike classes, a struct is a value type. This means that when an object is created from a struct and assigned to another variable, the variable will contain a copy of the struct object.

Like classes, structs support constructor, properties, and methods. The following code shows the definition for the Coordinate struct:

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;

namespace WindowsFormsApplication1
{
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
        }
    }
}

public struct Coordinate
{
    public double latitude { get; set; }
    public double longitude { get; set; }
}

The Coordinate struct contains two properties (defined using the automatic properties feature). You can add a constructor to the struct if you want:

public struct Coordinate
{
    public double latitude { get; set; }
    public double longitude { get; set; }

    public Coordinate(double lat, double lng)
    {
        latitude = lat;
        longitude = lng;
    }
}

Note

Remember, a struct cannot have a default constructor.

Note that the compiler will complain with the message "Backing field for automatically implemented property 'Coordinate.latitude' must be fully assigned before control is returned to the caller" when you try to compile this application. This restriction applies only to structs (classes won't have this problem). To resolve this, you need to call the default constructor of the struct, like this:

public struct Coordinate
{
    public double latitude { get; set; }
    public double longitude { get; set; }

    public Coordinate(double lat, double lng)
        : this()
    {
        latitude = lat;
        longitude = lng;
    }
}
You can also add methods to a struct. The following shows the ToString()
method defined in the Coordinate struct:
public struct Coordinate
{
    public double latitude { get; set; }
    public double longitude { get; set; }

    public Coordinate(double lat, double lng)
        : this()
    {
        latitude = lat;
        longitude = lng;
    }

    public override string ToString()
    {
        return latitude + "," + longitude;
    }
}

To use the Coordinate struct, create a new instance using the new keyword and then initialize its individual properties:

public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
        }

        private void Form1_Load(object sender, EventArgs e)
{
            Coordinate pt1 = new Coordinate();
            pt1.latitude = 1.33463167;
            pt1.longitude = 103.74697;
        }
    }

Or you can use the object initializer feature:

private void Form1_Load(object sender, EventArgs e)
        {
            //...
            Coordinate pt2 = new Coordinate()
            {
                latitude = 1.33463167,
                longitude = 103.74697
            };
        }

Because structs are value types, assigning one struct to another makes a copy of its value, as the following code sample shows:

private void Form1_Load(object sender, EventArgs e)
        {
            //...
            Coordinate pt2 = new Coordinate()
            {
                latitude = 1.33463167,
                longitude = 103.74697
            };

            Coordinate pt3;
            pt3 = pt2;
            Console.WriteLine("After assigning pt2 to pt3");
            Console.WriteLine("pt2: {0}", pt2.ToString());
            Console.WriteLine("pt3: {0}", pt3.ToString());

            pt3.latitude = 1.45631234;
            pt3.longitude = 101.32355;

            Console.WriteLine("After changing pt3");
            Console.WriteLine("pt2: {0}", pt2.ToString());
            Console.WriteLine("pt3: {0}", pt3.ToString());
        }

Here's the program's output:

After assigning pt2 to pt3
pt2: 1.33463167,103.74697
pt3: 1.33463167,103.74697
After changing pt3
pt2: 1.33463167,103.74697
pt3: 1.45631234,101.32355

Notice that after changing the properties of pt3, the latitude and longitude properties of pt2 and pt3 are different.

Summary

This chapter explained how to define a class and the various components that make up a class — properties, methods, constructors, and destructors. In addition, it explored the new features in C# 3.0 — object initializers, anonymous types, and automatic properties. While you need to use the new keyword to instantiate a new object, you can also create static classes that can be used without instantiation. Finally, you saw how to use structs, the lightweight alternative to classes, that behave much like classes but are value types.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.117.159.116