TERRYSMITH.NET    Doing Objects in VB.NET and C# - OBJECT BASICS IN .NET  
             Home              Painting              Photography              Software              Writing
Previous ] [ Index ] [ Next ]   

1

Object Basics in .NET

Welcome to .NET. Since this is a fast-pace guide for experienced developers, I'm not going to bore you with definitions of what objects are or what object-oriented programming is. Let's jump right into what you need to know in order to get started with .NET.

The Object Object

Every object in .NET ultimately inherits from the Object object. (Except, of course, the Object object, which doesn't inherit from itself.) If this seems remarkably similar to Java, it is. Inheritance from Object is implicit. If your class doesn't declaratively inherit from another class, it implicitly inherits from Object. The members of the Object class are described below:

Access Name Description
Public Equals Determines if two Object instances are equal.
Public GetHashCode Returns a hash value used by smoking-fast hash algorithms.
Public GetType Returns a Type instance representing the type of the current object.
Public ReferenceEquals Static method that compares the references of its two Object parameters.
Public ToString Returns a String representation of the current instance.
Protected MemberwiseClone Returns a clone of the current object by performing a shallow copy.
Table 1.1 Object Members

The Equals method comes in two overloaded versions. The first is static (called Shared in VB.NET as we will see later) and takes two Object instances as parameters as shown in the following VB.NET example:

balanced = Object.Equals(objectInLeftHand, objectInRightHand)

The second overloaded member is invoked upon a valid Object instance as follows:

balanced = objectInLeftHand.Equals(objectInRightHand)

By default, these methods only check for reference equality between objects; however, their purpose in life is to test for value equality. This is accomplished by objects that override Equals to provide their own "value-based" comparison. You should consider overriding Equals in any objects that you create. You do this by overriding the non-static Equals method. The static version of Equals is a helper function that first guarantees both arguments are not null before calling obj1.Equals(obj2). Note that it returns true if both objects are null. If you intentionally need to test for reference equality and not value equity, then use the ReferenceEquals method instead.

Note Properly implementing your own Equals for a type can get very tricky. See Chapter 6, Common Object Operations, of Applied Microsoft .NET Framework Programming by Jeffrey Richter for instructions on how to do so correctly.

The GetHashCode method of Object returns a hash value for use in hash tables. For any type in which you implement your own Equals method you should also override the GetHashCode method. This is important because the System.Collections.Hashtable class expects that any two objects that are equal must have the same hash code value.

The GetType method returns a Type-derived object used to identify the type of the object. You can use the Reflection classes along with this type instance in order to obtain metadata information about the type. (Be careful! If your object throws an exception during a reflection operation it will have seven years of bad luck.)

The ToString method can be overridden to return a string representation of your particular object's state. If it's not overridden it will return the full name of the type (this.GetType().FullName).

If you need to make a copy of an object, you can invoke the MemberwiseClone method. (Who said cloning is illegal?) This method will create a new instance of the type and set the new object's fields equal to the current object's fields. It simply copies all the bytes. Therefore, it performs a shallow copy. If the type has a member called myString, for example, an object and its clone will now be pointing to the same String object since the bytes copied will be the reference's bytes, not the object's.

Most likely, if you are building an object with the intention that it will often be cloned, you want to implement the ICloneable interface. The interface has a single Clone method as shown below:

public interface ICloneable
{
   Object Clone();
}

Quite simply, if you want to implement a deep copy, implement this interface along with your deep copy semantics and send a memo to your clients advising them to use this interface instead of MemberwiseClone.

Types

There are two-and-a-half varieties of data types in .NET: primitive, reference, and value types. The "one-half" comes in with primitive types, which really are not separate types, as we will see later. Primitive types in .NET are simply keywords supported by a .NET compiler that map to existing types in the .NET Framework Class Library (FCL) (not to be confused with the NFL, the National Framework Library). For example, an integer could be defined as follows in C#:

System.Int32 a = new System.Int32();

Or you could use the following:

int a = 0;

Both generate exactly the same intermediate language (IL). Though you're much more likely to use the later because the syntax is more readable. In short, nothing magical is going on. The compiler simply knows how to map keywords for primitive types to corresponding FCL types and generate the appropriate IL.

Jeffrey Richter presents a case for why he prefers to use FCL type names and completely avoid the primitive type names in Chapter 5 of his book, Applied Microsoft .NET Framework Programming. You will find that I reference Richter's book quite often. It is a definitive .NET reference that you should read when you're ready to dig deeper into .NET.

The remaining two data types are reference types and value types. The largest difference between the two is that reference types are allocated on the heap and value types are allocated on the stack. Reference types are always accessed via a pointer, or reference. Value types are declared using struct in C# and Structure in VB.NET. Reference types are declared using class in C# and Class in VB.NET.

Let's look at some basic, uninspiring, examples of both. First, classes and structures in VB.NET resemble the following:

Public Structure MyPreciousVBStruct
   Private magicRing As Object
End Structure

Public Class MyPreciousVBClass
   Private magicRing As Object
End Class

And with slight syntax modifications they look like the following in C#:

public struct MyPreciousCSStruct
{
   private Object magicRing;
}

public class MyPreciousCSClass
{
   private Object magicRing;
}

Enough with the trivial examples, here are the key facts you need to know about value types versus reference types:

Lightweight Boxing

Before pulling ourselves away from the exciting Battle-of-the-Types headliner between value types versus reference types, we need to briefly cover a different type of boxing. Value objects come in two forms: boxed and unboxed. For VB.NET and C# programmers boxing is taken care of for you by the compiler. However, you still need to know what boxing is so you can impress your dinner party companions. (If you really want to box some value types around yourself, you can use C++ with Managed Extensions. There you have to do all the boxing yourself. Now you know why C++ programmers look angry all the time.)

So what is boxing? There are many instances in which you, or rather the compiler, must get a reference to an instance of a value type. For example, if you implement a computer graphics application you may decide to define a Vector as a structure, thinking that being allocated on the stack it would make your code faster. Now, let's assume that you need to fill an ArrayList with a set of Vector structures. The Add method of the ArrayList takes an Object as a parameter and expects a pointer to a heap-managed object. What's a compiler to do? It beats the structure into submission through boxing. Boxing is nothing more than creating a reference type out of a value type. Memory is allocated on the managed heap (including the additional overhead for objects such as a method table pointer and a SyncBlockIndex), the value type's fields are copied to their new home, and the address of the object is returned. When the "entity" is later referenced as a value type instead of an object, it is unboxed back into a structure.

Is all of this expensive? Yes. Therefore, just because as a VB.NET or C# wizard you don't have to do anything explicitly to make boxing and unboxing occur, you need to be aware of it in order to make intelligent choices between structures and classes. If your program spends more time boxing than making you rich, you chose poorly.

Access Levels

Sure, at times we want everything to be public; but secrets must be hidden and job security secured (or consulting work from a former employer). Such a bad joke brings us to access levels. The following table summarizes the essentials:

Access Modifier Affect on Classes Affect on Members
Public (VB.NET)
public (C#)
Can be instantiated by any object. Can be accessed from anywhere. (Free-for-all.)
Private (VB.NET)
private (C#)
Creatable only by objects of its own type or by types that it is nested within. Accessible only by members within the type that defines it.
Friend (VB.NET)
internal (C#)
Creatable only by members of the same assembly. Accessible by any members within the same assembly, but not from outsiders.
Protected (VB.NET)
protected (C#)
Only nested types can be protected. (Once they leave the nest, forget it.) Allows types inheriting from the parent type to have access to them. Accessible only by members within the type that defines it or those inheriting from the type. (Keep it in the lineage.)
Protected Friend (VB.NET)
protected internal (C#)
Used in combination, these keywords allow nested types to be creatable by members of the same assembly or by types inheriting from the parent type. Accessible by any types within the assembly as well as from any types inheriting from the owning type. (Keep it in the family.)
Table 1.2 Access Levels for Classes and Methods

Note there is not an Unprotected Friend access level. No one gets left behind! Also note the syntactical difference between the Friend VB.NET keyword and the internal C# keyword.

Namespaces do not allow access modifiers. Instead, you control the visibility of namespace members by specifying access levels on classes and structures. Speaking of namespaces, let's look at them in more depth.

Namespaces

Namespaces serve as a logical naming scheme for grouping related types. If some types logically belong together they can be put into a common namespace to express the fact. Besides being tidy, they also prevent namespace collisions (i.e. they're good for safety), prevent namespace pollution (i.e. they're good for the environment), and they provide scoping (i.e. they're good for the dietary tract).

Namespaces are imported using using in C# or Imports in Visual Basic. It often seems as if these directives specify a particular assembly, but they don't. A given namespace can span multiple assemblies, and an assembly can define multiple namespaces. The use of namespaces is for convenience only and separate from the need to explicitly reference other assemblies that your code is dependent upon. When the compiler needs to find the definition for a type, it prepends each of the different imported namespaces to the type name and searches each referenced assembly until a match is found.

Namespaces can be nested, but your top-level namespace name should be your full company name. This is similar to packages in Java as far as scoping is concerned although the similarity ends there.

Previous ] [ Index ] [ Next ]   

Amazon Honor System Click Here to Pay Learn More


All images and text on this site are licensed only for viewing on your computer during your visit. No rights to save, copy, print, redistribute, use in derivative works, or in any other manner are allowed or implied without the prior written consent of the author.

All images and text are ©Terry Smith unless otherwise noted. All rights reserved.

terry@terrysmith.net