Professor Mustard Programming
 

Programming 101 - Unit 04 - OOP

04c) Static Stuff
 
You've had to stare at that "static" keyword, right from day one. Isn't it about time that I told you what it's for? Let's see if you can guess first, based on what you already know.

Until you got into OOP, which involves creating multiple objects from one class definition, you always used the static keyword, no questions asked. As soon as you got into OOP, though, you found that it's actually only your Main() program that needs that keyword. The classes and objects that you've been creating recently don't use it at all. Coincidence? I think not!

In simplest terms, "static" means "only one". A method that isn't static must belong to a specific object before it can be used. Point in fact, a non-static method doesn't really exist anywhere when your program starts. You've defined it in a class, sure, but that's only a blueprint. The actual method exists in each object that you create, and nowhere else. It's kind of a strange way to look at it, but you've been doing it since the start of this unit.

A static method doesn't need an object instance. In fact, it doesn't belong to any instance at all; it belongs to the class blueprint itself. If you don't believe me, just look at Console.WriteLine(). Have you ever actually created an instance of "Console" since you started this course? Of course not! You don't need to, because you can call "WriteLine()" from the "Console" class itself. And that's exactly what a static method (or member) allows you to do:

class Employee
{
  public static string StaticMember = "Hi, Mom!";
  public string NonStaticMember;

  public Employee()
  {
    NonStaticMember = "Greetings.";
  }
}



I've declared two string members in Employee, "StaticMember" and "NonStaticMember" (yeah, yeah, I made them public; now shut up and let me teach). Watch what happens when we actually go to use them in Main():

[STAThread]
static void Main(string[] args)
{
  Console.WriteLine(Employee.StaticMember);
  
  Employee emp = new Employee();
  console.WriteLine(emp.NonStaticMember);
}



We need an object instance to access "NonStaticMember", which is probably what you've come to expect from objects. "StaticMember", however, does not need an object instance. You just use the actual class name to reach it. In fact, you can't reach "StaticMember" from an object instance; the only way to get at it is through the class itself.

Static and non-static members allow us to break our members and methods into two categories: stuff that affects every object individually (non-static), and stuff that affects every object universally (static). For example, in a Human class, "HairColor" would be a non-static property, because every human would need a different value. "GlobalPopulation", however, would be static. It still relates to humans, which is why it's in the Human class, but it doesn't relate to any specific human. And that, by the way, is your tip-off as to when you should make something static: when it relates to the class, but not to a specific object of that class (of course, in a "real" program, you'd be using a collection of human objects, which would have its own Length or Count property anyway; which would make a static GlobalPopulation variable kind of redundant. But I digress.)

Within a class, you can still qualify a variable with its origin. That is, even when we're writing code that's inside the Employee class, we can still write "Employee.StaticMember" to reference StaticMember. With non-static variables, we use the "this" keyword, as in "this.NonStaticMember". It means "the NonStaticMember for this object". There's no need for you to use "this", however, because C# will implicitly assume that it's there, every time you mention a variable or class. I've only brought it up so that you'll know what "this" means if you encounter it somewhere.

Static members and methods are often abused by newbie programmers, because they see it as a super-easy way to communicate between different classes. And to be fair, it is. All you need to do is provide the class with new information, and suddenly all the individual objects know about it. It's pretty impressive. Once again, however, with unlimited access comes unlimited danger. A public static variable is even more putrid than a public member variable, because it's not even associated with a specific object: it's just out there for the entire world to see, and it can be changed by anyone, anytime (this is as close as C# comes to the "global variables" possible in C++ and Visual Basic). So take care when making things static. Ensure that you're creating a static variable because it makes architectural sense, and not because it's a shortcut. Breaking encapsulation will invariably lead to trouble in the long run.

Because static members and methods are always associated with a class, an object instance of that class can freely access them, even if they are private:

private static int __number = 5;
public int GetNumber()
{
  return __number;
}



However, a static method can not access a non-static method or member, because it would have no idea which specific instance you wanted. This one-way relationship gives us the interesting ability to provide a non-static interface for a static variable... that is, a property that is non-static, and therefore available in each object instance, but actually feeds off of a static member, and is therefore identical in every object instance. This is yet another example of how encapsulation effectively hides details from the code that uses it.

This is also a good time to mention the "const" keyword. The "readonly" keyword means that you can't change a variable's value, and "const" means that the variable's value is constant. Doesn't that theoretically mean that they do the same thing? No, not exactly. For one thing, "const" is implicitly static. This means that you can't apply the "static" keyword to a const variable, because it is always static. It also means that you can't make it non-static. Also, "readonly" values aren't actually evaluated until the program runs, whereas const values are burned right into your exe file forever, when you compile the program (until you re-compile it, anyway). The basic rule of thumb is that you should always use readonly, unless you have a specific need to use const. And if you're not certain what a specific need would entail, then you don't need it (insert lazy professor grin here). Seriously, though, I always use readonly; don't even worry about const unless you're positive that the value will never, ever change. Once again, I only mentioned it here so that you'll know what it is if you encounter it somewhere.

By the way, just as a historical note, C# programmers have it soft. The simple, unmanaged act of using the "new" keyword would introduce memory leaks into your program if you were using C++. You see, C++ does provide more raw power than C#, but that's because you have to worry about really low-level stuff, like memory management and bit values. C# does 99% of the job for you (it is still possible to burn memory in C#, but that's a more complex subject than anything you're doing just yet). And since I chose to devote an entire lesson to the wonders and value of encapsulation, it's likely that you now have a fairly good understanding as to why I chose C# over C++. Who wants to think about memory and bit values when you can be thinking about something fun? Ah, well, to each their own.



using System;

namespace C_Static
{
  class Class1
  {
    static readonly string ln = Environment.NewLine;

    [STAThread]
    static void Main(string[] args)
    {
      Console.WriteLine("Employees created: " + Employee.NumEmployeesCreated);
      Employee emp1 = new Employee("Frank", 15.0, 40);
      Console.WriteLine("Employees created: " + Employee.NumEmployeesCreated);
      Employee emp2 = new Employee("Joe", 20.0, 40);
      Console.WriteLine("Employees created: " + Employee.NumEmployeesCreated);

      Console.WriteLine(ln + "Emp1: Name = {0}, Annual Salary = ${1}", emp1.Name, emp1.GetAnnualPay());
      Console.WriteLine("Emp2: Name = {0}, Annual Salary = ${1}", emp2.Name, emp2.GetAnnualPay());

      Console.WriteLine(ln + "Emp1 Uniform Color: " + emp1.UniformColor);
      Console.WriteLine("Emp2 Uniform Color: " + emp2.UniformColor);

      Employee.SetUniformColor("Red");
      Console.WriteLine(ln + "Emp1 Uniform Color: " + emp1.UniformColor);
      Console.WriteLine("Emp2 Uniform Color: " + emp2.UniformColor);

      PromptForExit();
    }

    static void PromptForExit()
    {
      Console.Write(ln + "Program complete! Hit enter to exit...");
      Console.ReadLine();
    }
  }

  class Employee
  {
    // All employees must wear same color
    private static string __uniformColor = "Blue";

    // All employees are part of the same count
    private static int __numEmployeesCreated = 0;
    public static int NumEmployeesCreated { get{ return __numEmployeesCreated; } }

    private string _name;
    private double _hourlyPay;
    private int _hoursPerWeek;
    public string Name { get{ return _name; } set{ _name = value; } }
    public double HourlyPay { get{ return _hourlyPay; } set{ _hourlyPay = value; } }
    public int HoursPerWeek { get{ return _hoursPerWeek; } set{ _hoursPerWeek = value; } }

    // Pretend that UniformColor can be different for each employee, when it
    // actually always refers to the same static variable! Ha, ha!
    public string UniformColor { get{ return __uniformColor; } }

    // We'll also create a static method that changes everyone's uniform color,
    // for the boss's personal amusement.
    public static void SetUniformColor(string color)
    {
      __uniformColor = color;
    }

    public Employee()
    {
      ++__numEmployeesCreated;
      _name = "";
      _hourlyPay = 0.0;
      _hoursPerWeek = 0;
    }

    public Employee(string name, double hourlyPay, int hoursPerWeek)
    {
      ++__numEmployeesCreated;
      _name = name;
      _hourlyPay = hourlyPay;
      _hoursPerWeek = hoursPerWeek;
    }

    // Some simple calculation methods
    public double GetWeeklyPay()
    {
      return _hourlyPay * _hoursPerWeek;
    }

    public double GetAnnualPay()
    {
      return _hourlyPay * _hoursPerWeek * 52;
    }
  }
}