At this point you might be asking yourself the following question: Given that the same functionality found in the generic Gen class can be achieved without generics, by simply specifying object as the data type and employing the proper casts, what is the benefit of making Gen generic? The answer is that generics automatically ensure the type safety of all operations involving Gen. In the process, generics eliminate the need for you to use casts and to type-check code by hand.
To understand the benefits of generics, first consider the following program that creates a non-generic equivalent of Gen:
// NonGen is functionally equivalent to Gen // but does not use generics. using System; class NonGen { object ob; // ob is now of type object // Pass the constructor a reference of // type object. public NonGen(object o) { ob = o; } // Return type object. public object getob() { return ob; } // Show type of ob. public void showType() { Console.WriteLine("Type of ob is " + ob.GetType()); } } // Demonstrate the non-generic class. class NonGenDemo { public static void Main() { NonGen iOb; // Create NonGen object. iOb = new NonGen(102); // Show the type of data stored in iOb. iOb.showType(); // Get the value in iOb. // This time, a cast is necessary. int v = (int) iOb.getob(); Console.WriteLine("value: " + v); Console.WriteLine(); // Create another NonGen object and // store a string in it. NonGen strOb = new NonGen("Non-Generics Test"); // Show the type of data stored in strOb. strOb.showType(); // Get the value of strOb. // Again, notice that a cast is necessary. String str = (string) strOb.getob(); Console.WriteLine("value: " + str); // This compiles, but is conceptually wrong! iOb = strOb; // The following line results in a runtime exception. // v = (int) iOb.getob(); // runtime error! } }
This program produces the following output:
Type of ob is System.Int32 value: 102 Type of ob is System.String value: Non-Generics Test
As you can see, the output is similar to the previous version of the program.
There are several things of interest in this version. First, notice that NonGen replaces all uses of T with object. This makes NonGen able to store any type of object, as can the generic version. However, it also prevents the compiler from having any real knowledge about the type of data actually stored in NonGen, which is bad for two reasons. First, explicit casts must be employed to retrieve the stored data. Second, many kinds of type mismatch errors cannot be found until runtime. Let's look closely at each problem.
First, notice this line:
int v = (int) iOb.getob();
Because the return type of getob( ) is now object, the cast to int is necessary to enable the value returned by getob( ) to be unboxed and stored in v. If you remove the cast, the program will not compile. In the generic version of the program, this cast was not needed because int was passed as a type argument when iOb was constructed. In the non-generic version, the cast must be employed. This is not only an inconvenience, but a potential source of error.
Now, consider the following sequence from near the end of the program:
// This compiles, but is conceptually wrong! iOb = strOb; // The following line results in a runtime exception. // v = (int) iOb.getob(); // runtime error!
Here, strOb is assigned to iOb. However, strOb refers to an object that contains a string, not an integer. This assignment is syntactically valid because all NonGen references are the same, and any NonGen reference can refer to any other NonGen object. However, the statement is semantically wrong, as the commented-out line shows. In that line, the return type of getob( ) is cast to int, and then an attempt is made to assign this value to v. The trouble is that iOb now refers to an object that stores a string, not an int. Unfortunately, without use of generics, the compiler has no way to know this. Instead, a runtime exception will occur when the cast to int is attempted. To see this for yourself, try removing the comment symbol from the start of the line, and then compiling and running the program. A runtime error will occur.
The preceding sequence can't occur when generics are used. If this sequence were attempted in the generic version of the program, the compiler would catch it and report an error, thus preventing a serious bug that results in a runtime exception. The ability to create type-safe code in which type-mismatch errors are caught at compile time is a key advantage of generics. Although using object references to create "generic" code has always been possible in C#, that code was not type-safe, and its misuse could result in runtime exceptions. Generics prevent this from occurring. In essence, through generics, what were once runtime errors have become compile-time errors. This is a major advantage.
There is one other point of interest in the NonGen program. Notice how the type of the NonGen instance variable ob is obtained by showType( ):
Console.WriteLine("Type of ob is " + ob.GetType());
No comments:
Post a Comment