9 minute read

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

using System;

// namespace
namespace MyBusiness
{
    // main program
    class Program
    {
        // internal is the default if no access modifier is specified.
        static void Main(string[] args)
        {
            /*
            Function overloading
            */
            // int
            int or = 2;
            int newOR = DoubleMyResourc(or);

            Console.WriteLine($"My old resource is {or} and my new resource is {newOR}");

            // float
            float of = 2.5F;
            float newOF = DoubleMyResourc(of);

            Console.WriteLine($"My old resource is {of} and my new resource is {newOF}");

            /*
            Generics
            */
            // int
            int gr = 2;
            int newGR = DoubleMyResourc<int>(gr);

            Console.WriteLine($"My old resource is {gr} and my new resource is {newGR}");

            // float
            float gf = 2.5F;
            float newGF = DoubleMyResourc<float>(gf);

            Console.WriteLine($"My old resource is {gf} and my new resource is {newGF}");
        }

        public static int DoubleMyResourc(int resource)
        {
            return 2*resource;
        }

        public static float DoubleMyResourc(float resource)
        {
            return 2.0F*resource;
        }

        // Compile error! The type conversion is unknown to the compiler.
        // static T DoubleMyResource<T>(T resource)
        // {
        //     return  2 * resource;
        // }

        public static T DoubleMyResourc<T>(T resource)
        {
            // Type conversion is also called "casting"!
            decimal r = (decimal) Convert.ChangeType(resource, typeof(decimal));
            r *= 2.0M; // r = r * 2.0

            return (T)Convert.ChangeType(r, typeof(T));
        }
    }
}

Generic Class

In MyBusiness/Program.cs,

using System;
using Animals;

// namespace
namespace MyBusiness
{
    // main program
    internal 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
            var t1 = new Cat();
            t1.food = 5;
            Console.WriteLine($"Cat food with an integer: {t1.CheckFood(5)}");
            var t2 = new Cat();
            t2.food = "fish";
            Console.WriteLine($"Cat food with a string: {t2.CheckFood("fish")}");

            // The type is defined at allocation
            var gt1 = new GenericCat<int>();
            gt1.food = 5;
            Console.WriteLine($"Cat food with an integer: {gt1.CheckFood(5)}");
            var gt2 = new GenericCat<string>();
            gt2.food = "fish";
            Console.WriteLine($"Cat food with a string: {gt2.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(food2));
        }
    }
}

In PetLibrary/Animals.cs,

/*
The animal namespace
*/
namespace Animals
{
    // Working with non-generic types
    public class Cat
    {
        public object? food = default(object);
        public string CheckFood(object input)
        {
            if (food == input)
            {
                return "Expected food and input are the same.";
            }
            else
            {
                return "Expected food and input are NOT the same.";
            }
        }
    }

    // Working with generic types
    // One can consider T as a kind of template
    public class GenericCat<T> where T : IComparable
    {
        // A default() returns the default value of type parameter. Here is used for initialization.
        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.";
            }
        }
    }

    // Working with generic methods
    // One can consider T as a kind of template
    public class GenericMethodCat
    {
        public static double DoubleMyFood<T>(T input)
        where T : IConvertible
        {
            // convert using the current culture
            double d = input.ToDouble(
                Thread.CurrentThread.CurrentCulture);   
                // A system method to convert a string to a double, we simply use it for now.
            return 2 * d;
        }
    }
}
$ Cat food with an integer: Expected food and input are NOT the same.
$ Cat food with a string: Expected food and input are the same.
$ Cat food with an integer: Expected food and input are the same.
$ Cat food with a string: Expected food and input are the same.
$ Double cat food 4 to 8
$ Double cat food 3 to 6

Thread.CurrentUICulture Property Doc

You have now seen two ways to change the behavior of an inherited method. We can hide it using the new keyword (known as non-polymorphic inheritance), or we can override it (known as polymorphic inheritance).

Override and New Keywords Doc

In method overriding (using the keyword override), when base class reference variable pointing to the object of the derived class, then it will call the overridden method in the derived class.

In the method hiding (using the keyword new), when base class reference variable pointing to the object of the derived class, then it will call the hidden method in the base class.

E.g., when a method is hidden with new, the compiler is not smart enough to know that the object is an WildCat, so it calls the WriteToConsole method in Cat.

Single Inheritance

In MyBusiness/Program.cs,

using System;
using Animals;

// namespace
namespace MyBusiness
{
    // main program
    internal class Program
    {
        static void Main(string[] args)
        {
            // Declare a dog
            Dog shiba = new Dog();
            shiba.Speak();

            // Declare a cat and a wild cat
            Cat nana = new Cat("Nana", new DateTime(2019, 12, 9));
            WildCat leopard = new WildCat
            {
                Name = "Alice",
                CountryCode = "Taiwan"
            };

            nana.Speak();
            leopard.Speak();
        }
    }
}

In PetLibrary/Animals.cs,

/*
The animal namespace
*/
namespace Animals
{
    public class Animal
    {
        public virtual void Speak()
        {
            Console.WriteLine("");
        }
    }

    public class Dog : Animal
    {
        public override void Speak()
        {
            Console.WriteLine("Woof!");
        }
    }

    public class Cat : Animal
    {
        /*
        Class field, including different variables
        they can be organized by similar characteristics
        */

        // The name of the cat
        public string Name;
        // The birthday of the cat
        public DateTime DateOfBirth;

        /*
        Class methods, where functions should be implemented
        */

        // Constructors
        // Default constructor. It will be called by default
        public Cat()
        {
            Name = "Unknown";
            DateOfBirth = DateTime.Today;
        }
        // Parameterized Constructor
        public Cat(string name, DateTime dateOfBirth)
        {
            this.Name = name;
            this.DateOfBirth = dateOfBirth;
        }
        // Finalizer
        ~Cat()
        {
        }

        public override void Speak()
        {
            Console.WriteLine("Meow!");
        }

        // Print out method
        public void WriteToConsole()
        {
            Console.WriteLine($"{Name} was born on a {DateOfBirth:dddd}.");
        }
    }

    public class WildCat : Cat
    {
        public string? CountryCode { get; set; }
        public DateTime FoundDate { get; set; }

        // Default Constructor
        public WildCat()
        {
        }

        public override void Speak()
        {
            Console.WriteLine("WildMeow!");
        }

        // hidden methods
        // based on the keyword new
        public new void WriteToConsole()
        {
            base.WriteToConsole();
            Console.WriteLine(format:
              "{0} was born on {1:dd/MM/yy} and found on {2:dd/MM/yy}",
              arg0: Name,
              arg1: DateOfBirth,
              arg2: FoundDate);
        }
    }
}

Nested Inheritance Doc

Note that a class can derive from only a single direct base class. But what if I need information from multiple classes? Use interface.

In MyBusiness/Program.cs,

using System;
using Animals;

// namespace
namespace MyBusiness
{
    // main program
    internal class Program
    {
        static void Main(string[] args)
        {
            // Declare a dog
            Dog shiba = new Dog();
            shiba.Speak();

            // Declare a cat and a wild cat
            Cat nana = new Cat("Nana", new DateTime(2019, 12, 9));
            WildCat leopard = new WildCat
            {
                Name = "Alice",
                CountryCode = "Taiwan"
            };

            nana.Speak();
            leopard.Speak();
        }
    }
}

In PetLibrary/Animals.cs

/*
The animal namespace
*/
namespace Animals
{
    public class Animal
    {
        public virtual void Speak()
        {
            Console.WriteLine("");
        }
    }

    public class Dog : Animal, IRun
    {
        public override void Speak()
        {
            Console.WriteLine("Woof!");
        }

        // Implementation of interface IRun
        public double Speed { get; set; }
        public int Distance { get; }
        public double SpeedUp(double velocity)
        {
            Speed += 2.0 * velocity;
            return Speed;
        }
    }

    interface IRun
    {
        // Instance field
        // double Velocity; // compile error
        // Property
        double Speed { get; set; }
        int Distance { get; }

        double SpeedUp(double velocity);
    }

    public class Cat : Animal
    {
        /*
        Class field, including different variables
        they can be organized by similar characteristics
        */

        // The name of the cat
        public string Name;
        // The birthday of the cat
        public DateTime DateOfBirth;

        /*
        Class methods, where functions should be implemented
        */

        // Constructors
        // Default constructor. It will be called by default
        public Cat()
        {
            Name = "Unknown";
            DateOfBirth = DateTime.Today;
        }
        // Parameterized Constructor
        public Cat(string name, DateTime dateOfBirth)
        {
            this.Name = name;
            this.DateOfBirth = dateOfBirth;
        }
        // Finalizer
        ~Cat()
        {
        }

        public override void Speak()
        {
            Console.WriteLine("Meow!");
        }

        // Print out method
        public void WriteToConsole()
        {
            Console.WriteLine($"{Name} was born on a {DateOfBirth:dddd}.");
        }
    }

    public class WildCat : Cat
    {
        public string? CountryCode { get; set; }
        public DateTime FoundDate { get; set; }

        // Default Constructor
        public WildCat()
        {
        }

        public override void Speak()
        {
            Console.WriteLine("WildMeow!");
        }

        // hidden methods
        // based on the keyword new
        public new void WriteToConsole()
        {
            base.WriteToConsole();
            Console.WriteLine(format:
              "{0} was born on {1:dd/MM/yy} and found on {2:dd/MM/yy}",
              arg0: Name,
              arg1: DateOfBirth,
              arg2: FoundDate);
        }
    }
}
$ Woof!
$ Meow!
$ WildMeow!

Abstract Class Doc

In MyBusiness/Program.cs,

using System;
using Animals;

// namespace
namespace MyBusiness
{
    // main program
    internal class Program
    {
        static void Main(string[] args)
        {
            // Create a cat array
            Cat nana = new Cat("Nana", new DateTime(2019, 12, 9));
            WildCat leopard = new WildCat
            {
                Name = "Alice",
                CountryCode = "Taiwan"
            };

            nana.Speak();
            leopard.Speak();
        }
    }
}

In PetLibrary/Animals.cs,

/*
The animal namespace
*/
namespace Animals
{
    public abstract class Animal
    {
        public abstract void Speak();
    }

    public class Cat : Animal
    {
        /*
        Class field, including different variables
        they can be organized by similar characteristics
        */

        // The name of the cat
        public string Name;
        // The birthday of the cat
        public DateTime DateOfBirth;

        /*
        Class methods, where functions should be implemented
        */

        // Constructors
        // Default constructor. It will be called by default
        public Cat()
        {
            Name = "Unknown";
            DateOfBirth = DateTime.Today;
        }
        // Parameterized Constructor
        public Cat(string name, DateTime dateOfBirth)
        {
            this.Name = name;
            this.DateOfBirth = dateOfBirth;
        }
        // Finalizer
        ~Cat()
        {
        }

        public override void Speak()
        {
            Console.WriteLine("Meow!");
        }

        // Print out method
        public void WriteToConsole()
        {
            Console.WriteLine($"{Name} was born on a {DateOfBirth:dddd}.");
        }
    }

    public class WildCat : Cat
    {
        public string? CountryCode { get; set; }
        public DateTime FoundDate { get; set; }

        // Default Constructor
        public WildCat()
        {
        }

        public override void Speak()
        {
            Console.WriteLine("WildMeow!");
        }

        // hidden methods
        // based on the keyword new
        public new void WriteToConsole()
        {
            base.WriteToConsole();
            Console.WriteLine(format:
              "{0} was born on {1:dd/MM/yy} and found on {2:dd/MM/yy}",
              arg0: Name,
              arg1: DateOfBirth,
              arg2: FoundDate);
        }
    }
}

Selected Theory

Useful Advanced C# Data Structure

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

Updated: