What Are Generics in C#

What are generics in C#?

Generics refers to the technique of writing the code for a class without specifying the data type(s) that the class works on.

You specify the data type when you declare an instance of a generic class. This allows a generic class to be specialized for many different data types while only having to write the class once.

A great example are the many collection classes in .NET. Each collection class has it's own implementation of how the collection is created and managed. But they use generics to allow their class to work with collections of any type.

http://msdn.microsoft.com/en-us/library/ms379564(VS.80).aspx

Understanding C# generics much better

A very simple example is the generic List<T> class. It can hold a number of objects of any type. For example, you can declare a list of strings (new List<string>()) or a list of Animals (new List<Animal>()), because it is generic.

What if you couldn't use generics? You could use the ArrayList class, but the downside is that it's containing type is an object. So when you'd iterate over the list, you'd have to cast every item to its correct type (either string or Animal) which is more code and has a performance penalty. Plus, since an ArrayList holds objects, it isn't type-safe. You could still add an Animal to an ArrayList of strings:

ArrayList arrayList = new ArrayList();
arrayList.Add(new Animal());
arrayList.Add("");

So when iterating an ArrayList you'd have to check the type to make sure the instance is of a specific type, which results in poor code:

foreach (object o in arrayList)
{
if(o is Animal)
((Animal)o).Speak();
}

With a generic List<string>, this is simply not possible:

List<string> stringList = new List<String>();
stringList.Add("Hello");
stringList.Add("Second String");
stringList.Add(new Animal()); // error! Animal cannot be cast to a string

What is cool about generics, why use them?

  • Allows you to write code/use library methods which are type-safe, i.e. a List<string> is guaranteed to be a list of strings.
  • As a result of generics being used the compiler can perform compile-time checks on code for type safety, i.e. are you trying to put an int into that list of strings? Using an ArrayList would cause that to be a less transparent runtime error.
  • Faster than using objects as it either avoids boxing/unboxing (where .net has to convert value types to reference types or vice-versa) or casting from objects to the required reference type.
  • Allows you to write code which is applicable to many types with the same underlying behaviour, i.e. a Dictionary<string, int> uses the same underlying code as a Dictionary<DateTime, double>; using generics, the framework team only had to write one piece of code to achieve both results with the aforementioned advantages too.

How C# generic works?

Your assertions are correct, C++ templates are completely constructed at compilation, .Net creates the types it needs at run-time. Although, generic typing needs to be resolved at compile time, the generated classes for the MSIL that get used and reused are generated at run-time (albeit slightly differently for Value Types and Reference Types)

Differences Between C++ Templates and C# Generics (C# Programming Guide)

C# Generics and C++ templates are both language features that provide
support for parameterized types. However, there are many differences
between the two. At the syntax level, C# generics are a simpler
approach to parameterized types without the complexity of C++
templates. In addition, C# does not attempt to provide all of the
functionality that C++ templates provide. At the implementation level,
the primary difference is that C# generic type substitutions are
performed at runtime and generic type information is thereby preserved
for instantiated objects
.

Generics in the Run Time (C# Programming Guide)

Value Types

When a generic type is first constructed with a value type as a
parameter, the runtime creates a specialized generic type with the
supplied parameter or parameters substituted in the appropriate
locations in the MSIL. Specialized generic types are created one time
for each unique value type that is used as a parameter.

Reference Types

Generics work somewhat differently for reference types. The first time
a generic type is constructed with any reference type
, the runtime
creates a specialized generic type with object references substituted
for the parameters in the MSIL
. Then, every time that a constructed
type is instantiated with a reference type as its parameter,
regardless of what type it is, the runtime reuses the previously
created specialized version of the generic type. This is possible
because all references are the same size.

What does T denote in C#

It is a Generic Type Parameter.

A generic type parameter allows you to specify an arbitrary type T to a method at compile-time, without specifying a concrete type in the method or class declaration.

For example:

public T[] Reverse<T>(T[] array)
{
var result = new T[array.Length];
int j=0;
for(int i=array.Length - 1; i>= 0; i--)
{
result[j] = array[i];
j++;
}
return result;
}

reverses the elements in an array. The key point here is that the array elements can be of any type, and the function will still work. You specify the type in the method call; type safety is still guaranteed.

So, to reverse an array of strings:

string[] array = new string[] { "1", "2", "3", "4", "5" };
var result = reverse(array);

Will produce a string array in result of { "5", "4", "3", "2", "1" }

This has the same effect as if you had called an ordinary (non-generic) method that looks like this:

public string[] Reverse(string[] array)
{
var result = new string[array.Length];
int j=0;
for(int i=array.Length - 1; i >= 0; i--)
{
result[j] = array[i];
j++;
}
return result;
}

The compiler sees that array contains strings, so it returns an array of strings. Type string is substituted for the T type parameter.


Generic type parameters can also be used to create generic classes. In the example you gave of a SampleCollection<T>, the T is a placeholder for an arbitrary type; it means that SampleCollection can represent a collection of objects, the type of which you specify when you create the collection.

So:

var collection = new SampleCollection<string>();

creates a collection that can hold strings. The Reverse method illustrated above, in a somewhat different form, can be used to reverse the collection's members.

Where are generic methods stored?

As opposed to C++ templates, .NET generics are evaluated in runtime, not at compile-time. Semantically, if you instantiate the generic class with different type parameters, those will behave as if it were two different classes, but under the hood, there is only one class in the compiled IL (intermediate language) code.

Generic types

The difference between different instantiatons of the same generic type becomes apparent when you use Reflection: typeof(YourClass<int>) will not be the same as typeof(YourClass<string>). These are called constructed generic types. There also exists a typeof(YourClass<>) which represents the generic type definition. Here are some further tips on dealing with generics via Reflection.

When you instantiate a constructed generic class, the runtime generates a specialized class on the fly. There are subtle differences between how it works with value and reference types.

  • The compiler will only generate a single generic type into the assembly.
  • The runtime creates a separate version of your generic class for each value type you use it with.
  • The runtime allocates a separate set of static fields for each type parameter of the generic class.
  • Because reference types have the same size, the runtime can reuse the specialized version it generated the first time you used it with a reference type.

Generic methods

For generic methods, the principles are the same.

  • The compiler only generates one generic method, which is the generic method definition.
  • In runtime, each different specialization of the method is treated as a different method of the same class.

How do C#/.Net generics know their parameter types?

Since you've edited your question to extend it beyond the C# compiler to the JIT compiler, here's an overview of the process, taking List<T> as our example.

As we've established, there is only one IL representation of the List<T> class. This representation has a type parameter corresponding to the T type parameter seen in C# code. As Holger Thiemann says in his comment, when you use the List<> class with a given type argument, the JIT compiler creates a native-code representation of the class for that type argument.

However, for reference types, it compiles the native code only once and reuses it for all other reference types. This is possible because, in the virtual execution system (VES, commonly called the "runtime"), there is only one reference type, called O in the spec (see paragraph I.12.1, table I.6, in the standard: http://www.ecma-international.org/publications/standards/Ecma-335.htm). This type is defined as a "native size object reference to managed memory."

In other words, all objects in the (virtual) evaluation stack of the VES are represented by an "object reference" (effectively a pointer), which, taken by itself, is essentially typeless. How then does the VES ensure that we don't use members of an incompatible type? What stops us from calling the string.Length property on an instance of System.Random?

To enforce type safety, the VES uses metadata that describes the static type of each object reference, comparing the type of a method call's receiver to the type identified by the method's metadata token (this applies to access of other member types as well).

For example, to call a method of the object's class, the reference to the object must be on the top of the virtual evaluation stack. The static type of this reference is known thanks to the method's metadata and analysis of the "stack transition" -- the changes in the state of the stack caused by each IL instruction. The call or callvirt instruction then indicates the method to be called by including a metadata token representing the method, which of course indicates the type on which the method is defined.

The VES "verifies" the code before compiling it, comparing the reference's type to that of the method. If the types are not compatible, verification fails, and the program crashes.

This works just as well for generic type parameters as it does for non-generic types. To achieve this, the VES limits the methods that can be called on an reference whose type is an unconstrained generic type parameter. The only allowed methods are those defined on System.Object, because all objects are instances of that type.

For a constrained parameter type, the references of that type can receive calls for methods defined by the types of the constraint. For example, if you write a method where you have constrained type T to be derived from ICollection, you can call the ICollection.Count getter on a reference of type T. The VES knows that it is safe to call this getter because it ensures that any reference being stored to that position in the stack will be an instance of some type that implements the ICollection interface. No matter what the actual type of the object is, the JIT compiler can therefore use the same native code.

Consider also fields that depend on the generic type parameter. In the case of List<T>, there is an array of type T[] that holds the elements in the list. Remember that the actual in-memory array will be an array of O object references. The native code to construct that array, or to read or write its elements, looks just the same regardless of whether the array is a member of a List<string> or of a List<FileInfo>.

So, within the scope of an unconstrained generic type such as List<T>, the T references are just as good as System.Object references. The advantage of generics, though, is that the VES substitutes the type argument for the type parameter in the caller's scope. In other words, even though List<string> and List<FileInfo> treat their elements the same internally, the callers see that the Find method of the one returns a string, while that of the other returns a FileInfo.

Finally, because all of this is achieved by metadata in the IL, and because the VES uses the metadata when it loads and JIT-compiles the types, the information can be extracted at run time through reflection.

What are Generic Collections in C#?

List<T> means List<WhateverTypeYouWantItToBeAListOf>. So for example:

If I have an Employee Class, and I wanted a collection of Employees, I could say:

List<Employee> employeeList = new List<Employee>();

I could then add Employee Objects to that list, and have it be Type-safe and extend to however many employee objects I put in it.

Like so:

Employee emp1 = new Employee();
Employee emp2 = new Employee();

employeeList.Add(emp1);
employeeList.Add(emp2);

employeeList now holds emp1 and emp2 as objects.

There are several facets to generic collections, the most important being they provide an object independent way of having a... well... collection of objects. They are type-safe; which means that any collection will consist of one type of object. You won't have a Animal instance inside of List<Employee> (unless, of course, Employee is a base class that Animal inherits from. At that point, however, you have bigger problems.

Programming with Generics is its own topic, worthy of (at least) one chapter in a book. At a very high level, programming with generics provides another way to reuse code -- independent of any class hierarchy or implementation details of a specific class.

More information here.

What does an in generic parameter do?

You could read about generic variance and contravariance introduced in .NET 4.0. The impact that the in keyword has on the interface is that it declares it as contravariant meaning that T can only be used as input method type. You cannot use it as return type on the methods of this interface. The benefit of this is that you will be able to do things like this (as shown in the aforementioned article):

interface IProcessor<in T>  
{
void Process(IEnumerable<T> ts);
}

List<Giraffe> giraffes = new List<Giraffe> { new Giraffe() };
List<Whale> whales = new List<Whale> { new Whale() };
IProcessor<IAnimal> animalProc = new Processor<IAnimal>();
IProcessor<Giraffe> giraffeProcessor = animalProc;
IProcessor<Whale> whaleProcessor = animalProc;
giraffeProcessor.Process(giraffes);
whaleProcessor.Process(whales);


Related Topics



Leave a reply



Submit