[ Previous ]
[ Index ]
[ Next ]
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.
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.
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.
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:
- Value types are allocated on the stack, reference types on the heap.
- Value types inherit from System.ValueType that in turn derives from System.Object.
- Structures are best used for lightweight objects only. For example, System.Int32,
System.Boolean, and System.Decimal are all defined as structures, not classes. A structure
can have a reference type like a String as a member though you're not really gaining yourself
anything by doing so.
- Structures can have methods and properties just like classes can.
- All members of a structure as initialized to 0 by default courtesy of the default
constructor. (Reference type variables are assigned to null by default.) You can define custom constructors for structures; however, any constructors you define must take at least one parameter.
- The VB.NET and C# compilers do not allow structures to define their own destructors. (Destructors are called finalizers in VB.NET, as we will see shortly.) However, this is not an expressed requirement of the Common Language Runtime (CLR).
- A field-by-field copy is made when you assign one value type variable to another. When you assign a reference type variable to another reference type variable, only the memory address is copied.
- When two value types are compared in code, a field-by-field comparison is made. Quite simply, the
System.ValueType overrides the Equals method to perform this comparison.
- Structures cannot inherit from classes (except, of course, for the implicit
System.ValueType class) or other structures; however, structures can
implement interfaces.
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.
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 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 ]