Classes, Structs, and Objects
|Visual C# Tutorials|
Classes, Structs, and Objects
© 2006 Weldon W. Nash, III
|This tutorial—Classes, Structs, and Objects—is from Accelerated C# 2005, by Trey Nash. Copyright © 2006 Weldon W. Nash, III. All rights reserved. This article is reproduced by permission. This tutorial has been edited especially for C# Online.NET. Read the book review!|
Classes, Structs, and Objects
Everything is an object! At least, that is the view from inside the CLR and the C# programming language. This is no surprise because C# is, after all, an object-oriented language. The objects that you create through class definitions in C# have all the same capabilities of the other predefined objects in the system. In fact, keywords in the C# language such as
bool are merely aliases to predefined value types within the
System namespace, and they are
Note This chapter is rather long, but don’t allow it to be intimidating. In order to cater to a wider audience, this chapter covers as much C# base material as reasonably possible. If you’re proficient with either C++ or Java, you may find yourself skimming this chapter and referencing it as you read subsequent chapters. Some of the topics touched upon in this chapter are covered in more detail in later chapters.
The ability to invent your own types is tantamount to object-oriented systems. The cool thing is that, since even the built-in types of the language are plain-old CLR objects, the objects you create are on a level playing field with the built-in types. In other words, the built-in types don’t have special powers that you cannot muster. The cornerstone for creating these types is the class definition. Class definitions, using the C#
class keyword, define the internal state and the behaviors associated with the objects of that class’s type. The internal state of an object is represented by the fields that you declare within the class, which can consist of references to other objects, or values. Sometimes, but rarely, you will hear people describe this as the "shape" of the object, since the instance field definitions within the class define the memory footprint of the object on the heap.
The objects created from a class encapsulate the data fields that represent the internal state of the objects, and the objects can tightly control access to those fields. The behavior of the objects is defined by implementing methods, which you declare and define within the class definition. By calling one of the methods on an object instance, you initiate a unit of work on the object. That work can possibly modify the internal state of the object, inspect the state of the object, or anything else for that matter.
You can define constructors, which the system executes whenever a new object is created. You can also define a method called a finalizer, which works when the object is garbage-collected. As you’ll see in Chapter 13, you should avoid finalizers if at all possible. This chapter covers construction and destruction in detail, including the detailed sequence of events that occur during the creation of an object.
Objects support the concept of inheritance, whereby a derived class inherits the fields and methods of a base class. Inheritance also allows you to treat objects of a derived type as objects of its base type. For example, a design where an object of type
Dog derives from type
Animal is said to model an is-a relationship (i.e.,
Animal). Therefore, you can implicitly convert references of type Dog to references of type Animal. Here, implicit means that the conversion takes the form of a simple assignment expression. Conversely, you can explicitly convert references of type
Animal, through a cast operation, to references of type
Dog if the particular object referenced through the
Animal type is, in fact, an object created from the
Dog class. This concept, called polymorphism, whereby you can manipulate objects of related types as though they were of a common type, should be familiar to you. Computer wonks always try to come up with fancy five-dollar words for things such as this, and polymorphism is no exception, when all it means is that an object can take on multiple type identities. This chapter discusses inheritance, as well as its traps and pitfalls.
The CLR tracks object references. This means each variable of reference type actually contains a reference to an object on the heap (or is null, if it doesn’t currently refer to an object). When you copy the value of a reference type variable into another reference type variable, another reference to the same object is created—in other words, the reference is copied. Thus, you end up with two variables that reference the same object. In the CLR, you have to do extra work to create copies of objects—e.g., you must implement the
ICloneable interface or a similar pattern.
All objects created from C# class definitions reside on the system heap, which the CLR garbage collector manages. The GC relieves you from the task of cleaning up your objects’ memory. You can allocate them all day long without worrying about who will free the memory associated them. The GC is smart enough to track all of an object’s references, and when it notices that an object is no longer referenced, it marks the object for deletion. Then, the next time the GC compacts the heap, it destroys the object and reclaims the memory.
Note In reality, the process is much more complex than this. There are many hidden nuances to how the GC reclaims the memory of unused objects. I talk about this in the section titled "Destroying Objects" later this chapter. Consider this: The GC removes some complexity in one area, but introduces a whole new set of complexities elsewhere.
Along with classes, the C# language supports the definition of new value types through the
struct keyword. Value types are sort of like lightweight objects that typically don’t live on the heap, but instead live on the stack. To be completely accurate, a value type can live on the heap, but only if it is a field inside an object on the heap. Value types cannot be defined to inherit from another class or value type, nor can another value type or class inherit from them.
Value types can have constructors, but they cannot have a finalizer. By default, when you pass value types into methods as parameters, the method receives a copy of the value. I cover the many details of value types, along with their differences from reference types, in this chapter and in Chapter 13.
That said, let’s dive in and get to the details. Don’t be afraid if the details seem a little overwhelming at first. The fact is, you can start to put together reasonable applications with C# without knowing every single detailed behavior of the language. That’s a good thing, because C#, along with the Visual Studio IDE, is meant to facilitate rapid application development. However, the more details you know about the language and the CLR, the more effective you’ll be at developing and designing robust C# applications.