By Akash MaruWhen we look at the term "generic", unrelated to the programming world, it simply means something that is not tied to any sort of brand name. For example, if we purchase some generic dish soap, soap that has no brand name on it, we know that we are buying dish soap and expect it to help us clean our dishes, but we really don't know what exact brand (if any) will be inside the bottle itself. We can treat it as dish soap even though we don't really have any idea of its exact contents.
Think of Generics in this manner. We can refer to a class, where we don't force it to be related to any specific Type, but we can still perform work with it in a Type-Safe manner. A perfect example of where we would need Generics is in dealing with collections of items (integers, strings, Orders etc.). We can create a generic collection than can handle any Type in a generic and Type-Safe manner. For example, we can have a single array class that we can use to store a list of Users or even a list of Products, and when we actually use it, we will be able to access the items in the collection directly as a list of Users or Products, and not as objects (with boxing/unboxing, casting).
"Generic" Collections as we see them today
Currently, if we want to handle our Types in a generic manner, we always need to cast them to a System.Object, and we lose any benefit of a rich-typed development experience. For example, I'm sure most of us are familiar with the System.Collection.ArrayList class in Framework v1 and v1.1. If you have used it at all, you will notice a few things about it:
1. It requires that you store everything in it as an object
public virtual int Add(object value);
public virtual object this[int index] {get; set;}
2. You need to cast up in order to get your object back to its actual Type
3. Performance is really lacking, especially when iterating with foreach()
4. It performs no type safety for you (no exceptions are thrown even if you add objects of different types to a single list)
System.Collections.ArrayList list = new System.Collections.ArrayList();
list.Add("a string");
list.Add(45); //no exception thrown
list.Add(new System.Collections.ArrayList()); //no exception
foreach(string s in list) { //exception will be thrown!
System.Console.WriteLine(s);
}
For some situations we may feel the need to implement IEnumerable and
IEnumerator in order to create a custom collection of our given Type,
that is, a Type-safe collection for our needs. If you had a User
object, you could create another class (which implements IEnumerable and
IEnumerator, or just IList) that allows you to only add and access User
objects, even though most implementations will still store them as
objects. It is a lot of work implementing these two interfaces, and you
can imagine the work needed to create this additional collection class
every time you wanted a Type-safe collection.
The third and final way of creating a collection of items is by simply creating an array of that type, for example:
string[] mystrings = new string[]{"a", "b", "c"};
This will guarantee Type safety, but is not very reusable nor very
friendly to work with. Adding a new item to this collection would mean
needing to create a temporary array and copy the elements into this new
temporary array, resizing the old array, copying the data back into it,
and then adding the new item to the end of that collection. In my
humble opinion, this is too much work that tends to be very error prone.
What we need is a way to create a Type-safe collection of items that we
can use for any type imaginable. This template, or generic class,
should be able to perform all of the existing duties that we need for
our collections: adding, removing, inserting, etc. The ideal situation
is for us to be able to create this generic collection functionality
once and never have to do it again. You must also consider other types
of collections that we commonly work with and their functionality, such
as a Stack (First in, Last out) or a Queue (First In, First out), etc.
It would be nice to be able to create different types of generic
collections that behave in standard ways.
In the next I will show you how to create your first Generic Type.
Creating Our First Generic Type
In this section we will create a very simple generic class and
demonstrate how it can be used as a container for a variety of other
Types. Below is a simple example of what a generic class could look
like:
public class Col<T>
{
T t;
public T Val{get{return t;}set{t=value;}}
}
There are a few things to notice. The class name "Col<T>" is our
first indication that this Type is generic, specifically the brackets
containing the Type placeholder. This Type placeholder "T" is used to
show that if we need to refer to the actual Type that is going to be
used when we write this class, we will represent it as "T". Notice on
the next line the variable declaration "T t;" creates a member variable
with the type of T, or the generic Type which we will specify later
during construction of the class (it will actually get inserted by the
Common Language Runtime (CLR) automatically for us). The final item in
the class is the public property. Again, notice that we are using the
Type placeholder "T" to represent that generic type for the type of that
property. Also notice that we can freely use the private variable "t"
within the class.
In order to use this class to hold any Type, we simply need to create a
new instance of our new Type, providing the name of the Type within the
"<>" brackets and then use that class in a Type-safe manner. For
example:
public class ColMain
{
public static void Main()
{
//create a string version of our generic class
Col<string> mystring = new Col<string>();
//set the value
mystring.Val = "hello";
//output that value
System.Console.WriteLine(mystring.Val);
//output the value's type
System.Console.WriteLine(mystring.Val.GetType());
//create another instance of our generic class, using a different type
Col<int> myint = new Col<int>();
//load the value
myint.Val = 5;
//output the value
System.Console.WriteLine(myint.Val);
//output the value's type
System.Console.WriteLine(myint.Val.GetType());
}
}
When we compile the two classes above and then run them, we will see the following output:
hello
System.String
5
System.Int32
Even though we used the same class to actually hold our string and int,
it keeps their original type intact. That is, we are not going to and
from a System.Object type just to hold them.
Generic Collections
The .NET team has provided us with a number of generic collections
to work with in the the latest version of the .NET Framework. List,
Stack, and Queue are all going to be implemented for us. Let's see an
example on how to use the provided Generic List class.
For this example we want to be able to hold a collection of a Type that
we create, which we used to represent a User in our system. Here is the
definition for that class:
namespace Rob {
public class User {
protected string name;
protected int age;
public string Name{get{return name;}set{name=value;}}
public int Age{get{return age;}set{age=value;}}
}
}
Now that we have our User defined, let's create an instance of the .NET Framework Generic version of a simple List:
System.Collections.Generic.List<Rob.User> users
= new System.Collections.Generic.List<Rob.User>();
Notice the new "Generic" namespace within the System.Collections
namespace. This is where we will find all the new classes for our use.
The "List" class within that namespace is synonymous with the typical
System.Collections.ArrayList class, which I'm sure most of us are very
familiar with by now. Please note that although they are similar there
are several important differences.
So now that we have a Type-safe list of our User class, let's see how we
can use this class. What we will do is simply loop 5 times and create a
new User and add that User to our users collection above:
for(int x=0;x<5;x++) {
Rob.User user = new Rob.User();
user.Name="Rob" + x;
user.Age=x;
users.Add(user);
}
This should seem straight forward to you by now. In the next step we
will iterate over that same collection and output each item to the
console:
foreach(Rob.User user in users) {
System.Console.WriteLine(
System.String.Format("{0}:{1}", user.Name, user.Age)
);
}
This is slightly different that what we are used to. What you should
notice right off the bat is that we do not have to worry about the Type
being "Rob.User". This is because our generic collection is working in a
Type-safe manner; it will always be what we expect.
Another way to output the same list would be to use a simple for() loop
instead, and in this case we do not have to cast the object out of the
collection in order to use it properly:
for(int x=0;x<users.Count;x++) {
System.Console.WriteLine(
System.String.Format("{0}:{1}", users[x].Name, users[x].Age)
);
}
No more silly casting or boxing involved. Here is the complete listing
of the source plus the output of the result of executing the console
application:
User.cs
namespace Rob {
public class User {
protected string name;
protected int age;
public string Name{get{return name;}set{name=value;}}
public int Age{get{return age;}set{age=value;}}
}
}
Main.cs
public class M { public static void Main(string[] args) { System.Collections.Generic.List<Rob.User> users = new System.Collections.Generic.List<Rob.User>(); for(int x=0;x<5;x++) { Rob.User user = new Rob.User(); user.Name="Rob" + x; user.Age=x; users.Add(user); } foreach(Rob.User user in users) { System.Console.WriteLine(System.String.Format("{0}:{1}", user.Name, user.Age)); } System.Console.WriteLine("press enter"); System.Console.ReadLine(); for(int x=0;x<users.Count;x++) { System.Console.WriteLine(System.String.Format("{0}:{1}", users[x].Name, users[x].Age)); } } } OutputRob0:0 Rob1:1 Rob2:2 Rob3:3 Rob4:4 press enter Rob0:0 Rob1:1 Rob2:2 Rob3:3 Rob4:4 More on Generics more Generics features in the latest release of the .NET Framework include Generic Methods and Constraints. A generic method is very straight forward. Consider this example:public static T[] CreateArray<T>(int size) { return new T[size]; } This static method simply creates a method of the given Type "T" and of the given "size" and returns it to the caller. An example of calling this method would be:string[] myString = CreateArray<string>(5); This will new up an instance of our string array, with an initial size of 5. You should take time to investigate the new version of the framework. You will be surprised at all the little helpful features like this. Lastly, we should take a quick look at constraints. A constraint is setup to limit the Types which the Generic can accept. Let's say for example we had a generic type:public class Dictionary<K, V> where K : IComparable {} Notice the "where" clause on this class definition. It is forcing the K Type to be of Type IComparable. If K does NOT implement IComparable, you will get a Compiler error. Another type of constraint is a constructor constraint:public class Something<V> where V: new() {} In this example V must have at least the default constructor available, if not the Compiler will throw an error.
No comments:
Post a Comment