web analytics

Working with C# Generics

Options
@2021-04-09 08:03:42

C# generics vs C++ templates

Generics are not a completely new construct; similar concepts exist with other languages. For example, C++ templates can be compared to generics. However, there's a big difference between C++ templates and .NET generics.

 Model

With C++ templates the source code of the template is required when a template is instantiated with a specific type. When a template is used in a C++ program, the effect is as if a sophisticated macro processor had been used, that is each use of a different type in a template results in a separate chunk of source code being created by the compiler.

Contrary to C++ templates, C# generics are not only a construct of the C# language; generics are defined with the Common Language Runtime(CLR). When a generic type is used in a C+ program, it maintains its generic-ness (genericity) after it has been compiled; the actual implementations using a specific type are created at runtime, that is to say that the substitution that the C++ compiler does at compile time is done at JIT time in the C# generic world. This makes it possible to instantiate generics with a specific type in Visual Basic even though the generic class was defined with C#.

 Error Checking

Error is best illustrated through an example. Consider a C++ template that has a method like this;

T Add(T t1, Tt2)
{
    return t1 + t2;
}

the C++ compiler will be able to parse this method successfully. As mentioned above, when this template is actually used, the Ts will be replaced with an actual type. If a type such as int is used, the compiler will be able to create the Add method correctly, as it knows how to add two ints. If a type such as Employee was used, the compiler would issue an error, as the compiler would know that there was no way to add two Employees.

The C# generics world is very different. Because the type that is used with the generic is not known at compile time, the compiler needs additional information about the type that will be used with a generic class. This is done through constraints, which allow the author to constrain the types that can be used with a generic type. For example:

Class List<T> where T:IComparable

means that whenever I use a T in my implementation, I can call the CompareTo() function on it.
Constraints could theoretically provide nearly the same level of flexibility that templates do, but that would require a very complex constraint syntax. For the Whidbey release, only certain operations can be specified through contraints, which limits the number of operations you can perform.

For example, there is no way to say that a generic type must have an add operator, so you can't write “a + b“ in a generic class.

Runtime Operations

Generics in C# have full run-time support. If you use reflection, you will find that you can reflect over generic types, and create them at runtime. There's no real analog of this in the C++ world.

@2021-04-09 08:30:02

How to initialize variables with the type parameter in a C# generic class?

When defining a generic classes and its methods, one issue that arises is how to assign a default value to a parameterized type T when you do not know the following in advance:

  • Whether T will be a reference type or a value type.
  • If T is a value type, whether it will be a numeric value or a struct.

Given a variable t of a parameterized type T, the statement t = null is only valid if T is a reference type and t = 0 will only work for numeric value types but not for structs. The solution is to use the default keyword, which will return null for reference types and zero for numeric value types. For structs, it will return each member of the struct initialized to zero or null depending on whether they are value or reference types. The following example from the Point<T> class shows how to use the default keyword:

// A generic Point class.
public class Point<T>
{
   // Generic state date.
   private T xPos = default(T);
   private T yPos = default(T);
 
   // Generic constructor.
   public Point(T xVal, T yVal)
   {
      xPos = xVal;
      yPos = yVal;
   }
 
   // Generic properties.
   public T X
   {
      get { return xPos; }
      set { xPos = value; }
   }
 
   public T Y
   {
      get { return yPos; }
      set { yPos = value; }
   }
 
   public override string ToString()
   {
      return string.Format("[{0}, {1}]", xPos, yPos);
   }
 
   // Reset fields to the default value of the
   // type parameter.
   public void ResetPoint()
   {
      xPos = default(T);
      yPos = default(T);
   }
}

The variables xPos and yPos are defined by using the type parameter T. When the program is written and compiled, the actual type that will be substituted for T might not be known - this issue is only resolved when the code is executed. By using default keyword, the value used to initialize the variable will be determined when the statement is executed; if T is a reference type default(T) returns null, if T is numeric, default(T) returns 0, and if T is a boolean, default(T) returns false. If T is a struct, the individual fields in the struct are initialized in the same way (reference fields are set to null, numeric fields are set to 0, and boolean fields are set to false.)

@2021-04-09 14:54:34

Generic methods

In some cases, a type parameter is not needed for an entire class, but only when calling a particular method. Often, this occurs when creating a method that takes a generic type as a parameter. For example, when using the geneic Stack, we might often find ourselves pushing multiple values in a row onto a stack, and decide to write a method to do so in a single call. If we are only using a single kind of Stack, say Stack<int>, writing such a method is easy:

static void PushMultiple(Stack<int> s, params int[] values{
   foreach (int v in values) {
       s.Push(v);
   }
}

We can use this method to push multiple int values onto a Stack<int>:

Stack<int> s = new Stack<int>();
PushMultiple(s, 1, 2, 3, 4);

However, the method above only works with one particular constructed type: Stack<int>. While we can easily write similar code for other constructed Stack types, we would like to write a single method that can work with any Stack, no matter what type argument was used.

We do this by writing a generic method. Like a generic class declaration, a generic method is written with type parameters enclosed in angle brackets. With a generic method, the type parameters are written immediately after the method name, and can be used within the parameter list, return type, and body of the method. A generic PushMultiple method would look like this:

static void PushMultiple<ItemType>(Stack<ItemType> s, params ItemType[] values)
{
   foreach (ItemType v in values) {
      s.Push(v);
   }
}

Using this generic method, we can now push multiple items onto a Stack of any kind. Furthermore, the compiler type checking will ensure that the pushed items have the correct type for the kind of Stack being used. When calling a generic method, we place type arguments to the method in angle brackets. The generic PushMultiple method can be called this way:

Stack<int> s = new Stack<int>();
PushMultiple<int>(s, 1, 2, 3, 4);

This generic PushMultiple method is much better than the previous version, since it works on any kind of Stack. However, it appears to be less convenient to call, since the desired ItemType shall be supplied as a type argument to the method. In many cases, however, the compiler can deduce the correct type argument from the other arguments passed to the method, using a process called type inferencing. In the example above, since the first regular argument is of type Stack<int>, and the subsequent arguments are of type int, the compiler can reason that the type parameter shall also be int. Thus, the generic PushMultiple method can be called without specifying the type parameter:

Stack<int> s = new Stack<int>();
PushMultiple(s, 1, 2, 3, 4);
@2021-04-13 19:43:35

Using Parameter constraints on generic methods

Use constraints to enable more specialized operations on type parameters in the generic methods. In the following code:

using System;

namespace GenericMethod
{
    public class Program
    {
        public static void Main()
        {

            MyClass obj = new MyClass();

            obj.MethodA(5);

            object i = 5;

            obj.MethodA(i);

        }
    }

    public class MyClass
    {
        public void MethodA<T>(T arg) where T : class
        {
            Console.WriteLine("MyClass.MethodA<T>(T arg) where T is a class");
        }

        public void MethodA(int arg)
        {
            Console.WriteLine("MyClass.MethodA(int arg)");
        }
    }
}

The generic method MethodA<T> can be used with type arguemnt that is reference type. The output of the above code will be:

MyClass.MethodA(int arg)

MyClass.MethodA(T arg) where T is a class

Comments

You must Sign In to comment on this topic.


© 2024 Digcode.com