© Vaskaran Sarcar 2020
V. SarcarGetting Started with Advanced C#https://doi.org/10.1007/978-1-4842-5934-4_4

4. Generic Programming

Vaskaran Sarcar1 
(1)
Kolkata, West Bengal, India
 

In this chapter, you learn about generic programming and you are introduced to generics, one of the coolest features of C#. It is an integral part of advanced programming. Generic programming simply means the efficient use of generics. It first appeared in C# 2.0. Over time, additional flexibilities were added to this powerful feature, and nowadays, you find rare real-life applications that do not use generics at their core.

The Motivation Behind Generics

When you use a generic type in your application, you do not commit to a specific type for your instances. For example, when you instantiate a generic class, you can say that you want your object to deal with int types, but at another time you can say that you want your object to deal with double types, string types, object types, or so forth. In short, this kind of programming allows you to make a type-safe class without having to commit to any particular type.

This is not a new concept, and it is definitely not limited to C#. You see similar kinds of programming in other languages as well, for example, Java and C++ (using templates). The following are some of the advantages of using a generic application.
  • Your program is reusable.

  • Your program is enriched with better type-safety.

  • Your program can avoid typical runtime errors that may arise due to improper casting.

To address these points, I’ll start with a simple nongeneric program and analyze the potential drawbacks. After that, I’ll show you a corresponding a generic program and give a comparative analysis to discover the advantages of generic programming. Let’s start.

Demonstration 1

Demonstration 1 has a class called NonGenericEx . This class has two instance methods: DisplayMyInteger and DisplayMyString .
public int DisplayMyInteger(int myInt)
{
 return myInt;
}
public string DisplayMyString(string myStr)
{
 return myStr;
}
Did you notice that both methods are basically doing the same operation, but one method is dealing with an int and the other is dealing with a string? Not only is this approach ugly, it also suffers from another potential drawback, which you’ll see in the analysis section. But before we analyze it, let’s execute the program.
using System;
namespace NonGenericProgramDemo1
{
    class NonGenericEx
    {
        public int DisplayMyInteger(int myInt)
        {
            return myInt;
        }
        public string DisplayMyString(string myStr)
        {
            return myStr;
        }
    }
    class Program
    {
        static void Main(string[] args)
        {
            Console.WriteLine("***A non-generic program demonstration.***");
            NonGenericEx nonGenericOb = new NonGenericEx();
            Console.WriteLine("DisplayMyInteger returns :{0}", nonGenericOb.DisplayMyInteger(123));
            Console.WriteLine("DisplayMyString returns :{0}", nonGenericOb.DisplayMyString("DisplayMyString method inside NonGenericEx is called."));
            Console.ReadKey();
        }
    }
}

Output

This is the output.
***A non-generic program demonstration.***
DisplayMyInteger returns :123
DisplayMyString returns :DisplayMyString method inside NonGenericEx is called.

Analysis

Let’s suppose that now you need to deal with another datatype—a double. Using the current code, add the following line inside Main.
Console.WriteLine("ShowDouble returns :{0}", nonGenericOb.DisplayMyDouble(25.5));//error
You get the following compile-time error.
Error  CS1061  'NonGenericEx' does not contain a definition for 'DisplayMyDouble' and no accessible extension method 'DisplayMyDouble' accepting a first argument of type 'NonGenericEx' could be found (are you missing a using directive or an assembly reference?)
This is because you do not have a DisplayMyDouble method yet. At the same time, you cannot use any existing methods to deal with a double datatype. An obvious approach is to introduce a method that looks like the following.
public double DisplayMyDouble(double myDouble)
{
 return myDouble;
}

But how long can you tolerate this? If your code size kept growing in the same manner for all the other datatypes, your code would not be reusable for different datatypes. And at the same time, as the code grew, it would look ugly and the overall maintenance would become hectic. Fortunately, you have a simple solution when you prefer generic programming over its counterpart nongeneric programming.

First, the following are the key points that you should remember.
  • Generic classes and methods promote reusability, type-safety, and efficiency. Their nongeneric counterparts don’t have these qualities. You often see the use of generics with collections and the methods that work on them.

  • The .NET Framework class library includes a System.Collections.Generic namespace that has several generic-based collection classes. This namespace was added in version 2.0. This is why Microsoft recommends that any application that targets .NET Framework 2.0 (or later) should use generic collection classes instead of their nongeneric counterparts, such as ArrayList.

  • Angle brackets <> are used in generic programs. A generic type is placed in angle brackets; for example, <T> in your class definition. T is the most common single letter to indicate a generic type when you deal with a single generic type only.

  • You can define a class with placeholders for the type of its methods, fields, parameters, and so forth, in a generic program; later, these placeholders are replaced with the particular type that you want to use.

  • Here is the simple generic class used in demonstration 2:
    class GenericClassDemo<T>
        {
            public T Display(T value)
            {
                return value;
            }
        }

    T is called a generic type parameter.

    The following is an example of instantiation from a generic class:
    GenericClassDemo<int> myGenericClassIntOb = new GenericClassDemo<int>();

    Note that the type parameter is replaced with int in this case.

  • You may notice multiple generic type parameters in a particular declaration. For example, the following class has multiple generic types:

    public class MyDictionary<K,V>{//Some code}
  • A generic method might use its type parameter as its return type. It can also use the type parameter as a type of a formal parameter. Inside GenericClassDemo<T> class, the Display method uses T as a return type. This method also uses T as the type of its formal parameter.

  • You can place constraints on a generic type. This is explored later in this chapter.

Now go through demonstration 2.

Demonstration 2

Demonstration 2 is a simple generic program. Before you instantiate a generic class, you need to specify the actual types to substitute with the type parameters. In this demonstration, the following lines of code are inside Main.
GenericClassDemo<int> myGenericClassIntOb = new GenericClassDemo<int>();
GenericClassDemo<string> myGenericClassStringOb = new GenericClassDemo<string>();
GenericClassDemo<double> myGenericClassDoubleOb = new GenericClassDemo<double>();

These three lines of code tell you that the first line substitutes the type parameter with an int; the second line substitutes the type parameter with a string; and the third line substitutes the type parameter with a double.

When you do this kind of coding, the type substitutes the type parameter everywhere it appears. As a result, you get a type-safe class that is constructed based on your chosen type. When you choose an int type and use the following line of code,
GenericClassDemo<int> myGenericClassIntOb = new GenericClassDemo<int>();
you can use the following line to get an int from the Display method .
Console.WriteLine("Display method returns :{0}", myGenericClassIntOb.Display(1));
This is the complete demonstration.
using System;
namespace GenericProgramDemo1
{
    class GenericClassDemo<T>
    {
        public T Display(T value)
        {
            return value;
        }
    }
    class Program
    {
        static void Main(string[] args)
        {
            Console.WriteLine("***Introduction to Generic Programming.***");
            GenericClassDemo<int> myGenericClassIntOb = new GenericClassDemo<int>();
            Console.WriteLine("Display method returns :{0}", myGenericClassIntOb.Display(1));
            GenericClassDemo<string> myGenericClassStringOb = new GenericClassDemo<string>();
            Console.WriteLine("Display method returns :{0}", myGenericClassStringOb.Display("A generic method is called."));
            GenericClassDemo<double> myGenericClassDoubleOb = new GenericClassDemo<double>();
            Console.WriteLine("Display method returns :{0}", myGenericClassDoubleOb.Display(12.345));
            Console.ReadKey();
        }
    }
}

Output

This is the output.
***Introduction to Generic Programming.***
Display method returns :1
Display method returns :A generic method is called.
Display method returns :12.345

Analysis

Let’s do a comparative analysis of demonstration 1 (a nongeneric program) and demonstration 2 (a generic program). Both programs are doing the same operations but there are some key distinctions between them, as follows.
  • In demonstration 1, you need to specify methods like DisplayInteger, DisplayString, DisplayDouble, and so forth to handle the datatypes. But in demonstration 2, only one generic Display method is sufficient enough to handle the different datatypes, and you can accomplish this task with fewer lines of code.

  • When the DisplayDouble method was absent inside Main in demonstration 1, we encountered a compile-time error when we wanted to deal with the double datatype. But in demonstration 2, there was no need to define any additional methods to handle a double datatype (or any other datatype). So, you can see that this generic version is much more flexible than the nongeneric version.

Now consider demonstration 3.

Demonstration 3

This demonstration shows a nongeneric program that uses the ArrayList class . The size of an ArrayList can grow dynamically. It has a method called Add, which can help you to add an object to at the end of the ArrayList. In the upcoming demonstration, I used the following lines.
myList.Add(1);
myList.Add(2);
// No compile time error
myList.Add("InvalidElement");
Since the method expects objects as arguments, these lines are compiled successfully. But you’ll face the problem if you fetch the data using the following code segment.
foreach (int myInt in myList)
{
 Console.WriteLine((int)myInt); //downcasting
}

The third element is not an int (it is a string), and as a result, you encounter a runtime error. A runtime error is worse than a compile-time error, because at this stage, you can hardly do anything fruitful.

This is the complete demonstration.
using System;
using System.Collections;
namespace NonGenericProgramDemo2
{
    class Program
    {
        static void Main(string[] args)
        {
            Console.WriteLine("***Use Generics to avoid runtime error***");
            ArrayList myList = new ArrayList();
            myList.Add(1);
            myList.Add(2);
            //No compile time error
            myList.Add("InvalidElement");
            foreach (int myInt in myList)
            {
            /*Will encounter run-time exception for the final element  which is not an int */
                Console.WriteLine((int)myInt); //downcasting
            }
            Console.ReadKey();
           }
        }
}

Output

The program does not raise any compile-time errors, but at runtime, you see the exception shown in Figure 4-1.
../images/494901_1_En_4_Chapter/494901_1_En_4_Fig1_HTML.jpg
Figure 4-1

Runtime error InvalidCastException occurred

Now you understand that you encounter this runtime error because the third element (i.e., myList [2] in the ArrayList) was supposed to be an int, but I stored a string. At compile time, I did not encounter any issues because it was stored as an object.

Analysis

The prior demonstration also suffers from performance overhead due to boxing and downcasting.

A Quick Look into the List Class

Before you go further, let’s have a quick look at the built-in List class . This class is very common and widely used. It is made for generics, so when you instantiate a List class, you can mention the type that you want to put in your list. For example, in the following

List<int> myList = new List<int>(); contains a list of ints.
List<double> myList = new List<double>(); contains a list of doubles.
List<string> myList = new List<string>(); contains a list of strings

The List class has many built-in methods. I recommend that you go through them. These ready-made constructs make your programming life easier. For now, let’s use the Add method. Using this method, you can add items to the end of your list.

This is the method description from Visual IDE.
//
// Summary:
//   Adds an object to the end of the System.Collections.Generic.List`1.
//
// Parameters:
//   item:
//     The object to be added to the end of the //     System.Collections.Generic.List`1. The value can be null //     for reference types.
public void Add(T item);
The following segment of code creates a list of ints and then adds two items to it.
List<int> myList = new List<int>();
myList.Add(10);
myList.Add(20);

Now come to the important part. If you add a string to this list by mistake, you get a compile-time error.

This is the erroneous code segment.
//Compile time error: Cannot convert from 'string' to 'int'
//myList.Add("InvalidElement");//error

Demonstration 4

To compare with demonstration 3, in the following example, let’s use List<int> instead of ArrayList and then review the concepts that we’ve discussed so far.

This is the complete program.
using System;
using System.Collections.Generic;
namespace GenericProgramDemo2
{
    class Program
    {
        static void Main(string[] args)
        {
            Console.WriteLine("***Using Generics to avoid run-time error.***");
            List<int> myList = new List<int>();
            myList.Add(10);
            myList.Add(20);
            //Cannot convert from 'string' to 'int'
            myList.Add("InvalidElement");//Compile-time error
            foreach (int myInt in myList)
            {
                Console.WriteLine((int)myInt);//downcasting
            }
            Console.ReadKey();
        }
    }
}

Output

In this program, you get the following compile-time error
CS1503    Argument 1: cannot convert from 'string' to 'int'
for the following line of code.
myList.Add("InvalidElement");

You cannot add a string in myList because it was intended to hold integers only (note that I’m using List<int>). Since the error is caught at compile time, you do not need to wait until runtime to catch this defect.

Once you comment out the erroneous line, you can compile this program and generate the following output.
***Using Generics to avoid run-time error.***
1
2

Analysis

When you compare demonstration 3 with demonstration 4, you see that
  • To avoid runtime errors, you should prefer the generic version over its counterpart—the nongeneric version.

  • Generic programming helps you avoid penalties caused by boxing/unboxing.

  • To store strings, you can use something like List<string> myList2 = new List<string>(); to create a list that holds only the string types. Similarly, List<T> can be used for other datatypes. This shows that the List<T> version is more flexible and usable than the nongeneric version ArrayList.

Generic Delegates

In Chapter 1, you learned about user-defined delegates and their importance. Now, let’s discuss generic delegates. In this section, I cover three important built-in generic delegates—called Func, Action, and Predicate, which are very common in generic programming. Let’s start.

Func Delegate

There are 17 overloaded versions of the Func delegate . They can take 0 to 16 input parameters but always have one return type. For example,
Func<out TResult>
Func<in T, out TResult>
Func<in T1, in T2,out TResult>
Func<in T1, in T2, in T3, out TResult>
......
Func<in T1, in T2, in T3,in T4, in T5, in T6,in T7,in T8,in T9,in T10,in T11,in T12,in T13,in T14,in T15,in T16, out TResult>
To understand the usage, let’s consider the following method.
private static string DisplayEmployeeDetails(string name, int empId, double salary)
{
   return string.Format("Employee Name:{0},id:{1}, salary:{2}$", name, empId,salary);
}
To invoke this method using a custom delegate, you can follow these steps.
  1. 1.

    Define a delegate (say, Mydel); something like this:

    public delegate string Mydel(string n, int r, double d);
     
  2. 2.

    Create a delegate object and point the method using a code; something like the following:

    Mydel myDelOb = new Mydel(DisplayEmployeeDetails);
    Or in short,
    Mydel myDelOb = DisplayEmployeeDetails;
     
  3. 3.

    Invoke the method like this:

    myDelOb.Invoke("Amit", 1, 1025.75);
    Or, simply with this:
     myDelOb("Amit", 1, 1025.75);
     
If you use the built-in Func delegate , you can make your code simpler and shorter. In this case, you can use it as follows.
Func<string, int, double, string> empOb = new Func<string, int, double,string>(DisplayEmployeeDetails);
Console.WriteLine(empOb("Amit", 1,1025.75));
The Func delegate is perfectly considering all three input arguments (a string, an int, and a double, respectively) and returning a string. You may be confused and want to know which parameter denotes the return type. If you move your cursor on it in Visual Studio, you can see that the last parameter (TResult) is considered the return type of the function, and the others are considered input types (see Figure 4-2).
../images/494901_1_En_4_Chapter/494901_1_En_4_Fig2_HTML.jpg
Figure 4-2

Details of Func<in T1, in T2, in T3, outTResult> delegate

Note

The magic of in and out parameters will be revealed to you shortly.

Q&A Session

4.1 In the previous code segment, DisplayEmployeeDetails has three parameters, and its return type was string. Usually, I have different methods that can take a different number of input parameters. How can I use Func in those contexts?

Func delegates can consider 0 to 16 input parameters. You can use the overloaded version that suits your needs. For example, if you have a method that takes one string, and one int as input parameters, and whose return type is a string, and the method is something like the following.
private static string DisplayEmployeeDetailsShortForm(string name, int empId)
{
   return string.Format("Employee Name:{0},id:{1}", name, empId);
}
You can use following overloaded version of Func.
Func<string, int, string> empOb2 = new Func<string, int, string> (DisplayEmployeeDetailsShortForm);
Console.WriteLine(empOb2("Amit", 1));

Action Delegate

Visual studio describes the following about an Action delegate:

         Encapsulates a method that has no parameters and does not return a value.
    public delegate void Action();
But normally you’ll notice the generic version of this delegate which can take 1 to 16 input parameters but do not have a return type. The overloaded versions are as follows.
Action<in T>
Action<in T1,in T2>
Action<in T1,in T2, in T3>
....
Action<in T1, in T2, in T3,in T4, in T5, in T6,in T7,in T8,in T9,in T10,in T11,in T12,in T13,in T14,in T15,in T16>
Let’s suppose that you have a method called CalculateSumOfThreeInts that takes three ints as input parameters and whose return type is void, as follows.
private static void CalculateSumOfThreeInts(int i1, int i2, int i3)
{
    int sum = i1 + i2 + i3;
    Console.WriteLine("Sum of {0},{1} and {2} is: {3}", i1, i2, i3, sum);
}
You can use an Action delegate to get the sum of three integers, as follows.
Action<int, int, int> sum = new Action<int, int, int>(CalculateSumOfThreeInts);
sum(10,3,7);

Predicate Delegate

A Predicate delegate evaluates something. For example, let’s assume that you have a method that defines some criteria, and you need to check whether an object can meet the criteria or not. Let’s consider the following method.
private static bool GreaterThan100(int myInt)
{
    return myInt > 100 ? true : false;
}
You can see that this method evaluates whether an int is greater than 100 or not. So, you can use a Predicate delegate to perform the same test, as follows.
Predicate<int> isGreater = new Predicate<int>(IsGreaterThan100);
Console.WriteLine("101 is greater than 100? {0}", isGreater(101));
Console.WriteLine("99 is greater than 100? {0}", isGreater(99));

Demonstration 5

This is the complete program that demonstrates all the concepts discussed so far.
using System;
namespace GenericDelegatesDemo
{
    class Program
    {
        static void Main(string[] args)
        {
            Console.WriteLine("***Using Generic Delegates.***");
            // Func
            Console.WriteLine("Using Func delegate now.");
            Func<string, int, double,string> empOb = new Func<string, int, double,string>(DisplayEmployeeDetails);
            Console.WriteLine(empOb("Amit", 1,1025.75));
            Console.WriteLine(empOb("Sumit", 2,3024.55));
            // Action
            Console.WriteLine("Using Action delegate now.");
            Action<int, int, int> sum = new Action<int, int, int>(CalculateSumOfThreeInts);
            sum(10, 3, 7);
            sum(5, 10, 15);
            /*
            Error:Keyword 'void' cannot be used in this context
            //Func<int, int, int, void> sum2 = new Func<int, int, int, void>(CalculateSumOfThreeInts);
            */
            // Predicate
            Console.WriteLine("Using Predicate delegate now.");
            Predicate<int> isGreater = new Predicate<int>(IsGreaterThan100);
            Console.WriteLine("101 is greater than 100? {0}", isGreater(101));
            Console.WriteLine("99 is greater than 100? {0}", isGreater(99));
            Console.ReadKey();
        }
        private static string DisplayEmployeeDetails(string name, int empId, double salary)
        {
            return string.Format("Employee Name:{0},id:{1}, salary:{2}$", name, empId,salary);
        }
        private static void CalculateSumOfThreeInts(int i1, int i2, int i3)
        {
            int sum = i1 + i2 + i3;
            Console.WriteLine("Sum of {0},{1} and {2} is: {3}", i1, i2, i3, sum);
        }
        private static bool IsGreaterThan100(int input)
        {
            return input > 100 ? true : false;
        }
    }
}

Output

***Using Generic Delegates.***
Using Func delegate now.
Employee Name:Amit,id:1, salary:1025.75$
Employee Name:Sumit,id:2, salary:3024.55$
Using Action delegate now.
Sum of 10,3 and 7 is: 20
Sum of 5,10 and 15 is: 30
 Using Predicate delegate now.
101 is greater than 100? True
99 is greater than 100? False

Q&A Session

4.2 I’ve seen the use of built-in generic delegates. How can I use my own generic delegates?

I used the built-in generic delegates because they make your life easier. No one is restricting you from using your own generic delegate. I suggest you follow the construct of these generic delegates before you use your own, however. For example, in the previous demonstration, I used the Action delegate as follows.
Action<int, int, int> sum = new Action<int, int, int>(CalculateSumOfThreeInts);
sum(10, 3, 7);
Now, instead of using the built-in delegate, you can define your own generic delegate (say, CustomAction) as follows.
// Custom delegate
public delegate void CustomAction<in T1, in T2, in T3>(T1 arg1, T2 arg2, T3 arg3);
And then you could use it as follows.
CustomAction<int, int, int> sum2 = new CustomAction<int, int, int>(CalculateSumOfThreeInts);
sum2(10, 3, 7);

4.3 I’m seeing that when you created delegate instances, you didn’t use the short form. Is there any reason for that?

Good find. You can always use the short form. For example, instead of using
Action<int, int, int> sum = new Action<int, int, int>(CalculateSumOfThreeInts);

I could simply use

Action<int, int, int> sum = CalculateSumOfThreeInts;

But since you have only started to learn about delegates, these long forms can often help you to understand the code better.

4.4 Can I use Func delegate to point to a method that returns void?

When you have a method with a void return type, it is recommended that you use the Action delegate. If you use the following line of code in prior demonstration by mistake, you get a compile-time error because the target method return type is void.
//Error:Keyword 'void' cannot be used in this context
Func<int, int, int, void> sum2 = new Func<int, int, int, void>(CalculateSumOfThreeInts);//error

4.5 Can I have generic methods?

In demonstration 2, you saw a generic method, as follows.
public T Display(T value)
{
   return value;
}

It shows that you can opt for a generic method when you have a set of methods that are identical except for the types, it works on.

For example, in Demonstration2, you have seen that I used the same named method when I invoked: Display(1), Display("A generic method is called.") and Display(12.345).

The Default Keyword in Generics

It shows that you have seen the use of default keyword in switch statements, where default refers to a default case. In generic programming, it has a special meaning. You can use default to initialize generic types with the default values. In this context, you may note the following points.
  • The default value for a reference type is null.

  • The default value of a value type (other than struct and bool type) is 0.

  • For a bool type, the default value is false.

  • For a struct (which is a value type) type, the default value is an object of that struct with all fields set with their default values (i.e., the default value of a struct is value produced by setting all value types fields to their default values and all reference type fields to null.)

Demonstration 6

Consider the following example with the output.
using System;
namespace UsingdefaultKeywordinGenerics
{
    class MyClass
    {
        // Some other stuff as per need
    }
    struct MyStruct
    {
        // Some other stuff as per need
    }
    class Program
    {
        static void PrintDefault<T>()
        {
            T defaultValue = default(T);
            string printMe = String.Empty;
            printMe = (defaultValue == null) ? "null" : defaultValue.ToString();
            Console.WriteLine("Default value of {0} is {1}", typeof(T), printMe);
            // C# 6.0 onwards,you can use interpolated string
            //Console.WriteLine($"Default value of {typeof(T)} is {printMe}.");
        }
        static void Main(string[] args)
        {
            Console.WriteLine("***Using default keyword in Generic Programming.***");
            PrintDefault<int>();//0
            PrintDefault<double>();//0
            PrintDefault<bool>();//False
            PrintDefault<string>();//null
            PrintDefault<int?>();//null
            PrintDefault<System.Numerics.Complex>(); //(0,0)
            PrintDefault<System.Collections.Generic.List<int>>(); // null
            PrintDefault<System.Collections.Generic.List<string>>(); // null
            PrintDefault<MyClass>(); //null
            PrintDefault<MyStruct>();
            Console.ReadKey();
        }
    }
}

Output

This is the output.
***Using default keyword in Generic Programming.***
Default value of System.Int32 is 0
Default value of System.Double is 0
Default value of System.Boolean is False
Default value of System.String is null
Default value of System.Nullable`1[System.Int32] is null
Default value of System.Numerics.Complex is (0, 0)
Default value of System.Collections.Generic.List`1[System.Int32] is null
Default value of System.Collections.Generic.List`1[System.String] is null
Default value of UsingdefaultKeywordinGenerics.MyClass is null
Default value of UsingdefaultKeywordinGenerics.MyStruct is UsingdefaultKeywordinGenerics.MyStruct
Note

The last line of the output is printing the <namespace>.<Name of the structure>; basically you can’t set a default value for a structure. More specifically, the default value of a struct is the value returned by the default constructor of the struct. As said before, the default value of a struct is value produced by setting all value types fields to their default values and all reference type fields to null. The implicit parameterless constructor in each struct sets these default values. You cannot define an explicit parameterless constructor for your own use. It is also useful to know that the simple types in C# such as int, double, bool etc. are often called as struct types.

Q&A Session

4.6 How is the default keyword used in generic programming?

You have seen that the default keyword helps you find the default value of a type. In generic programming, sometimes you may want to provide a default value for a generic type. In the previous example, you saw that a default value differs according to a value type or a reference type. In that example, note the PrintDefault<T>() method carefully.

Instead of using the following line of code
T defaultValue = default(T);
if you use something like
T defaultValue = null;//will not work for value types
you get a compile-time error that says,
Error  CS0403  Cannot convert null to type parameter 'T' because it could be a non-nullable value type. Consider using 'default(T)' instead.
Or, if you use the following line of code
T defaultValue = 0;//will not work for reference types
you get compile-time error that says,
Error  CS0029  Cannot implicitly convert type 'int' to 'T'

Implementing Generic Interface

Just like generic classes, you can have generic interfaces . A generic interface can contain both generic and nongeneric methods. If you want to implement a generic interface method, you can follow the same approach that you use when you normally implement a nongeneric interface method. The following program demonstrates how to implement methods of a generic interface.

Demonstration 7

To cover both scenarios, in this example, the generic interface GenericInterface<T> has a generic method called GenericMethod(T param) and a nongeneric method called NonGenericMethod() . The first method has a generic return type, T, and the second one has a void return type.

The remaining parts are easy to understand, and I kept the comments for your reference.
using System;
namespace ImplementingGenericInterface
{
    interface GenericInterface<T>
    {
        //A generic method
        T GenericMethod(T param);
        //A non-generic method
        public void NonGenericMethod();
    }
    //Implementing the interface
    class ConcreteClass<T>:GenericInterface<T>
    {
        //Implementing interface method
        public T GenericMethod(T param)
        {
            return param;
        }
        public void NonGenericMethod()
        {
            Console.WriteLine("Implementing NonGenericMethod of GenericInterface<T>");
        }
    }
    class Program
    {
        static void Main(string[] args)
        {
            Console.WriteLine("***Implementing generic interfaces.*** ");
            //Using 'int' type
            GenericInterface<int> concreteInt = new ConcreteClass<int>();
            int myInt = concreteInt.GenericMethod(5);
            Console.WriteLine($"The value stored in myInt is : {myInt}");
            concreteInt.NonGenericMethod();
            //Using 'string' type now
            GenericInterface<string> concreteString = new ConcreteClass<string>();
            string myStr = concreteString.GenericMethod("Hello Reader");
            Console.WriteLine($"The value stored in myStr is : {myInt}");
            concreteString.NonGenericMethod();
            Console.ReadKey();
        }
    }
}

Output

This is the output.
***Implementing generic interfaces.***
The value stored in myInt is : 5
Implementing NonGenericMethod of GenericInterface<T>
The value stored in myStr is : 5
Implementing NonGenericMethod of GenericInterface<T>

Analysis

There are some interesting points to note in the previous example. Let’s check them.
  • If you have another concrete class that wants to implement GenericInterface<T>, and you write following code block, you get compile-time errors.
        class ConcreteClass2 : GenericInterface<T>//Error
        {
            public T GenericMethod(T param)
            {
                throw new NotImplementedException();
            }
            public void NonGenericMethod()
            {
                throw new NotImplementedException();
            }
        }

    This is because I did not pass type argument T to ConcreteClass2. You have three compile-time errors with the same “Error CS0246 The type or namespace name 'T' could not be found (are you missing a using directive or an assembly reference?).” message.

  • You get the same errors if you write the following segment of code:

class ConcreteClass2<U> : GenericInterface<T>//Error

The reason is obvious: T is not found.

When you implement the generic interface, the implementing class needs to work on the same T type parameter. This is why the following segment of code is valid.
class ConcreteClass<T> : GenericInterface<T>
{//remaining code}

Q&A Session

4.7 In the previous example, can my implementing class work on multiple type parameters?

Yes. Both of the following code segments are also valid.
class ConcreteClass2<U,T> : GenericInterface<T>//valid
{//remaining code}
class ConcreteClass2<T, U> : GenericInterface<T>//also valid
{remaining code}

The key thing to remember is that your implementing class needs to supply the argument(s) required by the interface (for example, in this case, an implementor class must include the T parameter, which is present in the GenericInterface<T> interface.

4.8 Suppose you’ve got the following two interfaces.
interface IFirstInterface1<T> { }
interface ISecondInterface2<T, U> { }
Can you predict whether the following segments of code will compile or not?
Segment 1:
class MyClass1<T> : IFirstInterface<T> { }
Segment 2:
class MyClass2<T> : ISecondInterface<T, U> { }
Segment 3:
class MyClass3<T> : ISecondInterface<T, string> { }
Segment 4:
class MyClass4<T> : ISecondInterface<string, U> { }
Segment 5:
class MyClass5<T> : ISecondInterface<string, int> { }
Segment 6:
class MyClass6 : ISecondInterface<string, int> { }

Only segment 2 and segment 4 will not compile. In segment 2, MyClass2 doesn’t include the U parameter. In segment 4, MyClass4 doesn’t include the T parameter.

In segment 1 and segment 3, MyClass1 and MyClass3 have the required parameter(s), respectively.

Segments 5 and 6 had no issues at all, because in these cases, the respective classes worked on interfaces whose constructions are closed.

Generic Constraints

You can place restrictions on generic type parameters . For example, you may opt that your generic type must be a reference type or a value type, or it should derive from any other base type and so forth. But why should you allow constraints in your code? The simple answer is that by using constraints, you can have lots of control on your code, and you allow a C# compiler to know in advance about the type you are going to use. As a result, a C# compiler can help you detect bugs during compile time.

To specify a constraint, you use the where keyword and a colon (:) operator, such as in the following.
class EmployeeStoreHouse<T> where T : IEmployee
or,
class EmployeeStoreHouse<T> where T : IEmployee,new()

IEmployee is an interface.

In general, the following constraints are used.
  • where T: struct means that type T must be a value type. (Remember that a struct is a value type.)

  • where T: class means that type T must be a reference type. (Remember that a class is a reference type.)

  • where T: IMyInter means that type T must implement the IMyInter interface .

  • where T: new() means that type T must have a default (parameterless) constructor. (If you use it with other constraints, place it in the last position.)

  • where T: S means that type T must be derived from another generic type S. It is sometimes referred to as a naked type constraint .

Now let’s go through a demonstration.

Demonstration 8

In demonstration 8, the IEmployee interface contains an abstract Position method. I use this method to set the designation of an employee before I store the details of the employee in an employee store (think of it as a simple database of employees). The Employee class inherits from IEmployee, so it needs to implement this interface method. The Employee class has a public constructor that can take two arguments: the first one sets the employee name, and the second one indicates the years of experience. I’m setting a designation based on employee experience. (Yes, for simplicity, I’m considering only the years of experience to set a position.)

In this demonstration, you see the following line.
class EmployeeStoreHouse<T> where T : IEmployee

It is the constraint for your generic parameter that simply tells you that the generic type T must implement the IEmployee interface .

Lastly, I used range-based switch statements, which are supported in C# 7.0 onward. If you’re using a legacy version, you can replace the code segment with traditional switch statements.

This is the complete demonstration.
using System;
using System.Collections.Generic;
namespace UsingConstratintsinGenerics
{
    interface IEmployee
    {
        string Position();
    }
    class Employee : IEmployee
    {
        public string Name;
        public int YearOfExp;
        //public Employee() { }
        public Employee(string name, int yearOfExp)
        {
            this.Name = name;
            this.YearOfExp = yearOfExp;
        }
        public string Position()
        {
            string designation;
      //C#7.0 onwards range based switch statements are allowed.
            switch (YearOfExp)
            {
                case int n when (n <= 1):
                    designation = "Fresher";
                    break;
                case int n when (n >= 2 && n <= 5):
                    designation = "Intermediate";
                    break;
                default:
                    designation = "Expert";
                    break;
            }
            return designation;
        }
    }
    class EmployeeStoreHouse<T> where T : IEmployee
    {
        private List<Employee> EmpStore = new List<Employee>();
        public void AddToStore(Employee element)
        {
            EmpStore.Add(element);
        }
        public void DisplayStore()
        {
            Console.WriteLine("The store contains:");
            foreach (Employee e in EmpStore)
            {
                Console.WriteLine(e.Position());
            }
        }
    }
    class Program
    {
        static void Main(string[] args)
        {
            Console.WriteLine("***Using constraints in generic programming.*** ");
            //Employees
            Employee e1 = new Employee("Suresh", 1);
            Employee e2 = new Employee("Jack", 5);
            Employee e3 = new Employee("Jon", 7);
            Employee e4 = new Employee("Michael", 2);
            Employee e5 = new Employee("Amit", 3);
            //Employee StoreHouse
            EmployeeStoreHouse<Employee> myEmployeeStore = new EmployeeStoreHouse<Employee>();
            myEmployeeStore.AddToStore(e1);
            myEmployeeStore.AddToStore(e2);
            myEmployeeStore.AddToStore(e3);
            myEmployeeStore.AddToStore(e4);
            myEmployeeStore.AddToStore(e5);
            //Display the Employee Positions in Store
            myEmployeeStore.DisplayStore();
            Console.ReadKey();
        }
    }
}

Output

This is the output.
***Using constraints in generic programming.***
The store contains:
Fresher
Intermediate
Expert
Intermediate
Intermediate

Q&A Session

4.9 Why am I getting multiple compile-time errors in the following line?
class EmployeeStoreHouse<T> where T : new(),IEmployee
There are currently two issues. First, you haven’t placed the new() constraint as the last constraint. Second, the Employee class does not have a public parameterless constructor. Visual Studio gives you a clue about both errors; an error screenshot is shown in Figure 4-3.
../images/494901_1_En_4_Chapter/494901_1_En_4_Fig3_HTML.jpg
Figure 4-3

Compile-time error due to improper usage of new() constraint

The simple remedy is
  • Place a new() constraint in the last position

  • Define a public parameterless constructor in the Employee class, such as

public Employee() { }

4.10 Can I apply constraints on constructors?

When you use a new() constraint for your generic type, you actually place the constraints on the constructor. For example, in the following code, the type must have a parameterless constructor.
public class MyClass<T> where T:new()
In this context, it is important to note that you cannot use a “parameterful” constructor constraint. For example, if you use something like new(int), in the following code, you get several compile-time errors.
class EmployeeStoreHouse<T> where T : IEmployee,new(int) //Error
One error says,
Error CS0701 'int' is not a valid constraint. A type used as a constraint must be an interface, a nonsealed class or a type parameter.

4.11 Can I apply multiple interfaces as constraints on a single type?

Yes. For example, if you use the ready-made List class, you see the following.
public class List<[NullableAttribute(2)]T>
    : ICollection<T>, IEnumerable<T>, IEnumerable, IList<T>, IReadOnlyCollection<T>, IReadOnlyList<T>, ICollection, IList
    {//some other stuff}

You can see that ICollection<T>, IEnumerable<T>, and IList<T> are applied on List<T>.

Using Covariance and Contravariance

In the discussion on delegates in Chapter 1, you learned that covariance and contravariance support delegates first appeared in C# 2.0. Since C# 4.0, these concepts can be applied to generic type parameters, generic interfaces, and generic delegates. Chapter 1 also explored these concepts with nongeneric delegates. In this chapter, we continue to explore these concepts with additional cases.

Before going forward, recall the following points.
  • Covariance and contravariance deal with type conversion with arguments and return types.

  • In .NET 4 onward, you can use these concepts in generic delegates and generic interfaces. (In earlier versions, you got compile-time errors.)

  • Contravariance is generally defined as an adjustment or modification. When you try to implement these concepts in the coding world, you understand the following truths (or similar truths).
    • All soccer players are athletes, but the reverse is not true (because there are many athletes who play golf, basketball, hockey, etc.) Similarly, you can say that all buses or trains are vehicles, but the reverse is not necessarily true.

    • In programming terminology, all derived classes are of type-based classes, but the reverse is not true. For example, suppose that you have a class called Rectangle that is derived from a class called Shape. Then you can say that all Rectangles are Shapes, but the reverse is not true.

    • According to MSDN, covariance and contravariance deal with implicit reference conversion for arrays, delegates, and generic types. Covariance preserves assignment compatibility, and contravariance reverses it.

Starting with the .NET Framework 4, in C#, there are keywords to mark the generic type parameters of interfaces and delegates as covariant or contravariant. For covariant interfaces and delegates, you see the use of the out keyword (to indicate that values are coming out). Contravariant interfaces and delegates are associated with the in keyword (to indicate that values are going in).

Consider a built-in C# construct. Let’s check the definition of IEnumerable<T> in Visual Studio, as shown in Figure 4-4.
../images/494901_1_En_4_Chapter/494901_1_En_4_Fig4_HTML.jpg
Figure 4-4

Partial screenshot of IEnumerable<T> interface from Visual Studio 2019

You can see that out is associated with IEnumerable. It simply means that you can assign IEnumerable<DerivedType> to IEnumerable<BaseType>. This is why you can assign IEnumerable<string> to IEnumerable<object>. So, you can say that IEnumerable<T> is a covariant on T.

Now check the definition of the Action<T> delegate in Visual Studio, as shown in Figure 4-5.
../images/494901_1_En_4_Chapter/494901_1_En_4_Fig5_HTML.jpg
Figure 4-5

Partial screenshot of Action<T> delegate from Visual Studio 2019

Alternatively, you can check the definition of the IComparer<T> interface in Visual Studio, as shown in Figure 4-6.
../images/494901_1_En_4_Chapter/494901_1_En_4_Fig6_HTML.jpg
Figure 4-6

Partial screenshot of IComparer<T> interface from Visual Studio 2019

You can see that in is associated with the Action delegate and the IComparer interface. It simply means that you can assign Action<BaseType> to Action<DerivedType>. So, you can say that Action<T> is contravariant on T.

Similarly, since the type parameter is contravariant in the IComparer interface, you can use either the actual type you specified or any type that is more general (or less derived).

Q&A Session

4.12 In a Func delegate, I see the presence of both the in and out parameters. For example, in Func<in T, out TResult> or Func<in T1, in T2, out TResult>, what should I interpret from these definitions?

It simply tells you that Func delegates have covariant return types and contravariant parameter types.

4.13 What do you mean by “assignment compatibility”?

Here is an example where you can assign a more specific type (or a derived type) to a compatible less-specific type. For example, the value of an integer variable can be stored in an object variable, like this:
 int i = 25;
 object o = i;//Assignment Compatible

Covariance with Generic Delegate

Let’s examine covariance with a generic delegate . In the following demonstration, I’m declaring a generic delegate with covariant return type, as follows.
delegate TResult CovDelegate<out TResult>();
In this example, Vehicle is the parent class, and Bus is the derived class, so you see the hierarchy. (I did not put any additional methods/code in these classes because they are not required for this demonstration.)
class Vehicle
{
      //Some code if needed
}
class Bus : Vehicle
{
     //Some code if needed
 }
In addition, you see the presence of the following two static methods: GetOneVehicle() and GetOneBus() . The first one returns a Vehicle object and the second one returns a Bus object.
private static Vehicle GetOneVehicle()
{
    Console.WriteLine("Creating one vehicle and returning it.");
        return new Vehicle();
}
private static Bus GetOneBus()
{
    Console.WriteLine("Creating one bus and returning the bus.");
The following segment of code is straightforward and easy to understand because they match the delegate signature.
CovDelegate<Vehicle> covVehicle = GetOneVehicle;
covVehicle();
CovDelegate<Bus> covBus = GetOneBus;
covBus();
Now comes the interesting part. Note the following assignment.
covVehicle = covBus;
This assignment doesn’t raise any compilation errors because I’m using the delegate with a covariant return type. But it is important to note that if you do not make the delegate’s return type covariant by using the out parameter, this assignment causes the following compile-time error.
Error CS0029  Cannot implicitly convert type 'CovarianceWithGenericDelegates.CovDelegate<CovarianceWithGenericDelegates.Bus>' to 'CovarianceWithGenericDelegates.CovDelegate<CovarianceWithGenericDelegates.Vehicle>'

Demonstration 9

Go through the complete demonstration. Refer to the supporting comments to help you understand.
using System;
namespace CovarianceWithGenericDelegates
{
    //A generic delegate with covariant return type
    //(Notice the use of 'out' keyword)
    delegate TResult CovDelegate<out TResult>();
    //Here 'out' is not used(i.e. it is non-covariant)
    //delegate TResult CovDelegate<TResult>();
    class Vehicle
    {
        //Some code if needed
    }
    class Bus : Vehicle
    {
        //Some code if needed
    }
    class Program
    {
        static void Main(string[] args)
        {
            Console.WriteLine("***Testing covariance with a Generic Delegate.***");
            Console.WriteLine("Normal usage:");
            CovDelegate<Vehicle> covVehicle = GetOneVehicle;
            covVehicle();
            CovDelegate<Bus> covBus = GetOneBus;
            covBus();
            //Testing Covariance
            //covBus to covVehicle (i.e. more specific-> more general) is //allowed through covariance
            Console.WriteLine("Using covariance now.");
            //Following assignment is Ok, if you use 'out' in delegate //definition
Otherwise, you'll receive compile-time error
            covVehicle = covBus;//Still ok
            covVehicle();
            Console.WriteLine("End covariance testing. ");
            Console.ReadKey();
        }
        private static Vehicle GetOneVehicle()
        {
            Console.WriteLine("Creating one vehicle and returning it.");
            return new Vehicle();
        }
        private static Bus GetOneBus()
        {
            Console.WriteLine("Creating one bus and returning the bus.");
            return new Bus();
        }
    }
}

Output

This is the output.
***Testing covariance with a Generic Delegate.***
Normal usage:
Creating one vehicle and returning it.
Creating one bus and returning the bus.
Using covariance now.
Creating one bus and returning the bus.
End covariance testing.

Covariance with Generic Interfaces

Let’s examine covariance with a generic interface . In this example, I use another built-in construct in C# called IEnumerable<T>. This is an interface that provides the foundation of the most important features in C#. IEnumerable<T> can be used in a foreach loop if you want to do something meaningful on each item in a collection and treat them one by one. Nearly every class in the .NET Framework that contains multiple elements implements this interface. For example, the commonly used List class implements this interface.

Demonstration 10

Like the previous demonstration, Vehicle is the parent class and Bus is the derived class in this example, but this time, I placed an instance method called ShowMe() in each of them. You’ve seen that in IEnumerable<T> , T is covariant, so this time, I can apply the following assignments.
IEnumerable<Vehicle> vehicleEnumerable= busEnumerable;
busEnumerable is an IEnumerable<Bus> object and may look like the following.
IEnumerable<Bus> busEnumerable=new List<Bus>();

In many real-life applications, it’s a common practice to use methods that return IEnumerable<T>. This is useful when you do not want to disclose the actual concrete type to others and have the ability to loop through the items.

Now go through the complete demonstration, and refer to the supporting comments if you need to.
using System;
using System.Collections.Generic;
namespace CovarianceWithGenericInterface
{
    class Vehicle
    {
        public virtual void ShowMe()
        {
            Console.WriteLine("Vehicle.ShowMe().The hash code is : " + GetHashCode());
        }
    }
    class Bus : Vehicle
    {
        public override void ShowMe()
        {
            Console.WriteLine("Bus.ShowMe().Here the hash code is : " + GetHashCode());
        }
    }
    class Program
    {
        static void Main(string[] args)
        {
            //Covariance Example
            Console.WriteLine("***Using Covariance with Generic Interface.*** ");
            Console.WriteLine("**Remember that T in IEnumerable<T> is covariant");
            //Some Parent objects
            //Vehicle vehicle1 = new Vehicle();
            //Vehicle vehicle2 = new Vehicle();
            //Some Bus objects
            Bus bus1 = new Bus();
            Bus bus2 = new Bus();
            //Creating a child List
            //List<T> implements IEnumerable<T>
            List<Bus> busList = new List<Bus>();
            busList.Add(bus1);
            busList.Add(bus2);
            IEnumerable<Bus> busEnumerable = busList;
            /*
             An object which was instantiated with a more derived type argument (Bus) is assigned to an object instantiated with a less derived type argument(Vehicle).Assignment compatibility is preserved here.
            */
            IEnumerable<Vehicle> vehicleEnumerable = busEnumerable;
            foreach (Vehicle vehicle in vehicleEnumerable)
            {
                vehicle.ShowMe();
            }
            Console.ReadKey();
        }
    }
}

Output

This is the output.
***Using Covariance with Generic Interface.***
**Remember that T in IEnumerable<T> is covariant
Bus.ShowMe().Here the hash code is : 58225482
Bus.ShowMe().Here the hash code is : 54267293

Contravariance with Generic Delegates

Let’s examine contravariance with a generic delegate . In this demonstration, I’m declaring a generic contravariant delegate, as follows.
delegate void ContraDelegate<in T>(T t);
Again, Vehicle is the parent class, and Bus is the derived class, and each of them contains a method called ShowMe() . You see the following code segment.
class Vehicle
{
    public virtual void ShowMe()
    {
        Console.WriteLine(" Vehicle.ShowMe()");
    }
}
class Bus : Vehicle
{
    public override void ShowMe()
    {
        Console.WriteLine(" Bus.ShowMe()");
    }
}
In addition to these classes, you see the presence of the following two static methods: ShowVehicleType() and ShowBusType() . (The first one invokes the ShowMe() from a Vehicle object and second one invokes ShowMe() from a Bus object.)
private static void ShowVehicleType(Vehicle vehicle)
{
    vehicle.ShowMe();
}
private static void ShowBusType(Bus bus)
{
    bus.ShowMe();
}
The following segment of code is straightforward and easy to understand because they match the delegate signature. (The output is also shown in the comments.)
ContraDelegate<Vehicle> contraVehicle = ShowVehicleType;
contraVehicle(obVehicle); // Vehicle.ShowMe()
ContraDelegate<Bus> contraBus = ShowBusType;
contraBus(obBus); // Bus.ShowMe()
Now comes the interesting part, which is opposite to covariance. Note the following assignment.
contraBus = contraVehicle;
This assignment doesn’t raise any compilation errors because I’m using a contravariant delegate. But it is important to note that if you do not make the delegate contravariant by using the in parameter, this assignment causes the following compile-time error.
Error CS0029 Cannot implicitly convert type 'ContravarianceWithGenericDelegates.ContraDelegate<ContravarianceWithGenericDelegates.Vehicle>' to 'ContravarianceWithGenericDelegates.ContraDelegate<ContravarianceWithGenericDelegates.Bus>'

Demonstration 11

Now go through the complete demonstration, and refer to the supporting comments to help you understand.
using System;
namespace ContravarianceWithGenericDelegates
{
    // A generic contravariant delegate
    delegate void ContraDelegate<in T>(T t);
    // A generic non-contravariant delegate
    //delegate void ContraDelegate<T>(T t);
    class Vehicle
    {
        public virtual void ShowMe()
        {
            Console.WriteLine(" Vehicle.ShowMe()");
        }
    }
    class Bus : Vehicle
    {
        public override void ShowMe()
        {
            Console.WriteLine(" Bus.ShowMe()");
        }
    }
    class Program
    {
        static void Main(string[] args)
        {
            Console.WriteLine("*** Testing Contra-variance with Generic Delegates.***");
            Vehicle obVehicle = new Vehicle();
            Bus obBus = new Bus();
            Console.WriteLine("Normal usage:");
            ContraDelegate<Vehicle> contraVehicle = ShowVehicleType;
            contraVehicle(obVehicle);
            ContraDelegate<Bus> contraBus = ShowBusType;
            contraBus(obBus);
            Console.WriteLine("Using contravariance now.");
            /*
            Using general type to derived type.
            Following assignment is Ok, if you use 'in' in delegate definition.
            Otherwise, you'll receive compile-time error.
            */
            contraBus = contraVehicle;//ok
            contraBus(obBus);
            Console.ReadKey();
        }
        private static void ShowVehicleType(Vehicle vehicle)
        {
            vehicle.ShowMe();
        }
        private static void ShowBusType(Bus bus)
        {
            bus.ShowMe();
        }
    }
}

Output

This is the output.
*** Testing Contra-variance with Generic Delegates.***
Normal usage:
 Vehicle.ShowMe()
 Bus.ShowMe()
Using contravariance now.
 Bus.ShowMe()

Contravariance with Generic Interface

Now you understand covariance and contravariance. You’ve seen the uses of covariance and contravariance with generic delegates, and an implementation of covariance using a generic interface. I’m leaving the remaining case as homework, where you need to write a complete program and implement the concept of contravariance using a generic interface .

I’m providing partial code segments that can help you implement it. If you want, you can verify your implementation using the following code segments as a reference. You can also refer to the associated comments for a better understanding.

Partial Implementation

Here is a generic contravariant interface.
// Contravariant interface
interface IContraInterface<in T>{ }
// Following interface is neither covariant nor contravariant
//interface IContraInterface< T> { }
class Implementor<T>: IContraInterface<T> { }
Here is an inheritance hierarchy.
class Vehicle
{
   // Some code if needed
}
class Bus : Vehicle
{
    // Some code if needed
}
Here is the key assignment.
IContraInterface<Vehicle> vehicleOb = new Implementor<Vehicle>();
IContraInterface<Bus> busOb = new Implementor<Bus>();
// Contravarince allows the following
// but you'll receive a compile-time error
// if you do not make the interface contravariant using 'in'
busOb = vehicleOb;

Q&A Session

4.14 When I use covariance, it looks as if I’m using a simple polymorphism technique. For example, in the previous demonstration, you used the following line.
IEnumerable<Vehicle> vehicleEnumerable = busEnumerable;

Is this correct?

Yes.

4.15 Can I override a generic method?

Yes. You need to follow the same rules that you apply for nongeneric methods. Let’s look at demonstration 12.

Demonstration 12

In this demonstration, BaseClass<T> is the parent class. It has a method called MyMethod that accepts T as a parameter, and it’s return type is also T. DerivedClass<T> derives from this parent class and overrides this method.
using System;
namespace MethodOverridingDemo
{
    class BaseClass<T>
    {
        public virtual T MyMethod(T param)
        {
            Console.WriteLine("Inside BaseClass.BaseMethod()");
            return param;
        }
    }
    class DerivedClass<T>: BaseClass<T>
    {
        public override T MyMethod(T param)
        {
            Console.WriteLine("Here I'm inside of DerivedClass.DerivedMethod()");
            return param;
        }
    }
    class Program
    {
        static void Main(string[] args)
        {
            Console.WriteLine("***Overriding a virtual method.*** ");
            BaseClass<int> intBase = new BaseClass<int>();
            // Invoking Parent class method
            Console.WriteLine($"Parent class method returns {intBase.MyMethod(25)}");//25
            // Now pointing to the child class method and invoking it.
            intBase = new DerivedClass<int>();
            Console.WriteLine($"Derived class method returns {intBase.MyMethod(25)}");//25
            // The following will cause compile-time error
            //intBase = new DerivedClass<double>(); // error
            Console.ReadKey();
        }
    }
}

Output

This is the output.
***Overriding a virtual method.***
Inside BaseClass.BaseMethod()
Parent class method returns 25
Here I'm inside of DerivedClass.DerivedMethod()
Derived class method returns 25

Analysis

You can see that by following a simple polymorphism, I’m using the parent class reference (intBase) to point to the child class object. There was no issue for this kind of coding because both cases dealt with int types only. But the following lines of code with comments are easy to understand because using intBase, you cannot point to an object that is dealing with different types (double in this case).
// The following will cause compile-time error
//intBase = new DerivedClass<double>(); // error

To print output messages, I used a string interpolation technique. I used it only for a change, but in cases like this, you need to use C# 6.0 or above; otherwise, you can use the traditional approach.

Q&A Session

4.16 Can I overload a generic method?

Yes. In this case, also you need to follow the same rules that you apply for nongeneric methods, but you have to be careful with methods that accept type parameters. In such cases, the type difference is not considered on generic types; instead, it depends on the type argument that you substitute for a type parameter.

4.17 You said that the type difference is not considered on generic types; instead, it depends on the type argument that you substitute for the type parameter. Can you please elaborate?

I meant that sometimes it may appear that you have followed the rule of overloading perfectly, but there is something more to consider when you overload a generic method that accepts type parameters.

You know that for overloading, the number and/or type parameters are different. So, if you have following two methods in your class, you can say that it’s an example of overloading.
public void MyMethod2(int a, double b) { // some code };
public void MyMethod2(double b, int a) { // some code };
Now consider the following code segment, which involves generic type parameters.
class MyClass<T,U>
{
    public  void MyMethod(T param1, U param2)
    {
        Console.WriteLine("Inside MyMethod(T param1, U param2)");
    }
    public void MyMethod(U param1, T param2)
    {
        Console.WriteLine("Inside MyMethod(U param1, T param2)");
    }
}
It may appear that you have two overloaded versions of MyMethod, because the order of the generic type parameters differs. But there is potential ambiguity, which will be clear to you when you exercise the following code segments.
MyClass<int, double> object1 = new MyClass<int, double>();
object1.MyMethod(1, 2.3); // ok
MyClass<int, int> object2 = new MyClass<int, int>();
// Ambiguous call
object2.MyMethod(1, 2); // error
For this segment of code, you get the following compile-time error (for the line marked with // error).
CS0121 The call is ambiguous between the following methods or properties: 'MyClass<T, U>.MyMethod(T, U)' and 'MyClass<T, U>.MyMethod(U, T)'

Demonstration 13

This is the full demonstration.
using System;
namespace MethodOverloadingDemo
{
    class MyClass<T,U>
    {
        public  void MyMethod(T param1, U param2)
        {
            Console.WriteLine("Inside MyMethod(T param1, U param2)");
        }
        public void MyMethod(U param1, T param2)
        {
            Console.WriteLine("Inside MyMethod(U param1, T param2)");
        }
           public void MyMethod2(int a, double b)
        {
            Console.WriteLine("Inside MyMethod2(int a, double b).");
        }
        public void MyMethod2(double b, int a)
        {
            Console.WriteLine("MyMethod2(double b, int a) is called here.");
        }    }
    class Program
    {
        static void Main(string[] args)
        {
            Console.WriteLine("***Method overloading demo.*** ");
            MyClass<int, double> object1 = new MyClass<int, double>();
            object1.MyMethod(1, 2.3);//ok
            object1.MyMethod2(1, 2.3);//ok
            object1.MyMethod2(2.3, 1);//ok
            MyClass<int, int> object2 = new MyClass<int, int>();
            // Ambiguous call
            object2.MyMethod(1, 2); // error
            Console.ReadKey();
        }
    }
}

Output

Again, you get the following compile-time error.
CS0121 The call is ambiguous between the following methods or properties: 'MyClass<T, U>.MyMethod(T, U)' and 'MyClass<T, U>.MyMethod(U, T)'
You can comment out the ambiguous call, as follows, and then compile and run the program.
//object2.MyMethod(1, 2);//error
This time, you get the following output.
***Method overloading demo.***
Inside MyMethod(T param1, U param2)
Inside MyMethod2(int a, double b).
MyMethod2(double b, int a) is called here.

Self-Referencing Generic Types

Sometimes you may need to compare two instances of a class. In a case like this, you have two options.
  • Use the built-in constructs.

  • Write your own comparison method.

When you are interested in using built-in constructs, you have multiple options. For example, you can use either the CompareTo method of IComparable<T> or the Equals method of IEquitable<T> . You may note that a nongeneric IComparable is also available in C#.

Here is information about CompareTo from Visual Studio.
//
// Summary:
//     Compares the current instance with another object of the same //     type and returns an integer that indicates whether the current instance //     precedes, follows, or occurs in the same position in the sort //     order as the other object.
//
// Parameters:
//   other:
//     An object to compare with this instance.
//
// Returns:
//     A value that indicates the relative order of the objects being //     compared. The return value has these meanings: Value Meaning Less //     than zero This instance precedes other in the sort order. Zero //     This instance occurs in the same position in the sort order as other. //     Greater than zero This instance follows other in the sort order.
   int CompareTo([AllowNull] T other);
Here is information about Equals from Visual Studio.
//
// Summary:
//     Indicates whether the current object is equal to another object of//     the same type.
//
// Parameters:
//   other:
//     An object to compare with this object.
//
// Returns:
//     true if the current object is equal to the other parameter; //     otherwise, false.
    bool Equals([AllowNull] T other);

If your class implements any of these interfaces, you can use these methods and override them as you need. These interfaces are available in the System namespace, and they are implemented by built-in types like int, double, and string.

In many cases, however, you may want to write your own comparison method. I do this in demonstration 14.

A type can name itself as the concrete type when it closes the type argument.

Demonstration 14

In this demonstration, the Employee class implements IIdenticalEmployee<T> , which has an abstract method called CheckEqualityWith. Let’s suppose that in your Employee class, you have employee IDs and department names. Once I instantiate objects from the Employee class, my task is to compare these objects.

For comparison purposes, I simply check whether the deptName and employeeID are the same for two employees. If they match, the employees are the same. (Using the word same, I mean only the content of these objects, not the reference to the heap.)

This is the comparison method.
public string CheckEqualityWith(Employee obj)
{
    if (obj == null)
    {
        return "Cannot Compare with a Null Object";
    }
    else
    {
       if (this.deptName == obj.deptName && this.employeeID == obj.employeeID)
       {
           return "Same Employee.";
       }
       else
       {
           return "Different Employees.";
       }
   }
}
Now go through the complete implementation and output.
using System;
namespace SelfReferencingGenericTypeDemo
{
    interface IIdenticalEmployee<T>
    {
        string CheckEqualityWith(T obj);
    }
    class Employee : IIdenticalEmployee<Employee>
    {
           string deptName;
           int employeeID;
           public Employee(string deptName, int employeeId)
           {
               this.deptName = deptName;
               this.employeeID = employeeId;
           }
           public string CheckEqualityWith(Employee obj)
           {
               if (obj == null)
               {
                   return "Cannot Compare with a null Object";
               }
               else
               {
                   if (this.deptName == obj.deptName && this.employeeID == obj.employeeID)
                   {
                       return "Same Employee.";
                   }
                   else
                   {
                       return "Different Employees.";
                   }
               }
           }
   }
   class Program
   {
        static void Main(string[] args)
        {
            Console.WriteLine("**Self-referencing generic type demo.*** ");
            Console.WriteLine("***We are checking whether two employee objects are same or different.***");
            Console.WriteLine();
            Employee emp1 = new Employee("Chemistry", 1);
            Employee emp2 = new Employee("Maths", 2);
            Employee emp3 = new Employee("Comp. Sc.", 1);
            Employee emp4 = new Employee("Maths", 2);
            Employee emp5 = null;
            Console.WriteLine("Comparing emp1 and emp3 :{0}", emp1.CheckEqualityWith(emp3));
            Console.WriteLine("Comparing emp2 and emp4 :{0}", emp2.CheckEqualityWith(emp4));
            Console.WriteLine("Comparing emp2 and emp5 :{0}", emp2.CheckEqualityWith(emp5));
            Console.ReadKey();
        }
    }
}

Output

This is the output.
**Self-referencing generic type demo.***
***We are checking whether two employee objects are same or different.***
Comparing emp1 and emp3 :Different Employees.
Comparing emp2 and emp4 :Same Employee.
Comparing emp2 and emp5 :Cannot Compare with a null Object

Analysis

This example shows you that a type can name itself as a concrete type when it closes the type argument. It demonstrates how to use a self-referencing generic type. Again, by using the word same this example, I meant only the content of the objects, not the reference to the heap.

Q&A Session

4.18 Can you summarize the key usage of generics?

You can promote type-safety without creating lots of types that are very similar and particularly differ only by the types they use. As a result, you can avoid runtime errors and reduce costs due to boxing and unboxing.

4.19 How do static variables work in context of generic programming?

Static data is unique for each of the closed types. Consider the following program and output for your reference.

Demonstration 15

In this demonstration, let’s focus on the count variable and see how it increments when the MyGenericClass<T> generic class is instantiated with different types.
using System;
namespace TestingStaticData
{
    class MyGenericClass<T>
    {
        public static int count;
        public void IncrementMe()
        {
            Console.WriteLine($"Incremented value is : {++count}");
        }
    }
    class Program
    {
        static void Main(string[] args)
        {
            Console.WriteLine("***Testing static in the context of generic programming.***");
            MyGenericClass<int> intOb = new MyGenericClass<int>();
            Console.WriteLine(" Using intOb now.");
            intOb.IncrementMe();//1
            intOb.IncrementMe();//2
            intOb.IncrementMe();//3
            Console.WriteLine(" Using strOb now.");
            MyGenericClass<string> strOb = new MyGenericClass<string>();
            strOb.IncrementMe();//1
            strOb.IncrementMe();//2
            Console.WriteLine(" Using doubleOb now.");
            MyGenericClass<double> doubleOb = new MyGenericClass<double>();
            doubleOb.IncrementMe();//1
            doubleOb.IncrementMe();//2
            MyGenericClass<int> intOb2 = new MyGenericClass<int>();
            Console.WriteLine(" Using intOb2 now.");
            intOb2.IncrementMe();//4
            intOb2.IncrementMe();//5
            Console.ReadKey();
        }
    }
}

Output

This is the output.
***Testing static in the context of generic programming.***
Using intOb now.
Incremented value is : 1
Incremented value is : 2
Incremented value is : 3
Using strOb now.
Incremented value is : 1
Incremented value is : 2
Using doubleOb now.
Incremented value is : 1
Incremented value is : 2
Using intOb2 now.
Incremented value is : 4
Incremented value is : 5

Q&A Session

4.20 What are the important restrictions in using generics?

Here are some important restrictions to note.
  • Static data is unique for each of the closed types but not for different constructed types.

  • You cannot use an external modifier in a generic method. So, following segment of code
    using System;
    using System.Runtime.InteropServices;
    class GenericClassDemo2<T>
    {
        [DllImport("avifil32.dll")] // error in generic method
        private static extern void AVIFileInit();
    }
    raises the following compile-time error:
    Error CS7042  The DllImport attribute cannot be applied to a method that is generic or contained in a generic type.
  • You cannot use a pointer type as type arguments. So, the last line in following code segment
    class GenericClassDemo2<T>
    {
        static unsafe void ShowMe()
        {
            int a = 10; // ok
            int* p; // ok
            p = &a; // ok
            T* myVar; // error
        }
    }
    raises the following compile-time error:
    Error CS0208  Cannot take the address of, get the size of, or declare a pointer to a managed type ('T')
  • In Q&A Session question 4.9, you saw that if you have multiple constraints, the new() constraint must be placed at the end.

Final Words

I hope that this chapter demystified the key features of generic programming. At first, generic syntax may look little bit overwhelming, but practice and repeated use of these concepts will help you master them, and you’ll be able to produce high-quality software using C#.

Now let’s jump into the next chapter, where you’ll learn about thread programming.

Summary

This chapter addressed the following key questions.
  • What is a generic program? And why is it important?

  • What are the advantages of generic programming over nongeneric programming?

  • Why is the default keyword useful in the context of generics? And how can it be used in my program?

  • How do you use built-in delegates—Func, Action, and Predicate—in a program?

  • How do you impose constraints in generic programming?

  • How do you use covariance and contravariance with generic delegates and interfaces?

  • How do you overload a generic method? And why should you be careful?

  • How do you override a generic method?

  • How do you use a self-referencing generic type?

  • How do static variables behave in a generic program?

  • What are some of the key restrictions in generics?

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.136.154.103