Page 1 of 1

C# Generics Primer

#1 Curtis Rutland  Icon User is online

  • (╯□)╯︵ (~ .o.)~
  • member icon


Reputation: 4489
  • View blog
  • Posts: 7,818
  • Joined: 08-June 10

Posted 22 February 2011 - 01:59 PM

*
POPULAR

Learning C# Series
Generics

Generics are a way to make your methods, classes, interfaces, and delegates work for multiple types while still being typesafe. Using what are known as "type parameters," you can allow your methods to work with a "generic" type that will represent a specific type that will be provided in the calling code.

Generics were added in C# 2.0 (.NET Framework 2.0, Visual Studio 2005). Nothing in this tutorial can be used below that version. Many objects were made obsolete by the inclusion of generics, and I'll try to touch on a few of them as I can.

Why is this important to learn about?

Many programmers may never create a generic method or class on their own, but they should still learn about how generics work, because they're heavily used in existing code. Specifically, they are very heavily used in Collections (Lists, Stacks, Queues, Dictionaries, etc...). Knowing when to use a preexisting class that implements generics will be very useful.

Also, if you need to write your own custom collections, or methods that deal with collections of many different types, generics are an incredibly valuable resource.

Definitions of terms used.

Generic Programming: is a style of computer programming in which algorithms are written in terms of to-be-specified-later types that are then instantiated when needed for specific types provided as parameters.

Type Safety: type safety is the extent to which a programming language discourages or prevents type errors. A type error is erroneous or undesirable program behaviour caused by a discrepancy between differing data types.

Note: All examples were created using Visual Studio 2010, targetting the .NET Framework 4.0. We'll do our best to point out anything that might not work in older versions.

Generics

Before we go about explaining how to create generic classes/methods/etc... let's look at an example of something that already uses a generic type argument. A very commonly used example is the List<T>. This would most closely resemble a resizable, generic array. When you create a new List, instead of T, you provide a Type that you want your list to be. If you want a List of Ints, you put <int>. This replaces the functionality of ArrayList, which was included before C# supported Generics.

//old way:
ArrayList arrayList = new ArrayList();
arrayList.Add("Hello World");
//this does not generate a compiler error:
arrayList.Add(1);

//new way:
List<string> listOfStrings = new List<string>();
listOfStrings.Add("Hello World");
//this generates a compiler error:
listOfStrings.Add(1);



Notice that you can add any object of any type to the ArrayList. This could occasionally be useful, but getting values from the array list can be a pain. They are returned as objects, and must be cast back to their original type. With a List<T>, there is no need to cast. The values you added come back the same type as they went in. Of course, this means you can't put an int in a List of Strings. This is what we refer to as Type Safety. Objects are type checked at compile time, and you can't use types that don't match, or aren't inherited from, the type argument. Here's an example of such type safety in action:

foreach (object obj in arrayList) {
    //this will cause an exception if we've added any non-strings
    string str = (string)obj;
    Console.WriteLine(str);
}

foreach (string str in listOfStrings) {
    //no need to cast, since we can take strings directly out of the list
    Console.WriteLine(str);
}



Type Parameters

A type parameter is passed to a generic method/class/interface/delegate and used as the generic type. It's contained in angle brackets (<Type>). This comes immediately after the name of the method/class/interface/delegate you are creating. For example, a basic generic method declaration may look like this:

public void DoSomething<T>(T parameter1){...


In this example (as well as most examples you'll find), we used T to represent the type parameter. It could be any identifier we want, but T is quite common. You'll see that we used it to specify that the provided parameter will be of the generic type specified.

You can have multiple type parameters if you need:

public void DoSomething<T, U>(T parameter1, U parameter2){...


In this example, we used two type parameters, and specified that the first method parameter be of type T, and the second be of type U. Those types must be provided when calling the method.

Generic Methods

First, we'll create some generic methods. For example's sake, let's create a method that takes a List of any type, and converts it to an array of the same type. (Note, this method already exists, but we'll recreate the wheel for example's sake). Such a method might look like this:

public static T[] ConvertListToArray<T>(List<T> list) {
    int count = list.Count;
    T[] array = new T[count];
    for (int i = 0; i < count; i++)
        array[i] = list[i];
    return array;
}


In this, we defined the type parameter as T. We specified that the method should return an array of T, and that it should take a List of T as a method parameter. Inside the method, on line three, we define a new array of type T. Note that the type T is only available to us because it's declared as a type parameter. If we used U instead of T in the definition, we'd have to change it to U everywhere else.

Also remember that T has no type of its own. It's representing a type that will be provided later. Because of this, we can define things as type T, and know that if we call this method with an argument of <string>, T becomes string. If we do it with <int>, T becomes int. It allows us to call the method like this:

List<int> list = new List<int>() { 1, 2, 3, 4, 5 };
int[] array = ConvertListToArray<int>(list);


Type Inference

Note that we could also have called the method like this:
List<int> list = new List<int>() { 1, 2, 3, 4, 5 };
int[] array = ConvertListToArray(list);


In that example, we didn't explicitly provide the type parameter. This is still valid, because the compiler can infer the correct type from the usage. In our method definition, we said the first parameter must be a List<T>. We provided a List<int>. Therefore, the compiler can infer that <T> must be <int>.

Here's a real-world example of a generic method I've used in an application:

public static string Serialize<T>(T data) {
    if (data == null)
        return null;
    using (var memoryStream = new MemoryStream()) {
        var serializer = new DataContractSerializer(typeof(T));
        serializer.WriteObject(memoryStream, data);

        memoryStream.Seek(0, SeekOrigin.Begin);

        var reader = new StreamReader(memoryStream);
        string content = reader.ReadToEnd();
        return content;
    }
}


It takes in an object, serializes it, and returns the resulting xml string.

Generic Classes

If you've taken a Data Structures class, you should be well familiar with what we're going to cover in this section. Generic classes are classes that can take and keep type arguments. These are similar in concept to Generic Methods, just expanded to an entire class.

The most obvious examples of this are collections. We've already visited List<T>, so we'll take a look at another collection that uses generics: Dictionary<TKey, TValue>. Follow the link for descriptions and useage examples.

This class is very similar to a Hashtable. Hashtables are like ArrayLists, in the sense that they take and return Objects. Dictionaries (like Lists), are the generic, typesafe answer to the non-generic, non-typesafe Hashtable (like ArrayList).

So let's create an example generic class. A LinkedLists's node might look something like this:

public class MyNode<T> {
    public T Data { get; set; }
    public MyNode<T> Next { get; set; }

    public MyNode(T data) {
        Data = data;
        Next = null;
    }
}


In this, we provide a type argument T. This is basically the type of the data that the node will store. We use that type argument to create a parameter Data, and to create a child Node Next. Also, we use it as a parameter for the Constructor.

Generic Interfaces

In the same way that classes can be generic, interfaces also can be generic. Some perfect examples are IEnumerable<T> and IList<T>. For example's sake, here's an example of how we might make a custom List interface:

public interface ICustomList<T> {
    T this[int index] { get; set; }
    T ElementAt(int index);
    void Add(T value);
    void Remove(T value);
    void Clear();
}


We've provided the type parameter T, and used it in several places, as return types and as parameter types.

Generic Delegates

Delegates can also be generic. A great example of a generic delegate would be EventHandler<TEventArgs>. This is a convenient premade delegate type for us to use with events. Here's an example of a very common use case:

public event EventHandler<EventArgs> SomeEvent;
protected virtual void OnSomeEvent() {
    if (SomeEvent != null)
        SomeEvent(this, new EventArgs());
}



In this, we create an event. Events requre a delegate type. So instead of creating our own custom delegate, we just simplify things and use the delegate already included in the framework: EventHandler<TEventArgs>. This requires us to provide a type argument. Since this is a simple event with no custom event arguments, we use the basic EventArgs.

Default Values in Generics

Now, it may come to pass that you need to initialize a generic variable to a default value, or perhaps return a default value. But how do you do that? Default values for reference types are null, but you can't store null in an int, and you can't store 0 in a struct. So how do you handle this? Easy: the [il]default[il] keyword. It will return the proper default value for whatever type you provide to it. Quick example:

public static T GetDefault<T>() {
    return default(T);
}


Constraints on Type Parameters

Sometimes, you need a generic parameter, but you need to constrain the parameter in some way. Maybe you only want reference types passed, or you want the type to have a default parameter. To do this, you can constrain your generic parameters. The MSDN has a much better writeup on this than I could do; I just wanted to include this section so you know you can do it. Read more about this topic here:

http://msdn.microsof...y/d5x73970.aspx

In Conclusion

Generics are an important part of C#, and have been for years, yet I frequently see people using ArrayLists and Hashtables. It's important to understand all the tools available to you so you avoid needless effort, both yours and the computer's. Generics are just one of the tools in your belt; make sure to learn how to use it, and more importantly, when its use is appropriate.

See all the C# Learning Series tutorials here!

This post has been edited by Curtis Rutland: 14 April 2011 - 02:46 PM


Is This A Good Question/Topic? 11
  • +

Replies To: C# Generics Primer

#2 Sergio Tapia  Icon User is offline

  • D.I.C Lover
  • member icon

Reputation: 1253
  • View blog
  • Posts: 4,168
  • Joined: 27-January 10

Posted 22 February 2011 - 02:54 PM

*makes cup of coffee*

*reads*
Was This Post Helpful? 0
  • +
  • -

#3 Curtis Rutland  Icon User is online

  • (╯□)╯︵ (~ .o.)~
  • member icon


Reputation: 4489
  • View blog
  • Posts: 7,818
  • Joined: 08-June 10

Posted 22 February 2011 - 03:06 PM

All of the subtopic headers are MSDN links, so I suggest following them for more relevant examples. I found it quite difficult to come up with examples that didn't rely on external knowledge, like extension methods. Also ones that weren't completely trite (might have failed that).

If anyone wants a more specific example on a specific topic, comment and I'll try to put one together.
Was This Post Helpful? 1
  • +
  • -

#4 here.to.code  Icon User is offline

  • D.I.C Head

Reputation: 20
  • View blog
  • Posts: 55
  • Joined: 15-February 11

Posted 14 April 2011 - 01:49 PM

View PostCurtis Rutland, on 22 February 2011 - 01:59 PM, said:

ArrayList arratList = new ArrayList();


Should be arrayList :P
Was This Post Helpful? 0
  • +
  • -

#5 Curtis Rutland  Icon User is online

  • (╯□)╯︵ (~ .o.)~
  • member icon


Reputation: 4489
  • View blog
  • Posts: 7,818
  • Joined: 08-June 10

Posted 14 April 2011 - 02:52 PM

Taken care of.
Was This Post Helpful? 0
  • +
  • -

#6 dsimer  Icon User is offline

  • New D.I.C Head

Reputation: 0
  • View blog
  • Posts: 16
  • Joined: 04-August 14

Posted 04 August 2014 - 01:15 PM

This is another great post! I took some notes and have come away with a much better understanding.

One thing:
  • It might be worth mentioning (if you didn't already and I missed it) that generics are generally only usable with types that are known at compile time. I struggled for a while with trying to use a type variable:
    Type myType = Type.getType("customType");
    ConvertListToArray<myType>(list);
    
    

    ...and it just doesn't work. I'm sure that many people already know this, but I didn't, and having known so would have saved me a lot time.

Was This Post Helpful? 0
  • +
  • -

#7 AdamSpeight2008  Icon User is offline

  • MrCupOfT
  • member icon


Reputation: 2262
  • View blog
  • Posts: 9,466
  • Joined: 29-May 08

Posted 04 August 2014 - 02:43 PM

An identifier can not be used as the type argument for a generic parameter.
Was This Post Helpful? 1
  • +
  • -

#8 dsimer  Icon User is offline

  • New D.I.C Head

Reputation: 0
  • View blog
  • Posts: 16
  • Joined: 04-August 14

Posted 04 August 2014 - 03:22 PM

View PostAdamSpeight2008, on 04 August 2014 - 02:43 PM, said:

An identifier can not be used as the type argument for a generic parameter.

Yes - that. Thanks!
Was This Post Helpful? 0
  • +
  • -

Page 1 of 1