English | 简体中文 | 繁體中文 | Русский язык | Français | Español | Português | Deutsch | 日本語 | 한국어 | Italiano | بالعربية
Generics refer to general forms rather than specific forms. In C#, generics mean that they are not specific to a particular data type.
C# allows you to define generic classes, interfaces, abstract classes, fields, methods, static methods, properties, events, delegates, and operators using type parameters without specifying a specific data type. Type parameters are placeholders for specific types that are specified when an instance of a generic type is created.
Generics are declared by specifying type parameters in angle brackets after the type name, for example, TypeName<T>, where T is the type parameter.
Generic classes are defined using type parameters in angle brackets after the class name. Below is a definition of a generic class.
class DataStore<T> { public T Data { get; set; } }
Above, DataStore is a generic class. T is called a type parameter and can be used as a field, property, method parameter, return type, and delegate type in the DataStore class. For example, Data is a generic property because we used the type parameter T as its type rather than a specific data type.
You can also define multiple type parameters and separate them with commas.
class KeyValuePair<TKey, TValue> { public TKey Key { get; set; } public TValue Value { get; set; } }
You can create an instance of a generic class by specifying the actual type in the angle brackets. Below, create an instance of the generic class DataStore.
DataStore<string> store = new DataStore<string>();
Above, we specify the type in the angle brackets when creating an instance. Therefore, T will be replaced with any type T used throughout the class at compile time. Therefore, the type of the Data property is string.
The following diagram illustrates how generics work.
You can assign a string value to the Data property. Attempting to assign a value other than a string will result in a compile-time error.
DataStore<string> store = new DataStore<string>(); store.Data = "Hello World!";//obj.Data = 123; //Compile-time error
You can specify different data types for different objects, as shown below.
DataStore<string> strStore = new DataStore<string>(); strStore.Data = "Hello World!"; //strStore.Data = 123; // Compile-time error DataStore<int> intStore = new DataStore<int>(); intStore.Data = 100; //intStore.Data = "Hello World!"; // Compile-time error KeyValuePair<int, string> kvp1 = new KeyValuePair<int, string>(); kvp1.Key = 100; kvp1.Value = "Hundred"; KeyValuePair<string, string> kvp2 = new KeyValuePair<string, string>(); kvp2.Key = "IT"; kvp2.Value = "Information Technology";
Generic classes increase reusability. The more types, the higher the reusability. However, excessive generalization can make the code difficult to understand and maintain.
Generic classes can be the base class for other generic or non-generic classes or abstract classes.
Generic classes can derive from other generic or non-generic interfaces, classes, or abstract classes.
Generic classes can contain generic fields. However, they cannot be initialized.
class DataStore<T> { public T data; }
Below is a declaration of a generic array.
class DataStore<T> { public T[] data = new T[10]; }
Methods that use type parameters to declare their return type or parameters are called generic methods.
class DataStore<T> { private T[] _data = new T[10]; public void AddOrUpdate(int index, T item) { if(index >= 0 && index < 10) _data[index] = item; } public T GetData(int index) { if(index >= 0 && index < 10) return _data[index]; else return default(T); } }
The AddOrUpdate() and GetData() methods mentioned above are generic methods. The actual data type of the item parameter will be specified when the DataStore<T> class is instantiated, as shown below.
DataStore<string> cities = new DataStore<string>(); cities.AddOrUpdate(0, "Mumbai"); cities.AddOrUpdate(1, "Chicago"); cities.AddOrUpdate(2, "London"); DataStore<int> empIds = new DataStore<int>(); empIds.AddOrUpdate(0, 50); empIds.AddOrUpdate(1, 65); empIds.AddOrUpdate(2, 89);
Generic parameter types can be used with multiple parameters that have or do not have non-generic parameters and return types. The following is a valid example of generic method overloading.
public void AddOrUpdate(int index, T data) { } public void AddOrUpdate(T data1, T data2) { } public void AddOrUpdate<U>(T data1, U data2) { } public void AddOrUpdate(T data) { }
By specifying the type parameter using the method name within the angle brackets, non-generic classes can contain generic methods, as shown below.
class Printer { public void Print<T>(T data) { Console.WriteLine(data); } } Printer printer = new Printer(); printer.Print<int>(100); printer.Print(200); // Infer according to the specified value printer.Print<string>("Hello"); printer.Print("World!"); // Infer according to the specified value
Generics improve code reusability. You do not need to write code to handle different data types.
Generics are type-safe. If you try to use a data type different from the one specified in the definition, a compile-time error will occur.
Generics have performance advantages because they eliminate the possibility of boxing and unboxing.