Team LiB
Previous Section Next Section

Static Features

Up until this point, all of the methods, attributes, and properties that we've discussed have been associated with an instance of a class. Every object has its own copy of the feature and can manipulate it independently of what other objects are doing. But there may be times when we'll wish to make a feature common to all instances of a class. In other words, instead of having each object have its own copy of an attribute, there may be an attribute whose value will be shared by all objects of a given class. The C# language satisfies this need through static features that are associated with classes as a whole rather than with individual objects.

Static Attributes

We've learned previously that whenever we create an object, we're creating an instance of the appropriate class whose attributes subsequently get "filled" with values specific to that object (see Figure 7-5).

Click To expand
Figure 7-5: Objects "fill in" their individual attribute values.

Suppose there were some piece of general information—say, the count of the total number of students enrolled at the university—that we wanted all Student objects to have shared access to. We could implement this as a simple attribute of the Student class, int totalStudents, along with code for manipulating the attribute as shown here:

using System;

public class Student
{
  private int totalStudents;
  // etc.

  // Property.
  public int TotalStudents {
    // accessor details omitted ...
  }

  public int ReportTotalEnrollment() {
    Console.WriteLine("Total Enrollment: " + TotalStudents);
  }

  public void IncrementEnrollment() {
    TotalStudents = TotalStudents + 1;
  }

  // etc.
}

This would be inefficient for two reasons:

  • First of all, each object would be duplicating the same information. Although an int(eger) doesn't take up a lot of memory, this is still, in principle, a waste of storage. And, storage space aside, one of our "quests" in adopting object technology is to avoid redundancy of data and/or code whenever possible.

  • Secondly, and perhaps more significantly, it would be cumbersome to have to call the IncrementEnrollment method on every Student object in the system each time a new Student were to be created, to ensure that all Students were in agreement on the total student count.

Fortunately, there is a simple solution! We can designate totalStudents to be what is known as a static attribute of the Student class through use of the static keyword:


public class Student
{
  // totalStudents is declared to be a static attribute.
  private static int totalStudents;

  // details omitted ...

  public int ReportTotalEnrollment() {
    Console.WriteLine("Total Enrollment: " + TotalStudents);
  }

  public void IncrementEnrollment() {
    totalStudents = totalStudents + 1;
  }
}
Note?/td>

The C# naming convention calls for using Pascal casing for all static attribute names regardless of their accessibility (public or nonpublic). Doing so causes conflicts if we later decide to declare a property for such attributes, as we'll then want to capitalize the property's name, as well.

public class Example
{
  private static int ImportantValue;  // This attribute name is
                                      // rendered in Pascal casing ...
  // ... as is this property name! The compiler won't approve.
  public static int ImportantValue {
    // accessor details omitted.
  }
}

To resolve this conflict, we're going to "break from tradition" by using Camel casing to name nonpublic static attributes, reserving the use of Pascal casing for public static attributes only.

We'll revisit this matter a bit later in this chapter.

A static attribute is one whose value is shared by all instances of a class; its value conceptually belongs to the class as a whole instead of belonging to any one instance/object of that class (see Figure 7-6).

Click To expand
Figure 7-6: The value of a static attribute conceptually belongs to the class as a whole.

Each Student object can access and modify the shared totalStudents attribute just as if it were a nonstatic attribute; in our earlier code example, the ReportTotalEnrollment and IncrementEnrollment methods look no different than any other Student method in terms of how they manipulate the totalStudents attribute. The difference is that the value of a static attribute is shared, so if we were to execute the following client code:

Student s1 = new Student();
s1.Name = "Fred";
s1.IncrementEnrollment();

Student s2 = new Student();
s2.Name = "Mary";
s2.IncrementEnrollment();

Student s3 = new Student();
s3.Name = "Steve";
s3.IncrementEnrollment();

then the resultant value of totalStudents would be affected as follows (assuming that it starts with a value of 0):

  • When s1 is passed the message s1.IncrementEnrollment(), the shared value of totalStudents is incremented by 1 (from 0 to 1).

  • When s2 is passed the message s2.IncrementEnrollment(), the shared value of totalStudents is incremented by 1 (from 1 to 2).

  • When s3 is passed the message s3.IncrementEnrollment(), the shared value of totalStudents is incremented by 1 (from 2 to 3).

At any time thereafter, if any one of the three objects inspects the value of totalStudents, it will be equal to 3. That is, if we were to invoke the ReportTotalEnrollment method on either s1, or s2, or s3, the result would be the same: the information printed by the method call would be the same in every case. Expanding upon our previous example:

Student s1 = new Student();
s1.Name = "Fred";
s1.IncrementEnrollment();

Student s2 = new Student();
s2.Name = "Mary";
s2.IncrementEnrollment();

Student s3 = new Student();
s3.Name = "Steve";
s3.IncrementEnrollment();

s1.ReportTotalEnrollment());
s2.ReportTotalEnrollment());
s3.ReportTotalEnrollment());

Here's the output for the preceding code:

Total Enrollment:  3
Total Enrollment:  3
Total Enrollment:  3

Static Properties

We would most likely declare the static totalStudents attribute to be private (like virtually all attributes), in which case we'd perhaps want to code public accessors for it. The preferred way to access the value of a static attribute in C# is to declare a static property. Other than the obvious use of the static keyword, a static property is indistinguishable from a nonstatic property:


public class Student
{
  private static int totalStudents;

  // Other details omitted.

  // We've declared a public static property to access our static attribute.
  public static int TotalStudents {
    get {
      return totalStudents;
    }
    set {
      totalStudents = value;
    }
  }

  // Details omitted.

  public void IncrementEnrollment() {
    // We're now taking advantage of our get/set accessors (note use of capital
    // "T" in the following code).
    TotalStudents = TotalStudents + 1;
  }
}
Note?/td>

As mentioned earlier, the official C# naming conventions call for us to use Pascal casing (a) for all static attributes regardless of their accessibility and (b) for the names of all public properties. If we were to follow both of these naming conventions, the TotalStudents attribute and the TotalStudents property would have exactly the same name, which the compiler won't allow.

To avoid such a conflict, one of these two naming conventions must be violated, and so as mentioned earlier, we've elected to use Camel casing to name nonpublic static attributes.

The get and set accessors for a static property are defined in the same manner as they are for a nonstatic property: the return type of the get accessor is implicitly the same as the property type, and the set accessor implicitly has a return type of void and is passed a parameter named value.

Static properties can't be invoked on an individual object, but are instead invoked on a class as a whole using dot notation:

Console.WriteLine("Total Enrollment = " + Student.TotalStudents);

If we were to try to invoke a static property on an object by mistake:

Student s1 = new Student();
Console.WriteLine("Total Enrollment = " + s1.TotalStudents);

the compiler would generate the following error:

Error CS0176: Static member 'Student.TotalStudents' cannot be accessed
with an instance reference; qualify it with a type name instead

Let's rework the client code from the previous example to make use of the static property:

Student s1 = new Student();
s1.Name = "Fred";
s1.IncrementEnrollment();

Student s2 = new Student();
s2.Name = "Mary";
s2.IncrementEnrollment();

Student s3 = new Student();
s3.Name = "Steve";
s3.IncrementEnrollment();

Console.WriteLine("Total Enrollment = " + Student.TotalStudents);

This results in the following output:

Total Enrollment = 3

Static Methods

Just as static attributes/properties are associated with a class as a whole versus relating to a specific individual object, static methods are in turn methods that may be invoked on a class as a whole.

Let's declare both the IncrementEnrollment and ReportTotalEnrollment methods to be static:

public class Student
{
  private static int totalStudents;

  // Other details omitted.
  public static int TotalStudents {
    get {
      return totalStudents;
    }
    set {
      totalStudents = value;
    }
  }

  // These two methods are now static methods.

  public static void IncrementEnrollment() {
    // The method body is unchanged from when this was a nonstatic method.
    TotalStudents = TotalStudents + 1;
  }

  public static int ReportTotalEnrollment() {
    // Ditto!
    Console.WriteLine("Total Enrollment: " + TotalStudents);
  }

  // etc.
}

As with static properties, static methods can only be invoked on a class as a whole:

Student.IncrementEnrollment();

We can't invoke static methods on individual object references; if we were to attempt to invoke the IncrementEnrollement method on a Student object by mistake:

Student s1 = new Student();
s1.IncrementEnrollment(); // This won't compile; IncrementEnrollment is static

the compiler would produce the following error message:

Error: cs0176:  Static member 'Student.IncrementEnrollment()' cannot be
accessed with an instance reference; qualify it with a type name instead

Reworking our client code example yet again to take into account the fact that IncrementEnrollment and ReportTotalEnrollment methods are now static methods, we'd have the following:

Student s1 = new Student();
s1.Name = "Fred";
Student.IncrementEnrollment();

Student s2 = new Student();
s2.Name = "Mary";
Student.IncrementEnrollment();

Student s3 = new Student();
s3.Name = "Steve";
Student.IncrementEnrollment();

Student.ReportEnrollment();

Here's the output that we'd get:

Total Enrollment = 3

Restrictions on Static Methods

Note that there is an important restriction on static methods with respect to how they may access attributes of the class in which they are declared: namely, they can't access nonstatic attributes of that class. If we were to attempt to write a static method such as Print, as in the following code example, that tried to access the value of a nonstatic attribute such as name, the compiler would prevent us from doing so.

public class Student
{
  // Two attributes -- one static, one nonstatic.
  private string name;
  private static int totalStudents;
  // etc.

  // Assume that public properties Name and TotalStudents have been defined for
  // both attributes (details omitted).
  public static void Print() {
    // A static method may NOT access NON-static attributes
    // such as 'name' -- the following line won't compile.
    Console.WriteLine(Name + " is one of " + TotalStudents +
                      "students");
  }
}

The compiler would generate the following error message regarding the WriteLine statement:

error cs0120: An object reference is required for the non-static field,
method, or property 'Student.name"

Why is this? As we learned in Chapter 3, classes are empty templates as far as nonstatic attributes are concerned; it's not until we instantiate an object that its (nonstatic) attribute values get filled in (see Figures 7-7 and 7-8).

Click To expand
Figure 7-7: A class serves as a template for creating objects.
Click To expand
Figure 7-8: Objects then fill in the values of their nonstatic attributes.

If a static method is invoked on a class as a whole, and that method were in turn to try to access the value of a nonstatic attribute, the value of that attribute would be undefined for the class, as illustrated conceptually in Figure 7-9.

Click To expand
Figure 7-9: Nonstatic attribute values are undefined in the class context.

Two other restrictions on static methods:

  • They can't be overridden by derived classes, and so the virtual keyword can't be applied to a static method when it's declared:

            // This won't compile.
            public virtual static void IncrementEnrollment() {
              // details omitted ...
            }
    
  • Static methods may not be declared to be abstract either:

            // This won't compile either.
            public abstract static void IncrementEnrollment();
    

If we were to try to compile either of the previous code snippets in the context of the Student class, the following compiler error would be generated:

error CS0112: A static member 'Student.IncrementEnrollment' cannot be
marked as override, virtual, or abstract

C#-Specific Terminology

To differentiate between static attributes and nonstatic attributes, C# uses the following alternative (preferred) terminology:

  • The term instance variable refers to a nonstatic attribute, because such an attribute has value or meaning for an instance, or object.

  • The term static variable refers to a static attribute.

To round out this terminology, the term local variable refers to a variable that is declared inside of a method, and hence is locally scoped relative to that method. Local variables are neither static nor instance variables.

Here is a code snippet that illustrates all three:

public class Student
{
  // Attributes.
  private string name;                // an instance variable
  private static int totalStudentCount;  // a static variable
  // Methods.
  public void SomeMethod(int x) {        // x is a local variable ...
    bool y;                              // ... as is y.
    // etc.
  }

  // etc.
}

Team LiB
Previous Section Next Section