Declaring classes in C

Team-Fly    

 
.NET and COM Interoperability Handbook, The
By Alan Gordon
Table of Contents
Chapter Four.  A Quick Introduction to C#

Declaring classes in C#

The syntax for declaring classes is a major point of departure between C# and C++. With C#, the entire definition of a class lives in a single file. In C++, you would typically have both a header file that contains a declaration of the class (and may optionally contain the inline declaration of some of the simpler methods ) and an implementation file usually with the extension .c or .cpp that contains the bulk of the implementation of the class. You typically distribute the header file to users of your class along with a compiled ".dll" or ".lib" file. The header file functions as a sort of metadata for the classes in the ".dll" or ".lib" file. The following program shows how you would declare and use a class in C#:

 namespace ClassApp {   using System;   public class Employee   {     public Employee(int id,string name)     {       this.name=name;       this.ID=id;     }     public decimal GetSalary()     {       return 500.00m;     }     int ID;     public string name;   }   public class TestClass   {     public static int Main(string[] args)     {       Employee emp=new Employee(80,"Alan Gordon");       Console.WriteLine(emp.name);       return 0;     }   } } 

Let's examine the code more closely. You begin the class declaration by specifying the accessibility level for the class (public in this case), the keyword class, and then the class name as follows :

 public class Employee 

The public accessibility qualifier means that the class is exposed to the outside world. In other words, it is visible to clients outside of its own assembly. The top-level types declared within an assembly can either have the public or internal accessibility level. If you wanted to declare a class so that it is only usable from within the assembly where it was defined you would declare it as internal instead.

 internal class Employee 

A complete list of the accessibility levels supported by types and members (fields, methods, properties, and) of a type is shown in Table 4-10.

Table 4-10. Accessibility levels

Accessibility Level

Description

public

Access is not restricted in any way.

internal

Access is limited to the assembly where the type or member is defined.

private

Access is limited to the containing type (can only be applied to members of a class or nested classes).

protected

Access is limited to the containing class or subclasses of the containing class type (can only be applied to members of a class or nested classes).

protected internal

Access is limited to the current assembly or subclasses of the containing class type (can only be applied to members of a class or nested classes).

Notice that only two of these accessibility levels (public and internal) may be applied to a top-level type in a namespace. A top-level type is a type that is defined directly beneath its namespace as the Employee class was defined within the ClassApp namespace previously. This is as opposed to a nested class as follows:

 namespace AccessTest {   public class Outerclass   {     // ...     protected class InnerClass     {     // ...     }   } } 

In this case, the class called OuterClass is a top-level class in the AccessTest namespace, so it can only have either the public or internal accessibility levels assigned to it. However, the class called InnerClass is not a top-level class; it is defined within OuterClass , so it can have any of the five accessibility levels shown previously.

The next section of code in the Employee class contains the constructor for the class.

 public Employee(int id,string name) {     this.name=name;     this.ID=id; } 

The constructors have the same name as their class, and they can be overloaded. Therefore, if you wanted to create another constructor for the Employee class that only took a name as a parameter and generated an ID for the employee internally, you would write the following code:

 public Employee(string name) {     this.name=name;     this.ID=550; } 

The compiler automatically generates a call to the constructor whenever you create a new instance of a particular class. It chooses the correct constructor from the overloads based on the parameters that you use when you call new. Therefore, the following call will generate a call to the first constructor:

 Employee emp=new Employee(80,"Alan Gordon"); 

And the following code will generate a call to the second constructor:

 Employee emp=new Employee("Tamber Gordon"); 

Constructors are usually public, but you can define a private constructor to prevent someone from creating an instance of a class if, for instance, the class only contains static members or should only be used as a base class.

Classes are reference types only. In other words, you can only create instances of classes on the heap using the new operator. This is unlike C++ where you can create classes on the heap or stack as follows:

 // C++ code // emp1 resides on the stack Employee emp1(80,"Alan Gordon"); // emp2 resides on the heap Employee *emp2=new Employee(90,"Tamber Gordon"); 

If you want to create user -defined types that reside on the stack in C#, you must declare them as value types using the struct keyword instead of class.

The next section of code declares a public method of the Employee class called GetSalary that returns a hard-coded amount of $500.00.

 public decimal GetSalary() {     return 500.00m; } 

Note

In the .NET Framework, the decimal type is typically used to store currency ( monetary ) values.


The final section of code in this class declaration contains a declaration of two member fields in this class: an int that contains the ID for the employee and a string that contains the name of the employee.

 int ID; public string name; 

The name field has a public accessibility level, but notice that the ID field has no accessibility level specified. What's its level? It turns out that, in this case, the accessibility level is private because the default accessibility level for a class is private. Each of the categories of types that you can declare in .NET: enum, class, interface, and struct has a default accessibility level as shown in Table 4-11.

Table 4-11. Default accessibility levels

Type

Default Accessibility Level

Enum

public

Class

private

Interface

public

Struct

private

One significant difference between C# and C++ is C#'s support for properties. You can think of properties as a method (or more typically a pair of methods) that behaves like a field. The public name field in Employee could be converted to a property as follows:

 public class Employee { //...   public decimal Name   {     get { return name; }     set { name=value; }   }   private string name; } 

To declare a property, you declare its accessibility, type, and name. Next you define the get and set logic for the property. The logic in the get block is executed whenever someone reads the property as follows:

 Employee emp1=new Employee(80,"Alan Gordon"); Console.WriteLine(  emp1.Name  ); 

The logic in the set block is executed whenever someone writes to the property as follows:

 Employee emp1=new Employee(80,"Alan Gordon");  emp2.Name  ="Bryan Gordon"; 

Notice that I changed the accessibility of the name field in the Employee class to private after I changed the name field to a property. This is good object-oriented practice. In most cases, it is a bad idea to expose a field of a class directly because it will make it difficult if you need to change the internal implementation of the class later because clients will be written that depend on the field. With properties, you get the convenient syntax of a field with the encapsulation of accessor methods. If you look at the generated MSIL for the Employee class, you will see that, behind the scenes, the C# compiler actually generated two hidden methods called get_Name and set_Name that are called whenever you use the Name property as follows:

 .method public hidebysig specialname   instance string  get_Name() cil managed {   // Code size       11 (0xb)   .maxstack  1   .locals init (string V_0)   IL_0000:  ldarg.0   IL_0001:  ldfld      string     ACME.BusinessObjects.Employee::mName   IL_0006:  stloc.0   IL_0007:  br.s       IL_0009   IL_0009:  ldloc.0   IL_000a:  ret } // end of method Employee::get_Name .method public hidebysig specialname   instance void  set_Name(string 'value') cil managed {   // Code size       8 (0x8)   .maxstack  2   IL_0000:  ldarg.0   IL_0001:  ldarg.1   IL_0002:  stfld      string     ACME.BusinessObjects.Employee::mName   IL_0007:  ret } // end of method Employee::set_Name 

You can create a read-only property by omitting the set block in your property declaration. Although they are unusual, you can also create a write-only property by omitting the get block.

I can't leave the subject of class declarations without talking about static fields and methods. So far, I have only declared per-instance fields and methods in the classes, but occasionally you will have the need for class-level fields and methods that are shared by all instances of a class. C#, like C++, uses the keyword static for declaring class-level fields. You can call static methods and access static fields and properties on a class without declaring an instance of the class. You access static members of a class using the class name instead of an object reference. This is in contrast with C++ that allowed you to access static members using the class name or an instance variable.

As an example, you could declare a variable that maintains a count of the currently extant employees as follows:

 public class Employee {   public Employee(int id,string name,decimal salary)   {     mID=id;     mName=name;     mSalary=salary;  mNumEmployees++;  }   ~Employee()   {  mNumEmployees--;  }   // the rest of the Employee class is //  omitted for clarity  static public int GetNumEmployees()   {   return mNumEmployees;   }  private int mID;   private string mName;   private decimal mSalary;  static private int mNumEmployees=0;  } 

Notice that you use the static keyword to declare a field to hold the current number of Employees (mNumEmployees). You then increment the count of Employees in the constructor and decrement the count in the destructor. I also declared a static method called GetNumEmployees that returns the count of Employees. You can now call the GetNumEmployees method using the following syntax:

 Console.WriteLine(Employee.GetNumEmployees()); 

Notice that I specified the class name Employee to the left of the . instead of an Employee instance. You can access static fields from per-instance (nonstatic) methods, but you cannot access nonstatic fields from static methods. Static methods are not associated with an instance of the class, so no instance data ("this" pointer) is available when a static method is called.

C# also supports static properties, so, instead of having a static GetNumEmployees method, you can declare a static NumEmployees property as follows:

 static public int NumEmployees {     get { return mNumEmployees; } } 

You can now use this property as follows:

 Console.WriteLine(Employee.NumEmployees); 

C# also supports static constructors. Static constructors are useful when you have static fields that require initialization. Static constructors are called automatically by the CLR before the first instance of the class is created. They are declared without access modifiers (public, private, and so forth), and they cannot have arguments. As an example, let's say that, instead of maintaining a count of the number of extant Employees, you want to maintain a static collection that contains references to every employee. To do this, you would change the declaration of the Employee class as follows:

 public class Employee {   public Employee(int id,string name,decimal salary)   {     mName=name;     mID=id;     mEmployees.Add(mID,this);   }   static Employee()   {     mEmployees=new Hashtable(50);   }   ~Employee()   {     mEmployees.Remove(mID);   }   // the rest of the Employee class is   //  omitted for clarity   static public Hashtable Employees   {     get { return mEmployees; }   }   private int mID;   private string mName;   private decimal mSalary;   static private Hashtable mEmployees; } 

Notice that I declared a private Hashtable field to hold the references to all of the Employee objects. I then declare a static constructor, and, in that constructor, I assign the Hashtable field to a new instance of the Hashtable class. I then alter the per-instance constructor and destructor to add and remove items from the Hashtable.


Team-Fly    
Top
 


. Net and COM Interoperability Handbook
The .NET and COM Interoperability Handbook (Integrated .Net)
ISBN: 013046130X
EAN: 2147483647
Year: 2002
Pages: 119
Authors: Alan Gordon

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net