Collections and Generics: .net 2.0
Name Space: System.Collections.Generic
When 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));
}
}
}
Output
Rob0: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.
Ref:
When 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));
}
}
}
Output
Rob0: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.
Ref:
Comments
Post a Comment