Lesson 1 - Introduction to collections and genericity

C# .NET Collections and LINQ Introduction to collections and genericity

In today's lesson of our C# .NET course, we'll introduce collections and go over a bit of background information about them. Then, we'll discuss different established collections in more detail in the following lessons.

Collection

The term "collection" refers to a set of items which are mostly of the same data type, and are used for a specific purpose. Throughout the courses, we've already encountered two types of collections, i.e. arrays and lists. There are a lot of collections. Although they often look similar from the outside, they work in very different ways, internally, and we choose them according to our intents and purposes. The .NET framework provides a large amount of pre-made collections. We'll gradually introduce you to them and work with them as needed.

Generic and non-generic collections

If we thought about making our own collection, we'd run into issues very soon. One of which would be choosing the data type for the collection items. For example, if we wanted to program our own List, we would create a MyList.cs class and add the appropriate methods to it. Since we want to make our collection universal and to be able to store any sort of items, e.g. both ints or users, we would run into issues with the data types of the elements within the collection. There are two ways to solve this problem and the .NET framework itself contains collections using both of these approaches to make its collections universal.

Non-generic collections

Since we know that all data types are descendants of the Object class, we can use this data type to store elements in our collection. We would then be able to put anything into our collection. The downside to this approach is that the collection won't know the actual type for each of the items. Therefore, it would only be able to return these elements simply as objects. Meaning that we would have to cast anything returned by the collection.

Here is an example of a non-generic collection, the ArrayList:

ArrayList list = new ArrayList();
list.Add("item");

string item = (string)list[0];
Console.WriteLine(item);

We create a list and add an item of the string type. In order to retrieve this item back from the list, we need to re-cast it back to a string.

To make the code above work, we had to add using System.Collections;. Since this namespace isn't in the default project configuration, it's safe to assume that non-generic collections won't be the most common approach.

Generic collections

Generic collections solve the data-type problem for the C# language. They introduce genericity. Simply speaking, it's the ability to specify the data type at the moment when an instance is created. In the collection class, we work with a generic type which serves as a placeholder for future data types. You may see it as a class that changes into others when an instance is created, for example, a string. It's sort of a class parametrization.

We already know the generic List and that the data type (parameter) of generic classes uses angle brackets. We only have the ability to specify the data type once, when we create a collection. This way, we no longer need to cast items:

List<string> list = new List<string>();
list.Add("item");

string item = list[0];
Console.WriteLine(item);

The program works the same as the one with the non-generic ArrayList, but we can now read values without using casts.

Generic collections have replaced non-generic collections which are not used often nowadays. In this course, we'll focus mainly on generic collections and only mention their non-generic versions.

Genericity

Genericity is, of course, a feature of the C# language, so we have the privilege to use them in our classes.

At this point, we won't bother with creating our own collection. Instead, let's create a simple class that will manage a single variable. The variable will be generic, so we'll be able to store any data type in it. Create a new console application project and name it Genericity. Add a new class and name it MyClass. Let's add the generic parameter in its declaration and name it T:

public class MyClass<T>
{
}

We are able to enter multiple generic parameters into the angle brackets, separated by commas. This may be useful at times, we'll go further into things like this once we get to generic dictionaries.

Let's move to the Main() method and create an instance of our class:

MyClass<int> instance = new MyClass<int>();

Don't forget to provide the angle brackets in both the data type and the constructor. We've specified the int data type in the T parameter for this class instance. We could also make another instance of the same class and give it a totally different data type, e.g. String. For our intents and purposes, a single class is enough for storing multiple data types.

Let's continue and create a class field. We are able to use T as an ordinary data type. Let's add a constructor the class, which will initialize the variable:

public class MyClass<T>
{
        private T variable;

        public MyClass(T variable)
        {
                this.variable = variable;
        }
}

Make sure to update the instance creation in the Main() method:

MyClass<int> instance = new MyClass<int>(10);

The instance contains the "variable" field now, which is of the int type and contains a value of 10. The example doesn't print anything, it's just there for you to see that it can be compiled.

We could even add a method with an extra generic parameter (other than the one that the class currently has):

public bool Compare<T2>(T2 a)
{
        return variable.Equals(a);
}

Let's compare our int with another data type:

MyClass<int> instance = new MyClass<int>(10);
Console.WriteLine(instance.Compare<string>("15"));

Other constructions

For completeness' sake, we'll introduce you to a few more constructs.

A generic class parameter can be specified in more detail, furthermore, it can be limited using the where keyword. We can ensure that the data type implements the IComparable interface:

public class MyClass<T> where T: IComparable
{
        ...
}

This allows us to call the interface's methods on the variables of the T type from within the class. The interface itself can also contain a generic parameter, so we could use generic types in its methods' headers.

If we were to write new() after the "where" keyword, we would be instantiating the T type from within the object. This is a sort of small-scale factory for instances of any type, which might look something like this:

public class Factory<T> where T : new()
{
    public T CreateInstance()
    {
        return new T();
    }
}

If we already have an interface in the "where", we simply add a new() between them and separate them with a comma.

To top it all off, let's go over how to limit the parameter type in terms of inheritance.

public class MyClass<A, B, C> where A : B where B : C
{

}

In the example above, we declare a class with three generic parameters where A is a descendant of B and B is a descendant of C.

In the next lesson, Lists with arrays in C# .NET, we'll look at Lists, and introduce the different implementations of this collection as well as their advantages and disadvantages.


 

 

Article has been written for you by David Capka
Avatar
Do you like this article?
1 votes
The author is a programmer, who likes web technologies and being the lead/chief article writer at ICT.social. He shares his knowledge with the community and is always looking to improve. He believes that anyone can do what they set their mind to.
Unicorn College The author learned IT at the Unicorn College - a prestigious college providing education on IT and economics.
Thumbnail
All articles in this section
Collections and LINQ in C# .NET
Activities (6)

 

 

Comments

To maintain the quality of discussion, we only allow registered members to comment. Sign in. If you're new, Sign up, it's free.

No one has commented yet - be the first!