Learn about encapsulation, abstraction, abstract data types, information hiding, class member access control, instance variables, local variables, class variables, reference variables, primitive variables, how C# identifies properties, and how to set and get properties in C#. Also learn about public accessor methods, public manipulator methods, data validation by property setters and public access methods, and a few other things along the way.
Published: January 24, 2010
Validated with Amaya
By Richard G. Baldwin
XNA Programming Notes # 0106
|
This tutorial lesson is part of a continuing series dedicated to programming with the XNA Game Studio. I am writing this series of lessons primarily for the benefit of students enrolled in an introductory XNA game programming course that I teach. However, everyone is welcome to study and benefit from the lessons.
An earlier lesson titled Getting Started (see Resources) provided information on how to get started programming with Microsoft's XNA Game Studio. (See Baldwin's XNA programming website in Resources.)
The three main characteristics of an object-oriented program
Object-oriented programs exhibit three main characteristics:
In this and the next two lessons, I will explain and illustrate those three characteristics plus some related topics.
I recommend that you open another copy of this document in a separate browser window and use the following links to easily find and view the figures and listings while you are reading about them.
I recommend that you also study the other lessons in my extensive collection of online programming tutorials. You will find a consolidated index at www.DickBaldwin.com.
In addition to the three explicit characteristics of encapsulation, inheritance, and polymorphism, an object-oriented program also has an implicit characteristic of abstraction.
Abstraction is the process by which we specify a new data type, often referred to as an abstract data type or ADT. This includes a specification of the type's data representation and its behavior. In particular,
How does abstraction relate to encapsulation?
Encapsulation is the process of gathering an ADT's data representation and behavior into one encapsulated entity. In other words, encapsulation converts from the abstract to the concrete.
Some analogies
You might think of this as being similar to converting an idea for an invention into a set of blueprints from which it can be built, or converting a set of written specifications for a widget into a set of drawings that can be used by the machine shop to build the widget.
Automotive engineers encapsulated the specifications for the steering mechanism of my car into a set of manufacturing drawings. Then manufacturing personnel used those drawings to produce an object where they exposed the interface (steering wheel) and hid the implementation (levers, bolts, etc.).
In all likelihood, the steering mechanism contains a number of other more-specialized embedded objects, each of which has state and behavior and also has an interface and an implementation.
The interfaces for those embedded objects aren't exposed to me, but they are exposed to the other parts of the steering mechanism that use them.
A new type
For our purposes, an abstract data type is a new type (not intrinsic to the C# language). It is not one of the primitive data types that are built into the programming language such as sbyte, short, int, long, float, double, etc.
Already known to the compiler
The distinction in the previous paragraph is very important. The data representation and behavior of the intrinsic or primitive types is already known to the compiler and cannot normally be modified by the programmer.
Not known to the compiler
The representation and behavior of an abstract type is not known to the compiler until it is defined by the programmer and presented to the compiler in an appropriate manner.
Define data representation and behavior in a class
C# programmers define the data representation and the behavior of a new type (present the specification to the compiler) using the keyword class. In other words, the keyword class is used to convert the specification of a new type into something that the compiler can work with; a set of plans as it were. To define a class is to go from the abstract to the concrete.
Create instances of the new type
Once the new type (class) is defined, one or more objects of that type can be brought into being (instantiated, caused to occupy memory).
Objects have state and behavior
Once instantiated, the object is said to have state and behavior. The state of an object is determined by the current values of the data that it contains. The behavior of an object is determined by its methods.
The state and behavior of a GUI Button object
For example, if we think of a GUI Button as an object, it is fairly easy to visualize the object's state and behavior.
A GUI Button can usually manifest many different states based on size, position, depressed image, not depressed image, label, etc. Each of these states is determined by data stored in the instance variables of the Button object at any given point in time. (The combination of one or more instance variables that determine a particular state is often referred to as a property of the object.)
Similarly, it is not too difficult to visualize the behavior of a GUI Button. When you click it with the mouse, some specific action usually occurs.
A C# class named Button
If you dig deeply enough into the C# class library, you will find that there is a class named Button in the System.Windows.Forms namespace. Each individual Button object in a C# Windows Forms Application is an instance of the C# class named Button. A Button object has a single constructor, dozens of methods, dozens of properties, and dozens of events.
The state of Button objects
Each Button object has instance variables, which it does not share with other Button objects. The values of the instance variables define the state of the button at any given time. Other Button objects in the same scope can have different values in their instance variables. Hence every Button object can have a different state.
The behavior of a Button object
Each Button object also has certain fundamental behaviors such as responding to a mouse Click event or responding to a GotFocus event.
The C# programmer has control over the code that is executed in response to the event. However, the C# programmer has no control over the fact that a Button object will respond to such an event. The fact that a Button will respond to certain event types is an inherent part of the type specification for the Button class and can only be modified by modifying the source code for the Button class.
If abstraction is the design or specification of a new type, then encapsulation is its definition and implementation.
A programmer defines the data representation and the behavior of an abstract data type into a class, thereby defining its implementation and its interface. That data representation and behavior is then encapsulated in objects that are instantiated from the class.
Expose the interface and hide the implementation
According to good object-oriented programming practice, an encapsulated design usually exposes the interface and hides the implementation. This is accomplished in different ways with different languages.
Just as most of us don't usually need to care about how the steering mechanism of a car is implemented, a user of a class should not need to care about the details of implementation for that class.
The user of the class (the using programmer) should only need to care that it works as advertised. Of course this assumes that the user of the class has access to good documentation describing the interface and the behavior of objects instantiated from the class.
Should be able to change the implementation later
For a properly designed class, the class designer should be able to come back later and change the implementation, perhaps changing the type of data structure used to store data in the object, and the using programs should not be affected by the change.
Class member access control
Object-oriented programming languages usually provide the ability to control access to the members of a class. For example, C#, C++ and Java all use the keywords public, private, and protected to control access to the individual members of a class. In addition, Java adds a fourth level of access control, which is called package-private. C# adds two levels of access control that are not included in Java or C++ (see the next section).
Five levels of access control
According to the C# specifications, "Each member of a class has an associated accessibility, which controls the regions of program text that are able to access the member." There are five levels of access control in C#:
A different interpretation
Another online source provides a different interpretation for two of the access levels:
What is an assembly?
You can learn more about an assembly here. Frankly, I'm not absolutely certain at this time how to interpret the access levels of internal and protected internal. However, I believe that an assembly in C# is similar to a package in Java, and if so, then I do know how to interpret these two access levels.
Public, private, and protected
To a first approximation, you can probably guess what public and private mean. Public members are accessible by all code that has access to an object of the class. Private members are accessible only by members belonging to the class.
The protected keyword is used to provide inherited classes with special access to the members of their base classes.
A public user interface
In general, the user interface for a class consists of the public methods. (The variables in a class can also be declared public but this is generally considered to be bad programming practice unless they are actually constants.)
For a properly designed class, the class user stores, reads, and modifies values in the object's data by calling the public methods on a specific instance (object) of the class. (This is sometimes referred to as sending a message to the object asking it to change its state).
Normally, if the class is properly designed and the implementation is hidden, the user cannot modify the values contained in the private instance variables of the object without going through the prescribed public methods in the interface.
C# has a special form of method, often called a set Accessor method. The use of this type of method makes it appear that an assignment is being made to store a value in a private instance variable belonging to an object when in fact, the assignment operation is automatically converted to a call to a set Accessor method. I discuss this more fully in my earlier tutorial titled Learning C# and OOP, Properties, Part 1. I will also show an example of a set Accessor method later.
C# also has a special form of method often called a get Accessor method that operates like a set Accessor method but in the reverse direction. A get Accessor method makes it appear that the value of a private instance variable can be obtained by referencing the name of the object joined to the name of the variable. In fact, that reference is automatically converted to a call to a get Accessor method.
Not a good design by default
An object-oriented design is not a good design by default. In an attempt to produce good designs, experienced object-oriented programmers generally agree on certain design standards for classes. For example, the data members (instance variables) are usually private unless they are constants. The user interface usually consists only of public methods and includes few if any data members.
Of course, there are exceptions to every rule. One exception to this general rule is that data members that are intended to be used as symbolic constants are made public and defined in such a way that their values cannot be modified.
The methods in the interface should control access to, or provide a pathway to the private instance variables.
Not bound to the implementation
The interface should be generic in that it is not bound to any particular implementation. Hence, the class author should be able to change the implementation without affecting the using programs so long as the interface doesn't change.
In practice, this means that the signatures of the interface methods should not change, and that the interface methods and their arguments should continue to have the same meaning even if the author of the class changes the internal implementation.
I will present and explain a simple C# console program that illustrates encapsulation and C# properties during the remainder of this lesson. The output from the program is shown in Figure 1.
Figure 1 Output from the
program named Props01.
text: Quit color: Red color: Bad color height: 20 double height: 40 height: 0 |
Will explain in fragments
I will explain the code in this program in fragments. A complete listing of the program is provided in Listing 11 near the end of the lesson.
Two classes
The program contains two separate class definitions. One class, named TargetClass, illustrates encapsulation and properties. The other class named Props01 instantiates an object of TargetClass and exercises its interface. For simplicity, both classes were defined in the same physical file, but that is not a requirement.
Will switch between classes
In an attempt to help you understand how the program works, I will switch back and forth between the two classes showing the cause and effect relationship between the code in one class and the code in the other class.
Code from the class named Props01 will be shown with the background color shown in Listing 1. Code from the class named TargetClass will be shown with the background color shown in Listing 2.
The declarations
The first two statements in Listing 1 apply equally to both classes. The first statement "uses" the namespace named System. This eliminates the requirement to qualify every reference to the Console class with the name of the namespace containing the Console class.
The second statement in Listing 1 establishes the namespace for the new program code, which is actually the name of the folder containing the source code file.
Listing 1. Beginning of the class named Props01. using System; namespace Props01{ class Props01a{ static void Main(string[] args){ TargetClass obj = new TargetClass(); //Access a public instance variable obj.text = "Quit"; Console.WriteLine("text: " + obj.text); |
Beginning of the class named Props01
The class definition for the class named Props01 begins with the keyword class shown highlighted in yellow in Listing 1.
As you will see when we get to the end of the explanation, the only thing that is contained in this class is the Main method. The required signature for the Main method is highlighted in cyan in Listing 1. I'm not going to explain all of the ramifications for the syntax of the method signature. At this point, I will simply tell you to memorize the syntax.
The first statement in the Main method uses the new operator to instantiate an object of the class named TargetClass and save a reference to that object in a local reference variable named obj. This reference will be used later to access the object.
Access a public instance variable in the object
The last two statements in Listing 1 access a public instance variable belonging to the object. The first of the two statements assigns the string value "Quit" to the variable. The second statement retrieves and displays that value.
Store a string value in the object's instance variable
Note the syntax of the first of these two statements. The value of the reference variable named obj is joined to the name of the object's public instance variable. (The name of the instance variable is text.) The assignment operator is used to store a string value in that instance variable.
You can think of this operation as involving the following steps:
Get and display the value in the object's instance variable
The last statement does essentially the same thing in reverse.
This produces the first line of output shown in Figure 1.
Beginning of the class named TargetClass
The class definition for the class named TargetClass begins in Listing 2.
Listing 2. Beginning of the class named TargetClass. public class TargetClass{ public string text; |
Declare a public instance variable
The code in the class begins by declaring a public instance variable. This is considered to be bad programming practice in most quarters unless the public variable is actually a constant.
This is the variable that is accessed by the last two statements in Listing 1.
In most cases, instance variables should be declared private and should be made accessible through public access methods or public set and get methods. You will see examples of public access, set, and get methods later.
Different kinds of variables
I will generally refer to three kinds of variables:
Any of these can be further qualified as follows:
Instance variables
An instance variable belongs to a specific object. The lifetime of an instance variable is the same as the lifetime of the object to which it belongs. The scope of an instance variable depends on its access modifier such as public, private, or protected.
public
A public instance variable can be accessed by any code in any method in any object that can obtain a reference to the object to which the variable belongs. However, you must first gain access to the object in order to gain access to the variable. Normally you gain access to the object using a reference to the object and then gain access to a member of the object such as a variable or method. This is indirection.
private
A private instance variable can be accessed by any code in any method that is defined in the class of the object to which the variable belongs.
protected
A protected instance variable can be accessed by the same methods that can access a private instance variable plus methods in subclasses of the class of the object to which the instance variable belongs.
I'm not going to try to explain the scope of internal and protected internal instance variables for the reasons that I discussed earlier.
Local variables
A local variable is declared inside a method or constructor. The lifetime of a local variable is limited to the time that control remains within the block of code in which the variable is declared. The scope of the variable is limited to the code block in which it is declared and then only to the statements following the declaration statement.
Class variables
A class variable belongs to a static class. I believe that the lifetime of a class variable is the same as the lifetime of the program in which the class is loaded.
It is not necessary to instantiate an object of the static class in order to access the variable. Assuming that you have access rights to the variable, you can access it simply by joining the name of the class to the name of the variable using the dot operator.
More bad programming practice
I personally consider it bad programming practice to use class variables in most cases unless the variables are actually constants. However, there are a few situations where you have no choice but to use a non-constant class variable.
Reference variables
A reference variable contains a reference to an object or contains null. You typically use the value stored in a reference variable to locate an object in memory. Once you locate the object, you typically use the dot operator along with the name of a variable or a method to ask the object to do something. This is called indirection.
Primitive variables
Primitive variables contain primitive values. No indirection is required to access the primitive value stored in a primitive variable.
An analogy
An analogy that I often use to explain the difference between a reference variable and a primitive variable goes as follows.
A primitive variable is analogous to your wallet.
If you get robbed and the robber takes your wallet, he has your money because the wallet contains your money just like a primitive variable contains a primitive value.
A reference variable is analogous to your check book.
If the robber takes your checkbook, he doesn't have your money -- not yet anyway. The checkbook doesn't contain your money. Instead, it contains a reference to your bank where your money is stored. A reference variable doesn't contain an object; it contains a reference to an object. Furthermore, two or more reference variables can contain references to the same object but this is usually not a good idea.
Call the object's public accessor methods
Listing 3 calls the object's public accessor methods named setColor and getColor twice in succession.
Listing 3. Call the object's public accessor methods. //Call public accessor methods obj.setColor("Red"); Console.WriteLine("color: " + obj.getColor()); //Call public accessor methods again with an // invalid input value. obj.setColor("Green"); Console.WriteLine("color: " + obj.getColor()); |
The setColor method
The purpose of the method named setColor is to store a string value in the object. The calling code has no way of knowing how the value is stored because the implementation is hidden behind a public interface method. This is an example of encapsulation.
The string value to be stored is passed as a parameter to the method each time the method is called. An invalid string value was purposely passed as a parameter on the second call to the setColor method.
The getColor method
The purpose of the getColor method is to retrieve the string value previously stored in the object by the setColor method. Once again, the calling code has no way of knowing how the value is retrieved and returned because the implementation is hidden behind a public interface method.
The value that is retrieved by each call to the getColor method is displayed on the standard output device (the black screen).
Would be a property in Java
If this were a Java program, the combination of these two methods would constitute a property named color because the pair of methods matches the design pattern for properties in Java. However, that is not the case in C#. As you will see later, C# uses a different approach to identifying properties. In C#, these are simply public access methods used for information hiding.
The definition of setColor and getColor in TargetClass
Listing 4 defines the public setColor and getColor methods in the class named TargetClass.
Listing 4. Definition of setColor and getColor methods in TargetClass. private string colorData = ""; public void setColor(string data){ //Validating code if(data.Equals("Red") || data.Equals("Blue")){ //Accept incoming data value colorData = data; }else{ //Reject incoming data value colorData = "Bad color"; }//end if-else }//end setColor //--------------------------------------------------// public string getColor(){ return colorData; }//end getColor |
Typical method definitions
These two methods are typical of the method definition syntax in C#. The syntax is not too different from a function definition in C++ so you should have no trouble understanding the syntax.
Data validation
In addition to information hiding, one of the reasons for hiding the implementation behind public access methods is to assure that only valid data is stored in the object. Therefore, public access methods that store data in the object often contain code that validates the incoming data (as shown in Listing 4) before actually storing the data in the object.
The validation code in Listing 4 will only accept incoming color data of "Red" or "Blue". If the incoming string doesn't match one of those two values, the string "Bad color" is stored in the object in place of the incoming value.
What do you do if...
It usually isn't too difficult to write code to implement a set of rules to validate the incoming data. The hard part is figuring out what to do if the incoming data is not valid. The choices range all the way from flagging the data as invalid as shown in Listing 4 to throwing an exception which, if not properly handled, will cause the program to terminate. The circumstances dictate the action to be taken.
Program output
The code in Listing 3 calls the setColor method twice. The first call passes a valid string, "Red", as a parameter. The second call passes an invalid string, "Green", as a parameter. This causes the second and third lines of text in Figure 1 to be displayed by the program.
Set, get, and display a property value with a valid value
As mentioned earlier, C# uses a special approach to identify properties. (You will see the code in TargetClass that accomplishes this shortly.) In the meantime, the code in Listing 5 first sets, then gets, and finally displays the value of a property named height belonging to the object referenced by the contents of the variable named obj.
Listing 5. Set, get, and display a property value with a valid value. obj.height = 20;
Console.WriteLine("height: " + obj.height);
|
Looks can be deceiving
If you compare Listing 5 with Listing 1, you will see that there is essentially no difference in the syntax of the code with the magenta highlight in the two listings. The syntax in both listings suggests that a value is being directly assigned to a public instance variable belonging to the object. As we already know, that is true for Listing 1. However, it is not true for Listing 5.
A set Accessor method
Although it doesn't look like it, the code with the magenta highlight in Listing 5 is actually calling a special set Accessor method that hides the implementation behind a public interface. As you will see shortly, this special method contains validation code similar what you saw in Listing 4.
A get Accessor method
Once again, although it doesn't look like it, the last statement in Listing 5 is actually calling a special get Accessor method belonging to the object to get the current value of the property. That method returns the value stored in the property. As before, the returned value is concatenated with a literal string and passed to the WriteLine method of the Console class for display on the black screen.
The set Accessor and get Accessor methods for the property named height
The special set Accessor and get Accessor methods for the property named height are shown in Listing 6.
Listing 6. The set Accessor and get Accessor methods for the property named height. private int heightData; public int height{ get{ return heightData; }//end get set{ //Validating code if(value < 84){ heightData = value; }else{ heightData = 0; }//end else }//end set }//end height property |
A hidden parameter named value
The value on the right side of the assignment operator in Listing 5 arrives on the set side of the code in Listing 6 as a hidden parameter named value.
Validating code
You can write whatever validating code is appropriate before assigning the incoming value to a private instance variable or perhaps storing it in a private data structure of some other type.
In this case, the value is accepted and stored in the private instance variable named heightData if it is less than 84 (the height in inches of a person that is seven feet tall). If the incoming value is greater than 83, it is not accepted and instead is flagged as invalid by storing 0 in the private instance variable.
A read-only property
You can omit the code on the set side if you need a read-only property.
Little more to say about this
There isn't much more that I can say to explain this syntax other than to tell you to memorize it. The code in Listing 5 causes the fourth line of text shown in Figure 1 to be displayed on the black screen.
Call manipulator method and display results
In addition to making it possible to set and get property values, objects often provide other public interface methods of varying complexity that make it possible to manipulate the data stored in the object in a variety of ways. In those cases, the using programmer needs access to good documentation that explains the behavior of such manipulator methods.
Listing 7 calls a manipulator method named doubleHeight that is designed to double the value of the height property. Then Listing 7 accesses and displays the new value of the height property.
Listing 7. Call manipulator method and display results. obj.doubleHeight(); Console.WriteLine("double height: " + obj.height); |
A manipulator method named doubleHeight
Listing 8 shows the manipulator method named doubleHeight.
Listing 8. A simple manipulator method named doubleHeight. public void doubleHeight(){ heightData *= 2; }//end doubleHeight //--------------------------------------------------// }//end TargetClass |
The method multiplies the current value in the private instance variable that is used to store the property named height by a factor of two, stores the modified value back into the same variable, and returns void.
The code in Listing 7 causes the fifth line of text shown in Figure 1 to be displayed.
Listing 8 also signals the end of the class named TargetClass.
Set and get the property with an invalid value
Listing 9 attempts to set an invalid value into the property named height.
Listing 9. Set and get the property named height with an invalid value. obj.height = 100; Console.WriteLine("height: " + obj.height); |
Then it retrieves and displays the value currently stored in that property.
As you saw in Listing 6, when an attempt is made to set a value greater than 83 in the property, a value of 0 is set in the property to flag it as invalid.
The code in Listing 9 causes the last line of text in Figure 1 to be displayed on the black screen.
Pause until the user presses any key
The last statement in the Main method, shown in Listing 10, causes the program to block and wait until the user presses a key. This causes the black screen to remain on the desktop until the user is finished viewing it.
Listing 10. Pause until the user presses any key. Console.ReadKey(); }//end Main }//end class Props01a |
The end of the program
Listing 10 signals the end of the Main method, the end of the class named Props01, and the end of the program. When the user presses a key, the black screen disappears from the desktop and program terminates.
I encourage you to copy the code from Listing 11. Use that code to create a C# console application. Build and run the program. Experiment with the code, making changes, and observing the results of your changes. Make certain that you can explain why your changes behave as they do.
You learned about abstraction, abstract data types, encapsulation, information hiding, class member access control, instance variables, local variables, class variables, reference variables, primitive variables, how C# identifies properties, and how to set and get properties in C#.
You also learned about public accessor methods, public manipulator methods, data validation by property setters and public access methods, and a few other things along the way.
A complete listing of the C# program discussed in this lesson is provided in Listing 11.Listing 11. The program named Props01. /*Project Props01 Copyright 2009, R.G.Baldwin This program is designed to explore and explain the use of encapsulation and properties in C#. Program output is: text: Quit color: Red color: Bad color height: 20 double height: 40 height: 0 *********************************************************/ using System; namespace Props01{ class Props01a{ static void Main(string[] args){ TargetClass obj = new TargetClass(); //Access a public instance variable obj.text = "Quit"; Console.WriteLine("text: " + obj.text); //Call public accessor methods obj.setColor("Red"); Console.WriteLine("color: " + obj.getColor()); //Call public accessor methods again with an // invalid input value. obj.setColor("Green"); Console.WriteLine("color: " + obj.getColor()); //Set and get a property obj.height = 20; Console.WriteLine("height: " + obj.height); //Call manipulator method and display results. obj.doubleHeight(); Console.WriteLine("double height: " + obj.height); //Set and get the property again with an invalid // input value. obj.height = 100; Console.WriteLine("height: " + obj.height); //Pause until user presses any key. Console.ReadKey(); }//end Main }//end class Props01a //====================================================// public class TargetClass{ //A public instance variable - not good practice public string text; //This would be a property named color in Java, but // not in C# private string colorData = ""; public void setColor(string data){ //Validating code if(data.Equals("Red") || data.Equals("Blue")){ //Accept incoming data value colorData = data; }else{ //Reject incoming data value colorData = "Bad color"; }//end if-else }//end setColor //--------------------------------------------------// public string getColor(){ return colorData; }//end getColor //--------------------------------------------------// //This is a C# property named height private int heightData; public int height{ get{ return heightData; }//end get set{ //Validating code if(value < 84){ heightData = value; }else{ heightData = 0; }//end else }//end set }//end height property //--------------------------------------------------// //This is a manipulator method public void doubleHeight(){ heightData *= 2; }//end doubleHeight //--------------------------------------------------// }//end TargetClass }//end package |
Copyright 2009, Richard G. Baldwin. Reproduction in whole or in part in any form or medium without express written permission from Richard Baldwin is prohibited.
Richard Baldwin is a college professor (at Austin Community College in Austin, TX) and private consultant whose primary focus is object-oriented programming using Java and other OOP languages.
Richard has participated in numerous consulting projects and he frequently provides onsite training at the high-tech companies located in and around Austin, Texas. He is the author of Baldwin's Programming Tutorials, which have gained a worldwide following among experienced and aspiring programmers.
In addition to his programming expertise, Richard has many years of practical experience in Digital Signal Processing (DSP). His first job after he earned his Bachelor's degree was doing DSP in the Seismic Research Department of Texas Instruments. (TI is still a world leader in DSP.) In the following years, he applied his programming and DSP expertise to other interesting areas including sonar and underwater acoustics.
Richard holds an MSEE degree from Southern Methodist University and has many years of experience in the application of computer technology to real-world problems.
-end-