A thorough understanding of C# requires you to master classes. Our previous article looked at the basics of classes, but there is still plenty to cover. Here, we will explore classes in more detail with a discussion of constants, fields, methods and parameters, and constructors and destructors.
Don’t miss these previous articles
Constants and fields
A constant provides a way to define a named value that is strongly typed. A field defines a variable within the class. The access control modifiers plus the new and static modifiers may be applied to both constants and fields. The type of a constant or field may be one of the built-in types or a user-defined type.
The value of the constant is specified in the declaration and cannot be reassigned. For example:
public const int k = 1000;
public const string me = “Paul”;
The value must be a constant expression that the C# compiler can evaluate at compile time. This is easy enough for value types such as integers, but what about reference types? Aside from strings, reference types are null until they’re assigned an object created with the new operator. The problem is that you can’t use the new operator in a constant expression. This doesn’t seem too useful—how many names for null do you need anyhow? Fields offer a solution.
The value for a field may also be specified in the declaration. This is a nice convenience, and it makes it easy to ensure that a field gets a consistent initial value when a class defines multiple constructors. Unlike constants, the value of a field isn’t calculated at compile time. Field values are determined at run time, either when the class is loaded, in the case of a static field, or when an object is created, in the case of an instance field. Constants are implicitly static, although the static modifier is not permitted. Declaring a static field nearly achieves the functionality of a constant. The final touch is to apply the readonly attribute to the field, and voila: You have a member with constant-like properties that can hold non-null references. The remaining difference is that unlike a constant, a readonly field can be reassigned within a constructor. Listing A shows the different kinds of fields.
Methods are a primary means to define behavior for a class. As with the class itself, the access control modifiers and the new, abstract, and sealed modifiers may be applied to a method. In addition, the virtual, override, and extern attributes may be applied to a method. A class may declare methods that either require or allow customization by a derived class. An abstract method provides no implementation and requires a nonabstract derived class to provide one. A virtual method provides an implementation but allows a derived class to provide an alternative. In both cases, a derived class is said to override the base class method.
In Listing B, abstract class A defines abstract method X. Abstract class B derives from A and defines virtual method Y. As an abstract class, B isn’t required to implement method X. Nonabstract class C derives from B and overrides methods X and Y. The override modifier clearly indicates the intent for C’s X and Y methods. The new modifier could be applied to C.Y instead of the override modifier to hide B.Y rather than override it. C.Y also specifies the sealed modifier. This prevents a class derived from C from overriding C.Y. However, a derived class could override C.X. The override sealed combination doesn’t compile with the Beta 1 C# compiler. You’ll need Beta 2 to use this combination. Finally, a derived class can access virtual methods in its base class, even if it has overridden them, using the base reserved word as shown in C.Z.
The extern modifier, in combination with the DllImport attribute, can be applied to a method to specify that the implementation is located in an external DLL. This enables access to existing unmanaged code from C#. In Listing C, the Win32 API function GetCurrrentProcessId in kernel32.dll is mapped to a static method of the same name in the class ExternExample.
A method specifies a return type, or void, a name, and a formal parameter list. The name and the formal parameter list are known as the signature of the method. A class may contain more than one method with the same name so long as each method has a different signature. Under such circumstances, the method name is said to be “overloaded.”
The formal parameter list can define fixed parameters. These can be followed by a parameter array. Each fixed parameter specifies a type and an identifier. A parameter array specifies the params modifier, an array type, and an identifier. A parameter array allows a method to be called with a variable number of arguments, although they must all be of the same type. In Listing D, the PrintWords method is called with zero, one, and three arguments for the parameter array.
By default, a fixed parameter is passed by value. The ref modifier can be specified to pass the parameter by reference. The out modifier can be specified to declare that a fixed parameter is an output parameter. A variable passed as the argument for an output parameter does not require initial assignment before being passed in a method invocation.
Arguments for ref or out parameters require the ref and out modifier in the method invocation. This forces the caller to acknowledge the parameter passing method and allows a reader to understand the passing method without referring to the method definition. In Listing E, the AddDelta method adds a specified delta to a target variable and sets output parameters to the initial target value plus twice and three times the delta, respectively.
Constructors and destructors
Classes may define instance and static constructors and a destructor. Each is optional. Also, each must be named according to the class name. None may specify a return type. An instance constructor is called to initialize a new instance of the class. It may specify a parameter list, and it may be overloaded. An instance constructor may also specify an initializer after the parameter list to call another constructor, either in the same class or the base class, through the this or base reserved words.
A static constructor is called automatically to initialize the class when the class is loaded into the .NET runtime. A destructor is called automatically to prepare an instance of a class for destruction. Neither the destructor nor the static constructor may specify a formal parameter list, be overloaded, or be invoked explicitly. Listing F illustrates the sequence in which constructors and destructors are called.
Destruction is nondeterministic in that it does not occur at the moment of scope exit or when the reference count becomes zero. Instead, destruction occurs during garbage collection when it is no longer possible for code to use the instance. Destructors may not be called at all during normal program exit. This tends to make C# destructors less useful than destructors in other languages.
We’ve covered a lot of ground here, but mastering these techniques will move your C# programming to a new level. In the next article, I’ll wrap up our discussion of C# class members.