Other Class Details in C#

This chapter discusses some of the miscellaneous issues of classes, including constructors, nesting, and overloading rules.

1. Nested Classes

Sometimes it’s convenient to nest classes within other classes, such as when a helper class is used by only one other class. The accessibility of the nested class follows similar rules to the ones outlined in Chapter 7 for the interaction of class and member modifiers. As with members, the accessibility modifier on a nested class defines what accessibility the nested class has outside the nested class. Just as a private field is visible only within a class, a private nested class is also visible only from within the class that contains it.

In the following example, the Parser class has a Token class that it uses internally. Without using a nested class, it might be written as follows:

public class Parser {

Token[] tokens;

}

public class Token {

string name;

}

In this example, both the Parser and Token classes are publicly accessible, which isn’t optimal. Not only is the Token class one more class taking up space in the designers that list classes, but it isn’t designed to be generally useful. It’s therefore helpful to make Token a nested class, which will allow it to be declared with private accessibility, hiding it from all classes except Parser.

Here’s the revised code:

public class Parser

{

Token[] tokens;

private class Token

{

string name;

}

}

Now, nobody else can see Token. Another option is to make Token an internal class so it won’t be visible outside the assembly, but with that solution, it is still visible inside the assembly.

Making Token an internal class also misses out on an important benefit of using a nested class. A nested class makes it clear to those reading the source code that the Token class can safely be ignored unless the internals for Parser are important. If this organization is applied across an entire assembly, it can help simplify the code considerably.

You can use nesting also as an organizational feature. If the Parser class was within a namespace named Language, you might require a separate namespace named Parser to nicely organize the classes for Parser. The Parser namespace would contain the Token class and a renamed Parser class. By using nested classes, the Parser class could be left in the Language namespace and contain the Token class.

2. Other Nesting

Classes aren’t the only types that can be nested; interfaces, structs, delegates, and enums can also be nested within a class.

3. Creation, Initialization, Destruction

In any object-oriented system, dealing with the creation, initialization, and destruction of objects is important. In the .NET runtime, the programmer can’t control the destruction of objects, but it’s helpful to know the other areas that can be controlled.

3.1. Constructors

If there are no default constructors, the C# compiler will create a public parameterless constructor. A constructor can invoke a constructor of the base type by using the base syntax, like this:

using System; public class BaseClass {

public BaseClass(int x)

{

this.x = x;

public int X

{

get

{

return(x);

}

}

int x;

}

public class Derived: BaseClass {

public Derived(int x): base(x)

{

}

}

class Test {

public static void Main()

{

Derived d = new Derived(15);

Console.WriteLine(“X = {0}”, d.X);

}

}

In this example, the constructor for the Derived class merely forwards the construction of the object to the BaseClass constructor.

Sometimes it’s useful for a constructor to forward to another constructor in the same object, as in the following example:

using System;

class MyObject

{

public MyObject(int x)

{

this.x = x;

}

public MyObject(int x, int y): this(x)

{

this.y = y;

}

public int X {

get

{

return(x);

}

}

public int Y {

get

{

return(y);

}

}

int x;

int y;

}

class Test {

public static void Main()

{

MyObject my = new MyObject(l0, 20);

Console.WriteLine(“x = {0}, y = {1}”, my.X, my.Y);

}

}

Private constructors are—not surprisingly—usable only from within the class on which they’re declared. If the only constructor on the class is private, this prevents any user from instantiating an instance of the class, which is useful for classes that are merely containers of static functions (such as System.Math, for example). C# 2.0 adds a new feature to accomplish a similar task; see the “Static Classes” section later in this chapter.

Private constructors are also used to implement the singleton pattern, when there should be only a single instance of a class within a program. This is usually done as follows:

public class SystemInfo {

private static SystemInfo cache = null;

private static object cacheLock = new object();

private SystemInfo()

{

// useful stuff here…

}

public static SystemInfo GetSystemInfo()

{

lock(cacheLock)

{

if (cache == null)

cache = new SystemInfo();

return(cache);

}

}

}

This example uses locking to make sure the code works correctly in a multithreaded envi­ronment. For more information on locking, see Chapter 31.

3.2. Initialization

If the default value of the field isn’t what’s desired, it can be set in the constructor. If there are multiple constructors for the object, it may be more convenient—and less error-prone—to set the value through an initializer rather than setting it in every constructor.

Here’s an example of how initialization works:

public class Parser        // Support class

{

public Parser(int number)

{

this.number = number;

}

int number;

}

class MyClass {

public int counter = 100;

public string heading = “Top”;

private Parser parser = new Parser(100);

}

This is pretty convenient; the initial values can be set when a member is declared. It also makes class maintenance easier since it’s clearer what the initial value of a member is.

To implement this, the compiler adds code to initialize these functions to the beginning of every constructor.

3.3. Destructors

Strictly speaking, C# doesn’t have destructors, at least not in the way most developers think of destructors, where the destructor is called when the object is deleted.

What’s known as a destructor in C# is known as a finalizer in some other languages and is called by the garbage collector when an object is collected. The programmer doesn’t have direct control over when the destructor is called, and it’s therefore less useful than in languages such as C++. Cleanup in a destructor is a last resort, and there should always be another method that performs the same operation so that the user can control the process directly.

When a destructor is written in C#, the compiler will automatically add a call to the base class’s finalizer (if present).

For more information on this, see Chapter 38. If garbage collection is new to you, you’ll probably want to read that chapter before delving into the following section.

3.4. Managing Nonmemory Resources

The garbage collector does a good job of managing memory resources, but it doesn’t know anything about other resources, such as database handles, graphics handles, and so on. Because of this, classes that hold such resources will have to do the management themselves.

In many cases, this isn’t a real problem; all that it takes is writing a destructor for the class that cleans up the resource, like so:

using System;

using System.Runtime.InteropServices;

class ResourceWrapper {

int handle = 0;

public ResourceWrapper()

{

handle = GetWindowsResource();

}

~ResourceWrapper()

{

FreeWindowsResource(handle);

handle = 0;

}

[DllImport(“dll.dll”)]

static extern int GetWindowsResource();

[DllImport(“dll.dll”)]

static extern void FreeWindowsResource(int handle);

}

Some resources, however, are scarce and need to be cleaned up in a more timely manner than the next time a garbage collection occurs. Since there’s no way to call finalizers automat­ically when an object is no longer needed,1 it needs to be done manually.

In the .NET Framework, objects can indicate that they hold on to such resources by imple­menting the IDisposable interface, which has a single member named Dispose(). This member does the same cleanup as the finalizer, but it also needs to do some additional work. If either its base class or any of the other resources it holds implement IDisposable, it needs to call Dispose() on them so they also get cleaned up at this time.  After it does this, it calls GC.SuppressFinalize() so that the garbage collector won’t bother to finalize this object. Here’s the modified code:

using System;

using System.Runtime.InteropServices;

class ResourceWrapper: IDisposable

{

int handle = 0;

bool disposed = false;

public ResourceWrapper()

{

handle = GetWindowsResource();

}

private void Dispose(bool disposing)

{

if(!this.disposed)

{

if(disposing)

{

// call Dispose() on our base class (if necessary) and

// on any other resources we hold that implement IDisposable

}

FreeWindowsResource(handle);

handle = 0;

}

disposed = true;

}

// does cleanup for this object only

~ResourceWrapper()

{

Dispose(false);

}

// dispose cleans up its object, and any objects it holds

// that also implement IDisposable.

public void Dispose()

{

Dispose(true);

GC.SuppressFinalize(this);

}

[DllImport(“dll.dll”)]

static extern int GetWindowsResource();

[DllImport(“dll.dll”)]

static extern void FreeWindowsResource(int handle);

}

If your object has semantics where another name is more appropriate than Dispose() (a file would have Close(), for example), then you should implement IDisposable using explicit interface implementation. You would then have the better-named function forward to Dispose().

3.5. IDisposable and the Using Statement

When using classes that implement IDisposable, it’s important to make sure Dispose() gets called at the appropriate time. When a class is used locally, this is easily done by wrapping the usage in try-finally, like in this example:

ResourceWrapper rw = new ResourceWrapper();

try {

// use rw here

}

finally

{

if (rw != null)

((IDisposable) rw).Dispose();

}

The cast of the rw to IDisposable is required because ResourceWrapper could have imple­mented Dispose() with explicit interface implementation. The try-finally is a bit ugly to write and remember, so C# provides the using statement to simplify the code, like this:

using (ResourceWrapper rw = new ResourceWrapper())

{

// use rw here

}

If two or more instances of a single class are used, the using statement can be written like so:

using (ResourceWrapper rw = new ResourceWrapper(), rw2 = new ResourceWrapper())

For different classes, two using statements can be placed next to each other:

using (ResourceWrapper rw = new ResourceWrapper())

using (FileWrapper fw = new FileWrapper())

In either case, the compiler will generate the appropriate nested try-finally blocks.

3.6. IDisposable and Longer-Lived Objects

The using statement provides a nice way to deal with objects that are around only for a single function. For longer-lived objects, however, there’s no automatic way to make sure Dispose() is called.

It’s fairly easy to track this through the finalizer, however. If it’s important that Dispose() is always called, it’s possible to add some error checking to the finalizer to track any such cases. You could do this with a few changes to the ResourceWrapper class:

static int finalizeCount = 0;

~ResourceWrapper()

{

finalizeCount++;

DoDispose();

}

[Conditional(“DEBUG”)]

static void CheckDisposeUsage(string location)

{

GC.Collect();

GC.WaitForPendingFinalizers();

if (finalizeCount != 0)

{

finalizeCount = 0;

throw new Exception(“ResourceWrapper(” + location +

“: Dispose()=” + finalizeCount);

}

}

The finalizer increments a counter whenever it’s called, and the CheckDisposeUsage() routine first makes sure all objects are finalized and then checks to see if there were any final­izations since the last check. If so, it throws an exception.[4]

4. Static Fields

It’s sometimes useful to define members of an object that aren’t associated with a specific instance of the class but rather with the class as a whole. Such members are known as static members.

A static field is the simplest type of static member; to declare a static field, simply place the static modifier in front of the variable declaration. For example, you could use the following to track the number of instances of a class that were created:

using System; class MyClass {

public MyClass()

{

instanceCount++;

}

public static int instanceCount = 0;

}

class Test {

public static void Main()

{

MyClass my = new MyClass();

Console.WriteLine(MyClass.instanceCount);

MyClass my2 = new MyClass();

Console.WriteLine(MyClass.instanceCount);

}

}

The constructor for the object increments the instance count, and the instance count can be referenced to determine how many instances of the object have been created. A static field is accessed through the name of the class rather than through the instance of the class; this is true for all static members.

5. Static Member Functions

The previous example exposes an internal field, which is usually something to be avoided. You can restructure it to use a static member function instead of a static field, like in the following example:

using System; class MyClass {

public MyClass()

{

instanceCount++;

}

public static int GetInstanceCount()

{

return(instanceCount);

}

static int instanceCount = 0;

}

class Test {

public static void Main()

{

MyClass my = new MyClass();

Console.WriteLine(MyClass.GetInstanceCount());

}

}

This now uses a static member function and no longer exposes the field to users of the class, which increases future flexibility. Because it’s a static member function, it’s called using the name of the class rather than the name of an instance of the class.

In the real world, this example would probably be better written using a static property, which is discussed Chapter 19.

6. Static Constructors

Just as there can be other static members, there can also be static constructors. A static constructor will be called before the first instance of an object is created. It’s useful to do setup work that needs to be done only once.

You can declare a static constructor simply by adding the static modifier in front of the constructor definition. A static constructor can’t have any parameters:

using System; class MyClass {

static MyClass()

{

Console.WriteLine(“MyClass is initializing”);

}

}

There’s no static analog of a destructor.

7. Constants

C# allows values to be defined as constants. For a value to be a constant, its value must be something that can be written as a constant. This limits the types of constants to the built-in types that can be written as literal values.

Not surprisingly, putting const in front of a variable means its value can’t be changed. Here’s an example of some constants:

using System;

enum MyEnum

{

Jet

}

class LotsOLiterals {

// const items can’t be changed.

// const implies static. public const int value1 = 33;

public const string value2 = “Hello”;

public const MyEnum value3 = MyEnum.Jet;

}

class Test

{

public static void Main()

{

Console.WriteLine(“{0} {1} {2}”,

LotsOLiterals.value1,

LotsOLiterals.value2,

LotsOLiterals.value3);

}

}

8. Read-Only Fields

Because of the restriction on constant types being knowable at compile time, const can’t be used in many situations.

In a Color class, it can be useful to have constants as part of the class for the common colors. If there were no restrictions on const, the following would work:

// error class Color {

public Color(int red, int green, int blue)

{

this.red = red;

this.green = green;

this.blue = blue;

}

int red;

int green;

int blue;

// call to new can’t be used with static

public const Color Red = new Color(255, 0, 0);

public const Color Green = new Color(0, 255, 0);

public const Color Blue = new Color(0, 0, 255);

}

class Test {

static void Main()

{

Color background = Color.Red;

}

}

This clearly doesn’t work because the static members Red, Green, and Blue can’t be calcu­lated at compile time. But making them normal public members doesn’t work either; anybody could change red to olive drab or puce.

The readonly modifier is designed for exactly that situation. By applying readonly, you can set the value in the constructor or in an initializer, but you can’t modify it later.

Because the color values belong to the class and not a specific instance of the class, they’ll be initialized in the static constructor, like so:

class Color {

public Color(int red, int green, int blue)

{

this.red = red;

this.green = green;

this.blue = blue;

}

int red;

int green;

int blue;

public static readonly Color Red;

public static readonly Color Green;

public static readonly Color Blue;

// static constructor static Color()

{

Red = new Color(255, 0, 0);

Green = new Color(0, 255, 0);

Blue = new Color(0, 0, 255);

}

}

class Test {

static void Main()

{

Color background = Color.Red;

}

}

This provides the correct behavior.

If the number of static members was high or creating them was expensive (either in time or in memory), it might make more sense to declare them as readonly properties so that members could be constructed on the fly as needed.

On the other hand, it might be easier to define an enumeration with the different color names and return instances of the values as needed:

class Color {

public Color(int red, int green, int blue)

{

this.red = red;

this.green = green;

this.blue = blue;

}

public enum PredefinedEnum {

Red,

Blue,

Green

}

public static Color GetPredefinedColor(

PredefinedEnum pre)

{

switch (pre)

{

case PredefinedEnum.Red:

return(new Color(255, 0, 0));

case PredefinedEnum.Green:

return(new Color(0, 255, 0));

case PredefinedEnum.Blue:

return(new Color(0, 0, 255));

default:

return(new Color(0, 0, 0));

}

}

int red;

int blue;

int green;

}

class Test {

static void Main()

{

Color background =

Color.GetPredefinedColor(Color.PredefinedEnum.Blue);

}

}

This requires a little more typing, but there isn’t a startup penalty or lots of objects taking up space. It also keeps the class interface simple; if there were 30 members for predefined colors, the class would be much harder to understand.[5] ■Note

9. Static Classes

New to C# 2.0 are static classes. At times, all the methods (and potentially, properties) on a class will be static. This typically happens in utility classes, such as math libraries, where saving state in instance variables serves no benefit. As mentioned earlier, it’s good programming to make the constructor private for classes like this, as it prevents users of the class from acciden­tally instantiating the class just to use a static method (C# prevents this, but other languages may not). However, the private constructor is merely a convention and doesn’t carry any compiler enforcement.

This lack of compiler enforcement has two problems: one, a developer can forget to add this feature to the class, and two, there’s nothing preventing the developer from accidentally adding an instance method. These problems aren’t contrived or mere thought experiments. System.Environment offers a number of static methods and properties that allow the state of the computing environment to be queried. As there’s only a single computing environment from the point of view of a process, all members are static. However, the static modifier on the

HasShutdownStarted property was accidentally omitted in version 1.0 of the .NET Framework library, and the product shipped without the ability to access this property.

The solution to the problem is quite simple: classes can be marked as static, which prevents them from having constructors, destructors, instance methods, and instance member variables. To declare a class as static, place the static modifier before the class keyword:

static class Util {

static int PerformCal() { return 0; }

}

With the static keyword in place, the compiler will enforce that no instance members are present and will prevent a client of the class from unwittingly constructing an instance of the static class.

10. Partial Classes

Despite being one of the “big four” new features of C# 2.0 (along with generics, iterators, and anonymous methods), partial classes are relatively uninteresting. They simply allow a class to be split over multiple source files and have no effect on the intermediate code produced by the compiler. The motivation for partial classes is as follows:

  • Some classes grow too big to be comfortably worked on within the editor using a single file.
  • Separate developers may want to work on the same class simultaneously without using the multiple checkout facilities of a source-control system.
  • Development environments may want to maintain a split between human-generated and machine-generated code.

Despite their mundane nature, these problems are genuine concerns when they do occur and where sufficiently important to warrant an extension to the C# language.

You implement partial classes by placing the partial modifier before the class keyword:

//declare once

partial class MyPartialClass { }

//and then again – this could be in a separate source file

partial class MyPartialClass { }

The modifier simply informs the compiler that fragments of the class may exist in multiple source files. It’s legal for a partial class to exist entirely within the one source file, or it can be spread out over any number of source files. Using a consistent naming convention to allow all the fragments of a class to be located is recommended, but not enforced, by the compiler. It will generally be worth using development environment features such as Visual Studio’s Class View to provide a holistic view of a class if the partial class modifier is used.

The following are other points to keep in mind:

  • Structs can use the partial modifier, but enumerations can’t.
  • Assemblies form the boundaries of classes, and a partial class can’t span multiple assemblies.
  • If one file marks a class as partial, all other declarations of the class must also use the partial
  • It’s legal for each partial class to add features to the class as long as they aren’t mutually exclusive.

For example, it’s a compiler error to define different base types or to have two implemen­tations of the same method. Besides these nonsensical scenarios, the programmer is free to make the job of the maintenance programmer as difficult or easy as possible. The following code will compile and run without any errors:

//in file1.cs

partial class MyPartialClass : MyBase {

public void Dispose() { }

}

//in file2.cs

public class MyBase{}

partial class MyPartialClass : IDisposable { }

In the interests of comprehensibility and maintainability, code that uses partial types should apply all class modifiers and nominate all base classes and interfaces in a main file and then group all logically similar functionality in a single file. Of course, you should avoid partial classes if they aren’t needed, and you shouldn’t employ them gratuitously.

Source: Gunnerson Eric, Wienholt Nick (2005), A Programmer’s Introduction to C# 2.0, Apress; 3rd edition.

Leave a Reply

Your email address will not be published. Required fields are marked *