Export (0) Print
Expand All

Dr. GUI .NET 1.1 #4

 

June 27, 2003

Summary: Dr. GUI describes System.Object, the base class of all other .NET Framework types, and shows how to use its methods in any .NET Framework type. The good doctor also. (nn printed pages)

Links

Dr. GUI .NET home page
Source code for this article

See the code
Run the sample DynamicCallASPX application.

Contents

Where We've Been; Where We're Going
System.Object: A Very Good Place to Start
The Documented Members
Runtime Type Information: Type and GetType
Getting Rid of Objects: A Bit More on Garbage Collection, Finalize, and Dispose
Give It a Shot!
What We've Done; What's Next

Speak Out!

If you have something you want to say, tell us and the world what you think about this article on the Dr. GUI .NET message board or read his latest thoughts in his blog.

Where We've Been; Where We're Going

Last time, we saw a fairly complete example of inheritance, using abstract (MustInherit) base classes and interfaces in a simple drawing application—and we did it using both Microsoft Windows® Forms and ASP.NET Web Forms. The ASP.NET application drew custom-drawn bitmaps on the fly as you did your drawing. Check out this cool application here.

This time, we're going to discuss the mother and father of all .NET classes: the venerable System.Object. We'll also discuss memory allocation and garbage collection (GC).

System.Object: A Very Good Place to Start

In the .NET Framework, all objects are derived, directly or indirectly, from a common base class: System.Object. Remember that inheritance/derivation tell us that the derived class IS-A specialized version of the base class. That means every object in the .NET Framework IS-A(n) Object and therefore implements all of the functionality of System.Object. In other words, the methods of System.Object are available in any .NET Framework object.

Given that, you can see that it's handy to know just what those methods are and how they're to be used and, if appropriate, overridden.

What's in System.Object?

Since it's handy to know what all objects can do, let's find out what's in System.Object. As with all things in programming, there are two versions of what's in there: the version according to the documentation, and what's really there. We'll look at both, and then show that you get a complete picture, for all practical purposes, from reading the documentation.

According to the documentation

According to the documentation for System.Object, there is one public constructor (which takes no parameters and does nothing) and six instance members: the public members, Equals, GetHashCode, GetType, ToString, and the protected members, Finalize and MemberwiseClone. There are also two public static/Shared methods: ReferenceEquals and an overload of Equals. We'll discuss in-depth what each of these does later.

According to ILDASM

We've seen in Dr. GUI.NET 1.1 #0 and Dr. GUI.NET 1.1 #1 that you can use ILDASM to examine the metadata and IL of your programs. You can also use ILDASM to examine the metadata and IL of the system files. From the metadata for any type (double-click the .class line, which has a red triangle), we know that System.Object is found in mscorlib.dll.

Oh where, oh where are my .NET system files?

You'll need to know where the .NET system files are located in order to find them: They're all in the "Microsoft.NET\Framework" directory tree under your Windows directory. Each version of the .NET runtime is in a separate directory named for the version number. On Dr. GUI's machine, the path is C:\WINDOWS\Microsoft.NET\Framework\v1.1.4322\mscorlib.dll.

Note that, for some reason, the "Search for files" function in Windows 2000 (but not Windows XP) will not find certain system files and directories, despite the fact that they're not hidden. The program gives you no warning that it's doing this to you, and the Help file fails to mention this fact. Nor could Dr. GUI find a way (besides upgrading to Windows XP) to shut off this nasty behavior.

Based on what we just learned, we can find the file (as Dr. GUI recalls, the "Search for files" function will work under Windows 2000 if you start in the "Microsoft.NET" directory under your Windows directory). Then start ILDASM and use the File.Open command to open the mscorlib.dll file.

Wow: metadata on the system!

When you open the System namespace, you'll see a tree with most of the types in System listed. The icon in front of each name indicates what kind of type: the blue, downward-pointing arrow-like thingies, each with a red stripe, are namespaces (even in the metadata viewers, types are contained in namespaces!); the blue boxes with the lines coming out the right are reference types; the light brown boxes with the lines coming out the right are value types (more on value types later); the value types with the letter "E" are enumerated types; and the reference types with the letter "I" are interfaces. Most (if not all) of the core .NET Framework types are here. You can browse their metadata and IL just as you can your own types. (Note that the IL for some methods is hidden, sometimes because that method calls native code.)

Undocumented members?

When you open System.Object, you'll see many more methods than we discussed above. The little purple box indicates a method; you'll note that private members are not distinguished from others. You'll have to double-click each member to see whether it's private or not.

When you do this, you'll discover that all of the undocumented members are private. As a rule, private members are not documented in the .NET Framework—after all, you can't ever call them (at least not directly—see below). In fact, several of the undocumented members changed their names between Beta1 and release. One can easily change the names of undocumented members, since no one besides the code in that particular class is supposed to use them.

The documented members are either public—accessible from any place—or "family," which is the .NET name for what C# calls "protected"—accessible only from the same or derived classes.

Frankly, Dr. GUI doesn't have a clue what most of these private methods do. Nor does he need to. He imagines the FieldSetter/FieldGetter pair and GetFieldInfo is somehow used for serialization, but doesn't have a good idea about either InternalGetType or FastGetExistingType, although he notes that they're both called by GetType.

The point isn't what these methods do, because you won't normally call them. (As it turns out, you can call even private members using reflection, provided you have the right security privileges—marking a something private is NOT a security mechanism!) The point is that the metadata allows you to see more of what's really in the class, even the private members that aren't documented (because there's no need to document them).

And it's instructional to compare the documented functionality of each of the methods with the IL. For instance, the documentation says that the constructor initializes the object. But Object has no fields, so there's nothing to initialize. Looking at the IL for the constructor (named ".ctor" in ILDASM) verifies this: It consists of just a RET (return to caller) instruction. By the same token, Finalize does nothing in class Object.

You can get an idea of what some of the other methods do by looking at the IL and comparing it to the documentation.

The Documented Members

As we just discussed, there are nine documented members: the constructor and the eight methods GetType, ToString, Finalize (destructor in C#), Equals (with static/Shared and instance overloads), a static/Shared ReferenceEquals, MemberWiseClone, and GetHashCode. All of the instance methods except GetType and MemberwiseClone are virtual/Overridable and can be overridden by any type that is inherited from Object (as all types are). GetType is not virtual/Overridable because its implementation never changes for any type. MemberwiseClone is likewise not virtual/Overridable because it's a helper function that also never changes for any type. Classes that support being cloned should implement the ICloneable interface and its Clone method. (Your implementation of Clone could, however, call Object.MemberwiseClone as part or all of what it does.)

Constructor

If you don't define any constructors, the compilers will give all types, including Object, a constructor that takes no parameters. (If you define any constructors, this default constructor goes away, but you can define one of your own.) In C#, the constructor is a method that is named the same as the class—so for class Foo, the constructor would be the method also named Foo. In Microsoft Visual Basic® .NET, the constructor is always named New.

Your constructors should always call exactly one constructor in your base type. The compilers will automatically generate a call to the base type's constructor that takes no parameters for you, but if you want to pass parameters to your base type's constructor, you'll have to remember to explicitly call the correct constructor at the beginning of your constructor. (The syntax for this varies from language to language.)

The base type's constructor will call a constructor of its immediate base type, and so on, creating a chain of constructor calls all the way back to System.Object. The call to the base type's constructor is done before the body of the current class's constructor is executed, ensuring that all of your base classes are constructed before your constructor runs.

If you look at the IL for Object's constructor, you'll see that it does nothing; it doesn't even call another constructor. That's because Object (and Object alone) has no base class. There's nothing else to do because Object has no fields to initialize, either.

Finalize/Destructor

If you can initialize an object in its constructor, you should be able to clean it up as well. And indeed you can, in Visual Basic .NET, by overriding the Finalize method in your class. In C#, the analogous method is called a destructor—it's named the same as the class with a leading "~" (similar to C++ syntax). However, the behavior of C# destructors is somewhat different from the analogous methods in both Visual Basic .NET and C++. (We'll discuss this shortly.)

In general, you should not write a Finalize method/destructor unless your object controls some resource (such as file handles, window handles, graphics objects, or database connections) other than managed memory. If your object merely has references to other objects, garbage collection will take care of freeing all the memory and other objects correctly. If one of those objects needs a finalizer, it should provide it. If you're writing a Finalize method in Visual Basic .NET, be sure to remember to call your base class's Finalize at the end of your class's Finalize (if your base class has a Finalize, that is).

The reason to avoid finalizers, unless you absolutely need them, is that objects with finalizers take much longer to garbage collect than do objects without, so don't override Finalize "just to be safe." Before the object that needs finalization can be garbage collected, it must first be placed in a finalization queue, where a background thread calls the finalizer. Only after its finalization is complete can it be garbage collected, so finalized objects always take at least two garbage collection cycles to be collected. So make sure you actually need to do the finalization, as finalizing objects needlessly will hurt your program's performance.

Further, you should always write a Dispose method implementing IDisposable if you need to free unmanaged resources. There's a standard design pattern for this in the .NET Framework documentation, and we'll discuss it more a little later. So the rule is this: if your class manages unmanaged resources, you absolutely need to implement both Finalize (or a destructor in C#) and Dispose following the standard design pattern.

And if you use a class that implements IDisposable, be sure to call Dispose (using the using statement in C# or using an appropriate Try/Finally block in Visual Basic .NET—see Dr. GUI .NET 1.1 #3 for details) as soon as you're done with the object. The fact that the class author implemented IDisposable is a clear indication that the class controls some unmanaged resource, so it's important to clean up after yourself as soon as possible. We'll talk about this next. You can also go to the previous article for an example of calling Dispose properly. (Check out the drawing methods.)

Finalizers and exceptions

If Finalize or an override of Finalize throws an exception, the runtime ignores the exception, terminates that Finalize method, and continues the finalization process.

Finalize and C# destructors

The C# syntax for finalizers is similar to the C++ syntax—they're called "destructors" and are even named as they are in C++: the class name with a "~" (tilde) prepended.

Although this looks like a C++ destructor, it's basically just "syntactic sugar" for the Finalize method. In fact, a C# destructor generates a Finalize method with one important difference: It also generates a call to the base class's Finalize method at the end of the method (if your base class has a Finalize method, that is). In that way, your finalization runs before you call your base class's finalizer—the opposite of construction order.

In C#, you're not allowed to write a Finalize method—your only option is to write a destructor. That means you don't have to remember to call your base class's Finalize.

Again, if you're using a language other than C#, you'll likely, depending on the language you're using, have to remember to call your base class's Finalize method (if there is one) at the end of your Finalize method. If you forget, you may leak resources from your base classes.

Remember: don't write a destructor or finalizer in any .NET language unless the type you're designing absolutely requires it. Dr. GUI can't emphasize this enough.

Not deterministic

If you're used to C++ destructors, you'll be disappointed about this interesting aspect of .NET finalizers: You can't know exactly when they'll be called.

Specifically, they're called not long before the object is garbage collected. When will that be? When the system gets around to it. You can ask it to do a garbage collection, but that's usually not what you want to do—and doing so isn't guaranteed to call all the finalizers right away in any case.

How to write finalizers and Dispose

So, what if your object controls some critical and expensive resource that needs to be cleaned up right away, such as a database connection or file handle? The right answer is not to rely on Finalize. Rather, you should write a Dispose method to implement IDisposable and take responsibility for calling it when you're done with your object.

The Dispose method should first check to see if it's been called already. If it hasn't, it should do the necessary cleanup, then call base.Dispose()/MyBase.Dispose() if your base class has a Dispose method. Finally, your Dispose method should call System.GC.SuppressFinalize to allow the object to skip the finalization step in garbage collection (we're assuming you know it's safe to skip finalization because you've designed Dispose correctly in your class and its base classes).

We also write a finalizer to make sure that we clean up even if our user forgets to call Dispose. The Finalize method should call Dispose to do the cleanup you need, then call your base class's Finalize, if it exists. (The base class call is automatic in C#'s destructor.)

If there's a chance that Dispose could throw an exception, be sure to use a try/finally statement with the call to base.Finalize()/MyBase.Dispose() in the finally block, so that your object's finalization will complete.

For more complete information and for sample code in C# and Visual Basic .NET, look at the design pattern for finalizers and Dispose in the .NET Framework SDK.

Not absolutely guaranteed

In addition to the issue that you don't know when the finalizer will be called, you're not absolutely guaranteed that Finalize will be called at all. Specifically, the Finalize method might not run to completion or might not run at all in the following exceptional circumstances:

  • Another finalizer blocks indefinitely (goes into an infinite loop, tries to obtain a lock it can never obtain, and so on). Because the runtime attempts to run finalizers to completion, other finalizers might not be called if a finalizer blocks indefinitely.
  • The process terminates without giving the runtime a chance to clean up. In this case, the runtime's first notification of process termination is a DLL_PROCESS_DETACH notification.

The runtime continues to Finalize objects during shutdown only while the number of finalizable objects continues to decrease.

ToString

The ToString method returns a String that contains a culture-sensitive string representation of your object. By culture-sensitive, we mean that the current culture (for instance, English (US)) might affect the return value from ToString. For example, 1.23.ToString() might return "1.23" in the United States, but "1,23" in Europe.

The implementation of ToString in System.Object returns the fully qualified type name of your object.

The built-in value types override ToString to return a culture-senstive string representation of their data—for instance, Int32, DateTime, or most any type. ToString returns a string representation of the date and time stored in the object. For example:

   New DateTime(2001, 2, 1, 3, 24, 56).ToString()

results in the string containing the date and time in the appropriate format for the current culture.

It's very common to override ToString in your own classes to return the string representation you want. For instance, a point class might return the coordinates separated by commas and enclosed by parentheses, such as "(2, 3)". There was an example of overriding ToString in the discussion of Polymorphism, Virtual, and Overrides in Dr. GUI .NET 1.1 #2.

MemberwiseClone

MemberwiseClone returns a new object that's a member-by-member duplicate of the original object. This type of copy is called a shallow copy. The contained objects to which your object's references refer are not copied. Therefore, both the original object and the cloned object will refer to the same contained objects.

MemberwiseClone is protected, so it can only be called from a method of the object it's cloning. And it's not virtual/Overridable, so it can't be overridden.

Its primary use is as a helper function for your own Clone method. It's handy because it will clone your object, including the base-class portion of your object, without the derived code having to know the details of the implementation.

If you want to allow users to clone objects of your class, implement the ICloneable interface, including its Clone method. If you just want a shallow copy (in other words, you copy references but not the contained objects to which those references point; the cloned object will point to the same contained objects as the original), just call MemberwiseClone. If you want to copy the contained objects, you could either copy the members yourself or call MemberwiseClone; then copy the sub-objects to which the old object's references refer and make the new object's references refer to the copied sub-objects. Copying the entire data structure referred to by an object is called a deep copy.

GetType

The GetType method returns a reference to your object's Type object. The Type object allows you to access a complete description of your object's metadata—including fields, methods, events, properties, and constructors of the class, plus parameters and return types of all the preceding, plus information on the module and assembly that contain the class. Note that this metadata includes information on all members, including private members. So it is not a security measure to mark a member private, since anyone can access any member through the metadata, even if not directly. This access includes the ability to get or set a private field and to call a private method. If you need security, use an actual security mechanism, such as code access security.

The metadata is represented as a network of collections of typed objects. For instance, there's a collection of MethodInfo objects that contains information on each method in your class. In fact, the MethodInfo object even contains an Invoke function that you can use to call the method. (By the same token, the FieldInfo object contains methods for getting and setting the field's value.)

For any given type, there is exactly one Type object in the application domain that describes it. That means if two objects return the same value from GetType, those objects are of the same type—and if the values are different, the objects are of different types.

The Type class also contains some interesting static methods that allow you to obtain (or create, if it doesn't already exist) a Type object on the fly. For instance, the Type.GetType static method takes a string containing the fully qualified type name. You call this to get the Type object corresponding to that name. Once you have that Type object, you can call Activator.CreateInstance to create an instance of that type. At that point, you can find out what methods are implemented by that type and call them using MethodInfo.Invoke, or otherwise manipulate the object.

We'll see an example of dynamically creating an object later on. The good doctor thinks you'll be amazed by it.

Equals (Instance Method)

Value vs. reference equality

The discussion of Equals (and the closely related GetHashCode) is a rather tricky one in any language with references. Should the equality operator return true only if the two references refer to the same object? That's called reference equality, or identity.

Or should it return true if the two objects contain the same value, regardless of whether the references refer to two distinct objects? That's called equality or, more specifically, value equality.

The implementation of Equals in System.Object uses reference equality, which is the only type of equality that's guaranteed valid for all objects. (Theoretically, the contract for Equals is for value equality, but it's very difficult to implement in the general case for reference types, so the .NET Framework uses reference equality by default instead.) So Equals returns true if the two references (this and the parameter) are the same. In other words, if the two references refer to the exact same object, we know for sure that they're equivalent.

Value types, such as Int32, use value equality: If all of the fields of the object are the same, the objects are considered equal. The base implementation of Equals for value types is in System.ValueType. Note that all value types in the .NET Framework Class Library are derived from System.ValueType. (In C#, you use struct rather than class to declare a value type; in Visual Basic .NET, it's Structure rather than Class.) Note further that many value types override Equals for greater efficiency. If you take a look at the IL code for System.ValueType.Equals, you'll see a fairly complicated method. It's pretty easy to write one that's faster.

You generally shouldn't override Equals in most reference types. For reference types, reference equality is usually appropriate. The exception to this rule is when your reference type looks like a base (or value) type—such as String, Point, Complex, BigNumber, and so on.

If your class is a value type, the built-in value equality operator might be sufficient, although it's fine to override Equals for greater efficiency.

Note that if you override Equals, you must override GetHashCode as well (as shown next). Also, you should consider implementing the IComparable interface so your objects can be used in ordered data collections.

One of the most obvious exceptions to the rule about using reference equality for reference classes is class String. (Strings are exceptional in so many ways, aren't they?) Although strings are reference types, every programmer expects them to have value equality semantics. For instance, you'd expect the following to work as described in the comments:

[Visual Basic .NET]
   Dim a as String = "Hello"
   Dim b as String = String.Copy(a)
   Console.WriteLine("{0}: a.Equals(b)", a.Equals(b)) ' true
   Console.WriteLine("{0}: a == b", a = b) ' true
   Console.WriteLine("{0}: a and b are the same string", _
      a Is b) '// false
   Console.WriteLine("{0}: a and b are the same string using RefEq", _
      Object.ReferenceEquals(a, b)) ' still false :)

[C#]
   String a = "Hello";
   String b = String.Copy(a);
   Console.WriteLine("{0}: a.Equals(b)", a.Equals(b)); // true
   Console.WriteLine("{0}: a == b", a == b); // true
   Console.WriteLine("{0}: a and b are the same string", 
      (Object)a == (Object)(b)); // false
   Console.WriteLine("{0}: a and b are the same string using RefEq", 
      Object.ReferenceEquals(a, b)); // still false :)

Note that we cast the String objects to Object in the last WriteLine (in Visual Basic .NET, we simply used the Is operator) so that we'll use the built-in operator == for references, which does reference equality. You can use this trick any time you want to do reference equality—it's especially useful when comparing to null. We also use the Object.ReferenceEquals static/Shared method to see if the two references are the same. Since this is just a call to a .NET Framework method, it works the same in both languages, so Dr. GUI prefers to use Object.ReferenceEquals.

Note that if String.Equals and String.operator == used reference equality, as other reference types typically do, the preceding code example would not work as we expect because a and b don't refer to the exact same String in memory. But because String uses value equality, the preceding code example does indeed work as you'd expect.

All of that is a long way of saying that there are some cases, even for reference types, where you'll want to override Equals in your class. But if you do, you also really, really, really should override GetHashCode, as described next.

If you override Equals, your implementation should be:

  • Type-safe: If the parameter is not your type, you should return false; two objects of different types can't be equal.
  • Reflexive: x.Equals(x) must return true for all values of x.
  • Symmetric: x.Equals(y) must return the same value as y.Equals(x) for all values of x and y.
  • Transitive: If x.Equals(y) is true and y.Equals(z) is true, then x.Equals(z) must be true for all values of x, y, and z.
  • Consistent: x.Equals(y) must return the same value consistently, unless x or y are modified.
  • Non-null: x.Equals(null) returns false if x != null.

You'll want to look at the guidelines for implementing the Equals method.

Equals vs. C#'s operator ==

If you program in C++ or C#, you'll have noticed that there's a built-in operator that looks a lot like Equals: the == operator. These two are not the same.

Equals is a method. The == operator is built into the C and C# languages—there is not a method for it in most objects. Instead, the language generates the appropriate operation to implement the == operator. For instance, it generates the IL for reference comparison for Object, the IL for a value comparison for integers, and a call to String.Equals for Strings.

Now it is possible to overload operators in C# and C++, so if you're writing your class for C# and/or C++ programmers, you may want to provide an overloaded operator == for your class. In general, you should do this for value types, but usually not for reference types, even if the reference type overrides Equals. Note that as you overload operator == in C#, you should provide the appropriately named method for use by languages that don't support overloaded operators.

If you don't overload operator ==, the compiler will, for all reference types except String, do a reference comparison. Note that the compiler will not in general call Equals, so using == is usually faster than calling Equals, which forces the compiler to generate the method call.

You do not necessarily need to override GetHashCode just because you overload operator ==. But if you overload operator ==, you should consider whether to override Equals as well. Programmers expect Equals and operator == to behave in the same manner, especially when they put your objects in .NET Framework data containers, such as ArrayLists and HashTables, that use Equals. If you do override Equals, you should also override GetHashCode so that objects of your class can be used as a key to a hash table.

static/Shared Equals

In addition to the instance Equals method, there is a static/Shared version of Equals. The following two calls are almost exactly equivalent, assuming a and b are the same type:

  • Instance: a.Equals(b)
  • static/Shared: Object.Equals(a, b)

The difference is that the static/Shared version tests to make sure that a and b aren't null/Nothing before calling a.Equals(b)—so it's more appropriate to use if a might be null/Nothing.

But note that it WILL, as documented, call overrides of Equals if you've overridden it. If you doubt the documentation, take a look at the current implementation in ILDASM.

ReferenceEquals

ReferenceEquals is a static/Shared method that always does reference equality—so it compares the two references and returns true only if they're equal. When you want to force the use of reference equality, use ReferenceEquals as in the example above.

GetHashCode

GetHashCode is the last of the methods we'll discuss. Its use is very limited: Its job is to return an Int32 hash code so the object can be used as a key by the HashTable class.

The implementation in Object.GetHashCode returns a unique hash code for each object. This works okay (but not great) if your object uses reference equality semantics.

But if you override Equals, you must also override GetHashCode. If you don't, someone somewhere will someday use your objects as keys into a hash table, and their code will break, since the HashTable class will compare two objects that have different hash codes even though Equals returns true.

Writing good hash functions is an art, not quite a science, and it's a bit complicated to explain in detail in this column. (Check out a good computer science book on data structures for more information. Many have chapters on hash tables and functions.) According to the documentation for Object.GetHashCode:

Hash functions usually are specific for each type, and should use at least one of the instance fields as input. A hash function has two interesting properties:
  • If two objects of the same type represent the same value, the hash function must return the same constant value for each object.
  • A hash function should generate a random distribution for all input for the best performance.

Hash functions should also execute very quickly, since they'll be called every time you do a lookup in the table (as well as every time you insert a key/object pair).

Note also that the objects used as keys should be immutable. If they change, the value of GetHashCode will change, and you won't be able to find the key in the hash table because it was stored using the old value. It's a good idea for the types to be immutable by contract (meaning there are no methods that will change the object's value), but it's okay to use mutable objects if you're very sure that you won't modify the keys stored in the hash table after they're stored.

You'll need to read this documentation in full and understand your data well before you can write a GetHashCode. Unfortunately, providing the information you'll need to do this well is beyond the scope of this column.

Runtime Type Information: Type and GetType

As mentioned earlier, the Type object returned by Object.GetType is very powerful. In fact, you can use this object to create objects of the corresponding type and call any of its methods or get/set any of its fields, including private members and fields.

As an example, the following short program asks you to type in the name of a type and the name of a method in that type. Then, in three lines, it creates the Type object, creates an instance of that object, and calls the method. And the type and method are based on the strings you type in!

Here's the console code in C# (or you can see the entire source file):

using System;
using System.Reflection;

// the first type for dynamic creation, with a HiThere method
class Type1 {
   public String HiThere() {
      return "Type1: Hi there!";
   }
}

// the second type for dynamic creation, with HiThere and
//    HelloWorld methods
class Type2 {
   public String HiThere() {
      return "Type2: Hi there!";
}

   public String HelloWorld() {
      return "Type2: Hello, world!";
   }
}

class TheApp {
   public static void Main() {
      // get names of type and method
      Console.Write("Type name: Type1 or Type2 ");
      String typeName = Console.ReadLine();

      Console.Write(
         "Method name: HiThere or HelloWorld (Type2 only) ");
      String methodName = Console.ReadLine();

      // create the Type object      
      Type t = Type.GetType(typeName);

      // create an instance of that type
      Object o = Activator.CreateInstance(t);

      // call the requested method
      t.GetMethod(methodName).Invoke(o, null);
   }
}

And here is the code for the console application in Visual Basic .NET (or you can see the entire source file):

' Note: If you run this in a Visual Studio VB Console App project,
' VS will insert a namespace for you, causing creation of the type to
' fail unless you type the name of the namespace. To allow this program
' to work, right click on the project in Solution Explorer, then select
' Properties. The "Root namespace" box will contain your project name.
' Erase it and rebuild your project.

' The first type for dynamic creation, with a HiThere method
Class Type1
    Public Function HiThere() As String
        Return "Type1: Hi there!"
    End Function
End Class

' The second type for dynamic creation, with HiThere and
'    HelloWorld methods
Class Type2
    Public Function HiThere() As String
        Return "Type2: Hi there!"
    End Function

    Public Function HelloWorld() As String
        Return "Type2: Hello, world!"
    End Function
End Class

Class DynamicExecution
    Public Shared Function InvokeStringVoid( _
         ByVal typeName As String, ByVal methodName As String)
        ' Create the Type object      
        Dim t As Type = Type.GetType(typeName)

        ' Create an instance of that type
        Dim o As Object = Activator.CreateInstance(t)

        ' Call the requested method
        Dim s As String
        Try
            s = CType( _
                t.GetMethod(methodName).Invoke(o, Nothing), String)
        Catch e As System.Exception
            s = "Exception thrown"
        End Try
        Return s
    End Function
End Class

Class TheApp
    Public Shared Sub Main()
        ' Get names of type and method
        Console.Write("Type name: Type1 or Type2 ")
        Dim typeName As String = Console.ReadLine()

        Console.Write( _
            "Method name: HiThere or HelloWorld (Type2 only) ")
        Dim methodName As String = Console.ReadLine()
        Dim s As String = _
            DynamicExecution.InvokeStringVoid(typeName, methodName)
        Console.WriteLine(s)
        Console.ReadLine()  ' wait for user before exiting
    End Sub
End Class

This is a very simple example, but you can do things such as passing parameters, using the return value from the method, and setting/getting fields and properties.

The code for the ASP.NET version contains the first three classes from above (Type1, Type2, and DynamicExecution) and also contains an event handler for the button, which reads the text boxes, validates the contents, and executes the method if the contents of the text box are okay.

Note   The validation step is absolutely crucial for Web applications. If you fail to do this, you open a huge security hole—anyone could cause your server to execute any .NET Framework method that took no parameters and returned a string! Don't! Always, always, Alwaysvalidate input that comes over the Internet!
In fact, we'd still have to validate input in code, even if we'd constrained the user's input on the Web page (for example by using a pair of drop-down menus instead of text boxes), because someone who wanted to hack our system could "skip" the user input part and fake the HTTP submission.

Here's the event handler for the button. Note that if you run this code on your machine, you'll have to change the namespace name if it's different from the one the good doctor chose. You can check out this application here, and you can see the complete code.

Private Sub Button1_Click(ByVal sender As System.Object, _
        ByVal e As System.EventArgs) Handles Button1.Click
    Dim typeName As String = TextBox1.Text
    Dim methodName As String = TextBox2.Text
    If (typeName <> "Type1" And _
            typeName <> "Type2") Or _
            (methodName <> "HiThere" And _
            methodName <> "HelloWorld") Then
        Label1.Text = "Bad type or method name, please retype!"
    Else
        typeName = "DynamicCallASPX." + typeName ' add namespace
        Label1.Text = _
            DynamicExecution.InvokeStringVoid(typeName, methodName)
    End If
End Sub

Note that our checks only insure that the type name is one of the two valid names and the method name is one of the two valid method names. This check allows Type1.HelloWorld to slip through. But that's not a problem, because we know that the bad name will throw an exception and not cause a security problem.

Getting Rid of Objects: A Bit More on Garbage Collection, Finalize, and Dispose

We've already discussed the fact that the .NET Framework is a garbage-collected runtime environment. What that means is that while you're still responsible for allocating the objects you need (generally by calling new/New), you don't have to free them. The system will automatically detect when an object you've allocated is no longer being used and free it. Between the time it detects the object is no longer in use and when the system frees it, the system will call your finalizer if you've overridden it.

How Garbage Collection (GC) Works

Periodically your program will be paused to do a garbage collection. At that time, the system looks through the global variables and stack to determine which objects are currently being referenced. It also walks references in the objects themselves to properly account for linked lists and other linked data structures. Being able to know where the references are in your objects is one of the reasons .NET Framework data is always strongly typed.

The objects that are not in use are freed (if they don't need finalization) or put in a list for finalization. This extra step for finalization is the biggest reason to avoid writing finalizers you don't need. After the objects are finalized, they're freed in some future garbage collection.

This is a very quick overview of .NET Framework garbage collection. For much more in-depth views, see Part 1 and Part 2 of Jeffrey Richter's excellent articles on the topic in MSDN Magazine. Also, see Rico Mariani's excellent article Garbage Collector Basics and Performance Hints.

The Bugs GC Prevents

Windows C++ programmers don't have garbage collection. As a result, they're prone to two types of errors: freeing objects too early and forgetting to free them at all. If you free an object too early, you can call methods on it or set its fields after it's been freed—and after some other object is using its memory. This type of bug can be incredibly difficult to find!

If you forget to free an object at all, the memory allocation for your program will grow. If you do this in a loop, it can grow without bound. Well, in reality it's bounded—by the amount of virtual memory on your machine. Forgetting to free objects results in what is called a memory leak. Memory leaks are a major source of headaches, especially in server programming, since the programs running on a server run indefinitely.

Since the .NET Framework frees memory for you (but only after you're done with it), both of these bugs are eliminated from .NET Framework programs, regardless of which managed language you're using.

GC also makes a hard problem easier. Have you ever tried to return a dynamic object by reference in C++? For instance:

Foo & MyFunc(int i) {
   return new Foo(i);
}
// call as in Foo f = MyFunc(5);

It's easy to return the object by address. What's hard is knowing what to do with it afterwards. There's no good way to delete the object, so you've likely caused a memory leak. And if you did figure out how to delete it, should you? Was it dynamically allocated to begin with? Does it use the standard delete operator?

The garbage collection in the .NET Framework takes care of this. All objects are returned by reference, and all will be automatically cleaned up when no longer in use.

How GC Changes Your Programming Style

When Dr. GUI first started programming using a garbage-collected system, he found it weird. To a C++ programmer, there's something just wrong about allocating objects and never freeing them. And because so many types are immutable, you often create them and throw them away very quickly.

In a garbage-collected system, that's okay. The system is designed to handle allocations very quickly, so the speed penalty is low. And if the object isn't used for long, cleaning it up is optimized to be very quick as well. It's hard to learn not to worry about allocating memory being expensive, but you really can do it!

Oh, and in the .NET Framework, you can always use value types if you want to avoid heap allocations altogether.

Give It a Shot!

Be the First on Your Block...

Since the .NET Framework is about doing distributed applications well, it stands to reason that it's no fun to program alone. So invite a friend or two or three to come learn with you. It'll be more fun, and you'll all learn more!

Some Things to Try...

Write a point class for three-dimensional space (with coordinates x, y, and z). For now, make this a reference type, not a value type. (Extra credit: make it a value type after you get it working as a reference type.)

In your point class, override ToString, Equals, and GetHashCode. Add any other methods you like (including, perhaps, overloaded operators).

Next in your point class, implement the ICloneable interface so that you can copy your object by calling Clone. If you like, implement Clone in two identical classes both by calling MemberwiseClone and by assigning the fields; then see which runs faster. (Time a bunch of clone operations—say 10,000 or 100,000—and use the system timer to time.)

Look at the generated metadata and code in ILDASM.

Try writing some other objects.

Use GetType to examine the metadata for your objects and set a field and/or call a method on an object.

What We've Done; What's Next

This time, we discussed the mother and father of all .NET classes: the venerable System.Object. We also discussed memory allocation, garbage collection, and how to properly dispose of unmanaged resources.

Next time, we'll talk about strings.

Show:
© 2014 Microsoft