Polymorphism


Key OOP Concept

It is like a phylogenetic tree. The higher hierarchy you climb, the more abstract concept about the object you get.

  • Objects: instances of classes. E.g., my cat.

  • Class: the blueprint for the data type (variables) and available methods for a given type or class of object. E.g., cat. Something that is cute, fluffy, with two eyes, four legs and a tail.


  • Encapsulation: the combination of the data and actions that are related to an object. Achieve by using access modifiers, which is a way to ensure security. E.g., a cat can access its toilet.

  • Composition: is about what an object is made of. E.g., A cat has two eyes, four legs and a tail.

  • Aggregation: is about what can be combined with an object. E.g., a cat does not have a horn. You cannot ask a cat to fly like a bird, but it can walk like a quadruped.

  • Inheritance: reuse code by having a subclass derive from a base or superclass. All functionality in the base class is inherited by and becomes available in the derived class. E.g., quadruped. A cat inherits the function of a quadruped.

  • Polymorphism: allow a derived class to override an inherited action to provide custom behavior. E.g., animal. Animals like dogs speak “Woof!”, but cat speaks “Meow!”.

  • Abstraction: is about capturing the core idea of an object and ignoring the details or specifics. C# has an abstract keyword which formalizes the concept. If a class is not explicitly abstract, then it can be described as being concrete. E.g., define a abstract class called Animal (a live creature that moves), but implement its derived class Cat by giving actual behaviors of a cat.

Understanding Polymorphism

Polymorphism: allow a derived class to override an inherited action to provide custom behavior. E.g., animal. Animals like dogs speak “Woof!”, but cats speak “Meow!”.

Generics Doc

Generics introduces the concept of type parameters to .NET, which make it possible to design classes and methods that defer the specification of one or more types until the class or method is declared and instantiated by client code.

Generic Method

Convert.ChangeType Doc IConvertible Interface Doc

namespace CRC_CSD_07;

class Program
{
    static void Main(string[] args)
    {
        // Function overloading
        int r = 2;
        int newR = DoubleMyResource(r);
        Console.WriteLine($"My old resource is {r} and the new one is {newR}");
        float f = 2.5f;
        float newF = DoubleMyResource(f);
        Console.WriteLine($"My old resource is {f} and the new one is {newF}");
        List<int> s = new List<int> { 1, 2, 3 };
        List<int> newS = DoubleMyResource(s);
        Console.WriteLine("My old resource is {" + string.Join(", ", s) + "} and the new one is {" + string.Join(", ", newS) + "}");

        // Generic functions
        int gr = 2;
        int newGR = DoubleMyResource<int>(gr);
        Console.WriteLine($"My old resource is {gr} and the new one is {newGR}");

        float gf = 2.5f;
        float newGF = DoubleMyResource<float>(gf);
        Console.WriteLine($"My old resource is {gf} and the new one is {newGF}");

        string gs = "12.5";
        string newGS = DoubleMyResource<string>(gs);
        Console.WriteLine($"My old resource is {gs} and my new resource is {newGS}");
    }

    // Function overloading
    public static int DoubleMyResource(int resource)
    {
        resource *= 2;
        return resource;
    }

    public static float DoubleMyResource(float resource)
    {
        resource *= 2.0f;
        return resource;
    }

    public static List<int> DoubleMyResource(List<int> resource)
    {
        List<int> newResource = new List<int>();
        // List<int> newResource = resource;

        // Copy the resource to a new list
        foreach (int r in resource)
        {
            newResource.Add(r);
        }
        // Add the elements in the newResource to the resource list
        foreach (int r in newResource)
        {
            resource.Add(r);
        }
        return resource;
    }

    // Generic function
    // T stands for a type that implements the IConvertible interface
    // IConvertible: Defines methods that convert the value of the implementing 
    // reference or value type to a common language runtime type that has an 
    // equivalent value.
    public static T DoubleMyResource<T>(T resource) where T : IConvertible
    {
        double d = resource.ToDouble(Thread.CurrentThread.CurrentCulture);
        // ToDouble: A system method to convert a type to a double. We just use it for now.
        // Thread.CurrentThread.CurrentCulture: ToDouble requires a parameter 
        // that implements IFormatProvider to understand the format of numbers 
        // for a language and region. We can pass the CurrentCulture property 
        // of the current thread to specify the language and region used by 
        // your computer. 
        d *= 2.0; // is equal to r = r * 2.0M

        return (T)Convert.ChangeType(d, typeof(T));
    }
}
$ My old resource is 2 and the new one is 4
$ My old resource is 2.5 and the new one is 5
$ My old resource is {1, 2, 3, 1, 2, 3} and the new one is {1, 2, 3, 1, 2, 3}
$ My old resource is 2 and the new one is 4
$ My old resource is 2.5 and the new one is 5
$ My old resource is 12.5 and my new resource is 25

Generic Class

In MyBusiness/Program.cs,

using System;
using Animals;

// namespace
namespace Animals;

class Program
{
    static void Main(string[] args)
    {
        // Cat is currently flexible, because any type can be set for the food 
        // field and input parameter. But there is no type checking, so inside 
        // the CheckFood method, we cannot safely do much and the results are 
        // sometimes not what you might expect
        Cat cat1 = new Cat();
        cat1.food = 5;
        Console.WriteLine($"Cat food with an integer: {cat1.checkFood(5)}");

        Cat cat2 = new Cat();
        cat2.food = "fish";
        Console.WriteLine($"Cat food with a string: {cat2.checkFood("fish")}");

        // The type is defined at allocation
        GenericCat<int> gCat1 = new GenericCat<int>();
        gCat1.food = 5;
        Console.WriteLine($"Cat food with an integer: {gCat1.checkFood(5)}");

        GenericCat<string> gCat2 = new GenericCat<string>();
        gCat2.food = "fish";
        Console.WriteLine($"Cat food with a string: {gCat2.checkFood("fish")}");

        // generic methods
        string food1 = "4";
        Console.WriteLine("Double cat food {0} to {1}",
            arg0: food1,
            arg1: GenericMethodCat.DoubleMyFood<string>(food1)
        );

        byte food2 = 3;
        Console.WriteLine("Double cat food {0} to {1}",
            arg0: food2,
            arg1: GenericMethodCat.DoubleMyFood<byte>(food2)
        );
    }
}

In PetLibrary/Cat.cs,

namespace Animals;

public class Cat
{
    public object? food = default(object);
    // object: Supports all classes in the .NET class hierarchy and provides low-level 
    // services to derived classes. This is the ultimate base class of all .NET 
    // classes; it is the root of the type hierarchy.
    // default: A default value expression produces the default value of a type

    public string checkFood(object input)
    {
        if (food == null)
        {
            return "Expected food is empty.";
        }
        else if (food == input)
        // else if (food.Equals(input)) // Here, you explicitly compare the value not the address. 
        // Cat is currently flexible, because any type can be set for the food 
        // field and input parameter. But there is no type checking, so inside 
        // the Process method, we cannot safely do much and the results are sometimes 
        // not what you might expect; for example, when passing int values into 
        // an object parameter! This is because the value 5 stored in the food is 
        // stored in a different memory address to the value 5 passed as a parameter 
        // and when comparing reference types like any value stored in object, they 
        // are only equal if they are stored at the same memory address, that is, the 
        // same object, even if their values are equal. We can solve this problem by 
        // using generics.
        {
            return "Expected food and input are the same.";
        }
        else
        {
            return "Expected food and input are NOT the same.";
        }
    }
}

public class GenericCat<T> where T : IComparable
{
    public T? food = default(T?);

    public string checkFood(T input)
    {
        if (food == null)
        {
            return "Expected food is empty!";
        }
        else if (food.CompareTo(input) == 0)
        {
            return "Expected food and input are the same.";
        }
        else
        {
            return "Expected food and input are NOT the same.";
        }
    }
}

public class GenericMethodCat
{
    public static double DoubleMyFood<T>(T input) where T : IConvertible
    {
        double d = input.ToDouble(Thread.CurrentThread.CurrentCulture);
        // ToDouble: A system method to convert a type to a double. We just use it for now.
        // Thread.CurrentThread.CurrentCulture: ToDouble requires a parameter that 
        // implements IFormatProvider to understand the format of numbers for a language 
        // and region. We can pass the CurrentCulture property of the current thread to 
        // specify the language and region used by your computer. 

        return 2.0 * d;
    }
}
$ Cat food with an integer: Expected food and input are NOT the same.
$ Cat food with an integer: Expected food and input are the same.
$ Cat food with an integer: Expected food and input are the same.
$ Cat food with an integer: Expected food and input are the same.
$ Double cat food 4 to 8
$ Double cat food 3 to 6

Do not forget to add the environment variables in MyBusiness.csproj

  <ItemGroup>
    <ProjectReference
      Include="../PetLibrary/PetLibrary.csproj" />
  </ItemGroup>

Thread.CurrentUICulture Property Doc


Selected Theory

Useful Advanced C# Data Structure

  • Collections Doc. We will introduce one-by-one later.