Êíèãà: C# 2008 Programmer

Function Members

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:

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

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

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.

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 setand 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();

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

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.

? The 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.

The Using Statement

C# provides a convenient syntax for automatically calling the Dispose() method, using the using keyword. In the following example, the conn object is only valid within the using block and will be disposed automatically after the execution of the block.

using System.Data.SqlClient;
...
using (SqlConnection conn = new SqlConnection()) {
 conn.ConnectionString = "...";
 //...
}

Using the using keyword is a good way for you to ensure that resources (especially COM objects and unmanaged code, which will not be unloaded automatically by the garbage collector in the CLR) are properly disposed of once they are no longer needed.

Îãëàâëåíèå êíèãè


Ãåíåðàöèÿ: 3.114. Çàïðîñîâ Ê ÁÄ/Cache: 3 / 0
ïîäåëèòüñÿ
Ââåðõ Âíèç