Static Methods, Static Fields, and Method Parameters
In this chapter we take a closer look at static methods, static fields, and method parameters. We have been working with static methods since the beginning of the course. When we write to the console using Console.WriteLine(), we are using a static method. Static methods are particularly useful and common because they do not require an object instance to be created before the method can be used. Static fields and various forms of parameter passing are also covered.
Static methods are special because a method declared as static in a class can be called without instantiating an object from the class. The method declaration is used from the class itself instead of from an object. When we used Math.Pow() in a previous chapter we were using a static method since we were not required to instantiate an object from the Math class before we used the method. Note the red "S" in the MSDN graphic below denotes that Pow() is a static member of the Math class. All members of the Math class can be viewed here on Microsoft Docs.
In Program.cs, the variables defined on lines 12-14 will be covered in the Method Parameters section below. Line 18 contains a call to the static method IncrementCounter(). Note that an object has not been instantiated to use this method. Instead of preceding the method name with the name of an object, the class name is supplied to access static members. In this case, the name of a class, StaticDemo, is used to call the method. The lines 15-18 in StaticDemo.cs contain the method declaration.
The use of the keyword static on line 15 enables other objects within the assembly to call the IncrementCounter() method without instantiating an object. Use of static methods can provide significant performance improvements since object creation and deletion are not required. On line 17 the field counter is incremented (more on this below). In Program.cs, the static method IncrementCounter() is called three times (lines 18, 20, 22). The static method GetCounter() is also called three times (lines 19, 21, 23).
Static fields are usually used in conjunction with static methods. When we used Math.PI in a previous chapter we were using a static field since we were not required to instantiate an object from the Math class before we used the field. Note the red "S" in the MSDN graphic below denoting that PI is a static member of the Math class.
On line 17 of StaticDemo.cs the field counter is incremented. Normally, I prefer to use the this pointer when working with member fields. In the case of StaticDemo.cs, using this as shown below produces an error. The problem is that this is a system-supplied pointer that points to the current object. And, since static methods do not require an object, the this pointer cannot be used with static properties, methods, or fields.
On line 8 of StaticDemo.cs, counter is specified as static. This means that there is only one instance of the counter variable. It is shared by all callers of the class methods. Note in the Program.cs output that counter is incremented each time IncrementCounter() is called. GetCounter() is called to display the current counter variable after each update. Notice in the output that the counter variable is incremented from 1, to 2, and then to 3 to reflect each of the three calls to the IncrementCounter() method.
We will now consider three alternative ways that variables can be passed to methods. Recall that items in the method header (line 43) are known as parameters and those in the method call (line 32) are knows as arguments. Arguments are passed by default by value. This means that a copy of the variable is made and that copy is passed to the method. On line 32 the SayHello() method is called and keywords ref and out appear. Notice that the same keywords appear in the method header on line 43.
The first argument (valString) does not have a preceding keyword and is therefore passed by value. Notice on line 12 that valString is initialized to valString in Main. Since a copy of valString is passed to the SayHello() method, if the copy is changed in SayHello() the original in Main will not be effected.
The next argument on line 32, refString, is preceded by the keyword ref. Therefore, a reference to the variable is passed to the method instead of a copy. This means that if the variable referenced by refString is changed in SayHello(), the original in Main is changed also. Variables passed using the ref keyword must contain a value before being passed. They cannot be null. This can be tested by removing the initialization on line 13.
The third argument on line 32 (outString) is passed similarly to passing by reference. However, there are two important differences. Arguments preceded by out are not required to be initialized before being used in an method call. Out arguments can be null at the point of method call. Whereas, ref arguments must be initialized prior to the method call. The other notable difference between out and ref is that out arguments must be changed in the method that was called. It is considered unassigned if it is not changed in the method. Arguments passed by ref on the other hand are not required to be modified in the method that was called.
Another way of stating the above two differences is: ref arguments must be assigned by the caller and out arguments must be assigned by the callee.
The method SayHello() is defined on lines 43-52. The parameters have the same names as the arguments but this is not required. However, arguments and parameters must be aligned correctly. That is, argument one goes with parameter one, etc. The programmer can use named parameters to arbitrarily order arguments but that is not common and is not covered in this course. The only actions performed in SayHello() are to modify the contents of each parameter so the effect of the change can be observed in the output.
Notice in the output that all three variables used in the demonstration are output in Main, from within SayHello(), and after the call to SayHello(). On lines 28-30 the original values are output. Next, the values of the parameters in SayHello() are shown. Notice that all three variables contain their settings from within SayHello() when output on lines 49-51. Finally, the contents are output again in Main on lines 35-37. Note that valString still contains its original content in Main since only a copy was passed to SayHello(). However, both refString and outString reflect the changes made to the reference variables from within SayHello(). Even after returning to Main, the reference variables retain the changes.
In the next chapter, we look at structures and interfaces.