Zurück

Introduction to Generics in C#


Overview

Generics provide the solution to a limitation in earlier versions of the common language runtime and the C# language in which generalization is accomplished by casting types to and from the universal base type Object. By creating a generic class, you can create a collection that is type-safe at compile-time.

The limitations of using non-generic collection classes can be demonstrated by writing a short program that makes use of the ArrayList collection class from the .NET Framework base class library. ArrayList is a highly convenient collection class that can be used without modification to store any reference or value type.

using System;
using System.Collections;
using System.Collections.Generic;
using System.Text;

namespace Generics
{
    class GenericList
    {
        static void Main(string[] args)
        {
            // The .NET Framework 1.1 way to create a List
            ArrayList list = new ArrayList();

            // Add an integer to the List
            list.Add(3);

            // Add a string to the List. This will compile,
            // but may cause a Runtime Error!

            list.Add("This will trigger a Runtime Error!");

            // We cast to int, but this will causes an InvalidCastException
            // when encounter a string in the List.

            int t = (int)list[1];

            t = 0;
            foreach (int x in list)
            {
                t += x;
            }

            // The .NET Framework 2.0 way to create a List
            List<int> list1 = new List<int>();

            // No boxing, no casting
            list1.Add(3);
            list1.Add(1);

            // Compile-time error:
            list1.Add("This will trigger a Compile-time Error!");

            // Get the values from the List, no casting
            int t1 = list1[1];

            t1 = 0;
            foreach (int x in list1)
            {
                t1 += x;
            }
        }
    }
}

The .NET Framework 1.1 Way

Any reference or value type that is added to an ArrayList is implicitly upcast to Object. If the items are value types, they must be boxed when added to the list, and unboxed when they are retrieved. Both the casting and the boxing and unboxing operations degrade performance; the effect of boxing and unboxing can be quite significant in scenarios where you must iterate over large collections.

The .NET Framework 2.0 Way

For client code, the only added syntax with List<T> compared to ArrayList is the type argument in the declaration and instantiation. In return for this slightly greater coding complexity, you can create a list that is not only safer than ArrayList, but also significantly faster, especially when the list items are value types.

Creating a custom type-safe List

Of course, you can also create custom generic types and methods to provide your own generalized solutions and design patterns that are type-safe and efficient. The following code example shows a simple generic linked-list class for demonstration purposes. (In most cases, it is recommended that you use the List<T> class provided by the .NET Framework class library, rather than create your own.) The type parameter T is used in several places where a concrete type would normally be used to indicate the type of the item stored in the list. It is used in the following way.

using System;
using System.Collections.Generic;
using System.Text;

// --------------------------------------------------------------------
// The following code example shows a simple generic linked-list
// class for demonstration purposes. (In most cases, it is recommended
// that you use the List<T> class provided by the .NET Framework class
// library, rather than create your own.) The type parameter T is
// used in several places where a concrete type would normally be
// used to indicate the type of the item stored in the list.
// --------------------------------------------------------------------


namespace Generics
{
    // Type parameter T in angle brackets <>
    public class CustomList<T>
    {
        // Fields
        private Node head;

        // The nested class is also generic on T
        private class Node
        {
           
// Fields
            private Node next;

          
 // T as private member data type
            private
T data;

           
// T used in non-generic constructor
            public Node(
T pData)
            {
                next = null;
                data = pData;
            }

           
// Properties
            public Node Next
            {
                get
                {
                    return next;
                }
                set
                {
                    next = value;
                }
            }

           
// T as return type of the Property
            public
T Data
            {
                get
                {
                    return data;
                }
                set
                {
                    data = value;
                }
            }
        }


        // Constructor
        public CustomList()
        {
            head = null;
        }

        // T as method parameter type:
        public void Add(T pType)
        {
            Node n = new Node(pType);
            n.Next = head;
            head = n;
        }

        // Enables foreach on the List
        public IEnumerator<T> GetEnumerator()
        {
            Node current = head;
            while (current != null)
            {
                yield return current.Data;
                current = current.Next;
            }
        }

        // Provides indexing on the List
        public T this[int index]
        {
            get
            {
                int ctr = 0;
                Node current = head;
                while (current != null && ctr <= index)
                {
                    if (ctr == index)
                    {
                        return current.Data;
                    }
                    else
                    {
                        current = current.Next;
                    }
                    ++ctr;
                }
                return default(T);
            }
        }

        // Provides ToString() on the List
        public override string ToString()
        {
            if (this.head != null)
            {
                return this.head.ToString();
            }
            else
            {
                return string.Empty;
            }
        }
    }

    // Test the CustomList
    class TestCustomList
    {
        // My own Class of any Datatype
        private class ExampleClass
        {
            private object o;

            public ExampleClass(object obj)
            {
                o = obj;
            }

            public object objGet
            {
                get
                {
                    return o;
                }
            }
        }

        static void Main()
        {
            // Declare a List of type int, then loop through the List
            CustomList<int> list1 = new CustomList<int>();

            for (int x = 0; x < 10; x++)
            {
                list1.Add(x);
            }

            foreach (int i in list1)
            {
                System.Console.Write(i + " ");
            }
            System.Console.WriteLine("\nDone\n");

            // Declare a List of type string, then loop through the List
            CustomList<string> list2 = new CustomList<string>();
            list2.Add("Martin");
            list2.Add("Zahn");
            list2.Add("Oberdiessbach");

            foreach (string s in list2)
            {
                System.Console.Write(s + " ");
            }
            System.Console.WriteLine("\nDone\n");

            // Use Indexer on the List
            string s1 = list2[1];

            // Declare a List of type ExampleClass, then loop through the List
            // ExampleClass holds any DataType.

            CustomList<ExampleClass> list3 = new CustomList<ExampleClass>();
            ExampleClass a = new ExampleClass(7);
            list3.Add(a);

            ExampleClass b = new ExampleClass("Hello");
            list3.Add(b);

            foreach (ExampleClass o in list3)
            {
                System.Console.Write(o.objGet.ToString() + " ");
            }
            System.Console.WriteLine("\nDone");

            // Use Indexer on the List
            ExampleClass c = list3[0];
            System.Console.Write("From Indexer: " + c.objGet.ToString() + "\n");
        }
    }
}

Generics Conclusion

  • Use generic types to maximize code reuse, type safety, and performance.

  • The most common use of generics is to create collection classes.

  • The .NET Framework class library contains several new generic collection classes in the System.Collections.Generic namespace. These should be used whenever possible in place of classes such as ArrayList in the System.Collections namespace.

  • You can create your own generic interfaces, classes, methods, events and delegates.

  • Generic classes may be constrained to enable access to methods on particular data types.

  • Information on the types used in a generic data type may be obtained at run-time by means of reflection.