Object-oriented programming will probably be new to some C# programmers, as well as to those new to programming entirely. C++ allows object oriented programming, but C#, with the .NET Framework as a model really encourages object orientation. C++ allows code to exist outside of any class, whereas C# mandates that all code must exist inside classes. The .NET Framework is not a perfect model for studying how to structure classes to achieve best object orientation, but it is a place to start.
This section provides an overview of object-oriented programming, particularly as it relates to the .NET development platform. Object-oriented programming is a substantial topic, so it’s highly recommended that you do some additional reading to supplement the material provided here. There are also many books on both object-oriented programming and design patterns, related subjects.
In the simplest terms, an object is an instance of a class in object-oriented languages such as C++, C#, and Visual Basic .NET. Objects typically mirror their real world counterparts: employees, invoices, and purchase orders are all examples of real world entities that can be modeled as objects in your programs. Objects typically provide both properties, which describe some attribute of the object (for example, an employee might have an EmployeeID property), and methods, which allow actions to be taken by the object (for example, an Invoice object might expose a Process method). Properties and methods make it easier for programmers to know how to use a given object.
In addition to properties and methods, object-oriented programming offers three helpful features: inheritance, polymorphism, and encapsulation.
Inheritance is the ability to create new classes that are derived from existing classes. A derived class “inherits” all the properties and methods of the class from which it is derived (referred to as the parent class). Programmers can then add additional properties and methods to the derived class, or in some cases override the implementation of an existing method inherited from the parent class.
Polymorphism lets programmers have identically named methods in different classes, and it allows the correct method to be executed based on the context of the call. For example, a programmer could define an Invoice class with a Process method. The programmer could then define separate TimeInvoice and MaterialsInvoice classes that derived from the Invoice class, but which might or might not use the same implementation of the Process method exposed by the parent class. A method calling the Process method on a class that is descended from Invoice does not need to know exactly which implementation of Process will be called.
Encapsulation allows objects to be treated as “black boxes” in that only the properties and methods defined by the programmer as publicly available are visible outside of the object. Internal object state and the implementation of the publicly available methods are hidden by encapsulation. This limited visibility lets developers freely modify the internal state and implementation of their objects, as long as they don’t change the publicly defined interface used to access the object. For instance, an EmployeeList class might expose methods FirstEmployee, NextEmployee and so on. Internally, such a list could be managed using a simple array, a linked list (in which each item points to the next item in a list), or a database table. To the consumer of the class, it should not matter exactly how the list is maintained internally, so long as the public methods such as FirstEmployee return the correct information.
Classes are the basic unit of an object-oriented application. A class in C# typically contains property and method definitions, as well as event handlers (a special class of methods). Classes are often grouped into useful collections through the use of namespaces. Namespaces can contain classes and other namespaces. For example, the System.Web namespace defined by the .NET Framework SDK contains not only classes such as the HttpRequest, HttpResponse, and HttpServerUtility classes (which provide the equivalent functionality of the ASP Request, Response, and Server intrinsic objects), but also a number of namespaces, including the System.Web.UI, System.Web.Caching, and System.Web.SessionState namespaces, each of which also contains classes and/or namespaces.
By using classes as containers for code and namespaces, the .NET architecture makes it very easy to create a rich, yet intuitive application hierarchy that can make your code easier to use, debug, and maintain.
When you develop applications in the Visual Studio .NET environment, a root namespace will be created with the same name as your project. If you specify additional namespaces within your code modules, these will become child namespaces of the root namespace. You can change the root namespace by right-clicking your project in Solution Explorer and clicking Properties. The Root Namespace property appears under Common Properties, General.
Now that you know a bit about object-oriented programming, let’s take a look at a simple example of how to put it to use. First let’s define a class based on a real world entity, an animal.
public class Animal public virtual void Eat( Console.WriteLine("Yum!") public virtual void Sleep( Console.WriteLine("Zzzzz") }
You use the virtual modifier to let C# know that the behavior can be overridden by a derived class.
Unlike C++, C# allows only single inheritance. Although some C++ programmers will find this to be a problem, the reality is that in virtually all cases, there are better alternatives to using multiple inheritance. C# does enable you to implement multiple interfaces, something that is often useful.
Next create a more specific animal class, Cat, that derives from the Animal class. Note the use of the listing of the Animal class after the name of the new class and a colon on the first line of the declaration. This is how you let C# know that the class inherits from another class. Inheriting from another class is really that simple. This class will override the behavior of the Eat method, but not the Sleep method, as follows:
public class Cat : Anima public override void Eat( Console.WriteLine("Yum, Yum...Meow, Meow!") }
You’ll also create a Dog class that derives from Animal and that overrides the Sleep method, but not the Eat method, as follows:
public class Dog : Anima public override void Sleep( Console.WriteLine("Zzzzzz...woofwoofwoofwoof...zzzzzz!") }
Finally, use the Cat and Dog classes in the following code. Note that you use the imports statement to access the members of the Animals namespace without fully qualified names (such as Cat instead of Animal.Cat).
class UseAnimal static void Main(string args Cat cat Dog dog cat = new Cat() cat.Eat() cat.Sleep() dog = new Dog() dog.Eat() dog.Sleep() }
This code, when compiled and run from a command line, provides the following output:
Yum, Yum...Meow, Meow Zzzzz.. Yum Zzzzzz...woofwoofwoofwoof...zzzzzz!
Even though you called the same two methods on both the Cat and Dog classes, when you overrode a method of the parent Animal class, you got the behavior that you specified for the particular animal. When you didn’t override, the output shows that you got the behavior inherited from the parent Animal class. You can find the full source code for this example, including a batch file for compiling the classes, in the source folder for this chapter (Chapter_03) in the source archive for this book.
The preceding example might not seem terribly useful, and admittedly, creating your own Cats and Dogs by inheriting from Animal won’t help you much in your development efforts. Fortunately, the same principles demonstrated in that example apply to the classes that make up the .NET Framework. Many of these classes can be used to derive your own specialized classes. You’ll see specific examples of this in Chapter 10.
Another nifty feature of the .NET Framework and the common language runtime is that they enable you to inherit from classes written in other languages. Why is this important? Because it means that development teams can make better use of the existing skill sets of their developers, as well as the company’s existing code base. For example, a company with experienced Visual Basic developers (and existing code in Visual Basic) can have those developers continue to write in Visual Basic .NET. If the company also has a group of skilled Java developers, they can easily make the transition to C# and use the existing class resources created by the VB developers through inheritance.