This is your first visit inside The Matrix. Welcome!

The Matrix has you…

This place will take you into the mirage of learning C# programming language, without any previous coding experience being required

Follow the White Rabbit…█

Close
Friday, November 24, 2017 16:17

Generic methods

Generic methods, like generic classes, are parameterized (typified) methods, which we use when we cannot specify the type of the method’s parameters. Also like in the case of generic classes, the replacement of unknown types with specific types happens when the method is called.

We typify a method when we add <K> after the name and before the opening bracket of the method, where K is the replacement of the type that will be used later.

<return_type><methods_name><K>(<params>)

we can use unknown type K for parameters in the parameter’s list of method <params>, whose type is unknown and also for return value or to declare variables of type substitute K in the body of the method.

For instance, let’s consider a method that swaps the values of two variables:

public static void Swap<K>(ref K a, ref K b)
{
    K oldA = a;
    a = b;
    b = oldA;
}

This is a method that swaps the values of two variables, without carrying of their types. That is why we define it as a generic, so we can use it for all types of variables.

So, now we can use it to swap integers and strings, for instance:

int num1 = 3;
int num2 = 5;

Console.WriteLine("Before swapping: {0} {1}", num1, num2);

// Invoking the method with type int
Swap<int>(ref num1, ref num2);
Console.WriteLine("After swapping: {0} {1}\n", num1, num2);

string str1 = "Hello";
string str2 = "There";

Console.WriteLine("Before swapping: {0} {1}!", str1, str2);

// Invoking the method with type string
Swap<string>(ref str1, ref str2);
Console.WriteLine("After swapping: {0} {1}!", str1, str2);

When ran, the output would be this one:

generic methods

Analyzing the code, we notice the ref keyword, of which we haven’t learned yet. Just assume for now that we use it to retain the changes to the variables, after the called method ends its execution.

Aside of that, you should know that by calling a generic method, we can miss the explicit declaration of a specific type (in our example ), because the compiler will detect it automatically, recognizing the type of the given parameters. In other words, our code can be simplified using the following calls:

Swap(ref num1, ref num2); // same as invoking Swap<int>
Swap(ref str1, ref str2); // same as invoking Swap<string>

The compiler will be able to recognize what is the specific type only if this type is involved in the parameter’s list. The compiler cannot recognize what is the specific type of a generic method only by the type of its return value, or if it does not have parameters. In both cases, this specific type will have to be given explicitly. In our example, it will be similar to the original method call, or by adding <int> or <string>.

Static methods can also be typified, unlike the properties and constructors of classes.

In our Generic classes lesson we had the following example of a generic class with two generic methods:

public class AnimalShelter<T>
{
    //some code
    public void Shelter(T newAnimal)
    {
        // Method body 
    }

    public T Release(int i)
    {
        // Method body 
    }
}

If we try to reuse the variable, which is used to mark the unknown type of the generic class, for example as T, in the declaration of generic method, then when we try to compile the class, we will get a warning: Type parameter ‘T’ has the same name as the type parameter from outer type ‘CommonOperations<T>’

This is happening because the scope of action of the unknown type T, defined in declaration of the method, overlaps the scope of action of the unknown type T, in class declaration:

public class CommonOperations<T>
{
    // CS0693
    public void Swap<T>(ref T a, ref T b)
    {

        T oldA = a;
        a = b;
        b = oldA;
    }
}

So if we want our code to be flexible, and our generic method to be safely called with a specific type, different from that in the generic class, declared at the moment of its instantiation, we just have to declare the replacement of the unknown type in the declaration of the generic method to be different than the parameter for the unknown type in the class declaration, as shown below:

public class CommonOperations<T>
{
    // No warning
    public void Swap<K>(ref K a, ref K b)
    {
        K oldA = a;
        a = b;
        b = oldA;
    }
}

Therefor, always make sure that there will be no overlapping of substitutes of the unknown types of method and class.

So! Now that we learned both about generic classes and methods, lets take our animal shelter example from the previous lesson and re-code it using both these two new concepts:

public class AnimalShelter<T>
{
    private const int DefaultPlacesCount = 20;
    private T[] animalList;
    private int usedPlaces;

    public AnimalShelter() : this(DefaultPlacesCount)
    {
    }

    public AnimalShelter(int placesCount)
    {
        this.animalList = new T[placesCount];
        this.usedPlaces = 0;
    }

    public void Shelter(T newAnimal)
    {
        if (this.usedPlaces >= this.animalList.Length)
            throw new InvalidOperationException("Shelter is full.");
        this.animalList[this.usedPlaces] = newAnimal;
        this.usedPlaces++;
    }

    public T Release(int index)
    {
        if (index < 0 || index >= this.usedPlaces)
            throw new ArgumentOutOfRangeException("Invalid cell index: " + index);
        T releasedAnimal = this.animalList[index];
        for (int i = index; i < this.usedPlaces - 1; i++)
        {
            this.animalList[i] = this.animalList[i + 1];
        }
        this.animalList[this.usedPlaces - 1] = null;
        this.usedPlaces--;
        return releasedAnimal;
    }
}

First thing we notice is that the above code generates a compiler error: Cannot convert null to type parameter ‘T’ because it could be a non-nullable value type. Consider using ‘default(T)’ instead.

The error is inside the method Release(), and it is related to recording a null value in the last released (rightmost) cell in the shelter. The problem is that we are trying to use the default value for a reference type, but we are not sure whether this type is a reference type or a primitive. Therefore the compiler displays the error above. If the type AnimalShelter is instantiated by a structure and not by a class, then the null value is not valid.

To handle this problem, we have to use in our code the construct default(T) instead of null, which returns the default value for the particular type that will be used instead of T. As we know, the default value for reference type is null, and for numeric types – zero. We can make the following change:

//this.animalList[this.usedPlaces - 1] = null;
this.animalList[this.usedPlaces - 1] = default(T);

Finally, the compilation runs smoothly and the class AnimalShelter operates correctly. We can test it as follows:

static void Main(string[] args)
{
    AnimalShelter<Dog> shelter = new AnimalShelter<Dog>();

    shelter.Shelter(new Dog());
    shelter.Shelter(new Dog());
    shelter.Shelter(new Dog());

    Dog d = shelter.Release(1); // Release the second dog
    Console.WriteLine(d);
    d = shelter.Release(0); // Release the first dog
    Console.WriteLine(d);
    d = shelter.Release(0); // Release the third dog
    Console.WriteLine(d);

    d = shelter.Release(0); // Exception: invalid cell index (no more dogs)
    
    Console.Read();
}

Concluding the last two lessons: generic classes and generic methods increase the re-usability, the security and the performance of the code, compared to other non-generic alternatives. As a general rule, the programmer should strive to create and use generic classes, whenever it is possible. The more generic types are used, the higher level of abstraction there is in the program, and the source code becomes more flexible and reusable. However, you should keep in mind that overuse of generics can lead to over-generalization and the code may become unreadable and difficult to understand by other programmers.

Comments

comments

Tags: , , ,

Leave a Reply