Click here to Skip to main content
15,892,253 members
Articles / Programming Languages / C#
Tip/Trick

Inheritance Tip and Trick in C#

Rate me:
Please Sign up or sign in to vote.
2.27/5 (4 votes)
14 Mar 2021CPOL3 min read 4.1K   4   3
Three examples to avoid inheritance problems in C#
Of the three key principles of OOP, inheritance brings the most problems. In this tip, I will try to explain why this is so and show three key examples of avoiding inheritance problems while observing all the principles of OOP.

Introduction

I try to show three key examples of avoiding inheritance problems while observing all the principles of OOP. The key principles of OOP, by which an application with an OOP approach is built, can be so contradictory that using them has more cons than pros. Of the three key principles of OOP, inheritance brings the most problems.

Background

Let's describe what inheritance in OOP is. Inheritance is an object-oriented programming concept whereby an abstract data type can inherit the data and functionality of some existing type, facilitating the reuse of software components. Inheritance is one of the principles that helps us implement polymorphism, but polymorphism, in principle, can be implemented without inheritance.

Using the Code

Let's write a couple of classes that describe different animals and they are inherited from the general class, and that is, we create an abstract class, Animal. Let's take a look at the code for our example, and the code is here:

C#
abstract class Animal
        {
            public abstract void Voice();
        }
        class Cat : Animal
        {
            public override void Voice()
            {
                Console.WriteLine("meow");
            }
        }
        class WhiteBear : Animal
        {
            public override void Voice()
            {
                Console.WriteLine("Bugaga");
            }
        }
        class Horse : Animal
        {
            public override void Voice()
            {
                Console.WriteLine("Hee-haw");
            }
        }

Everything seems to be fine. We created a base abstract class Animal and then overridden the method Voice() to create sounds in the descendants (Cat, WhiteBear, Horse ). Next, let's add some functionality for moving in our classes. To do this, we go into our abstract class and create another virtual method and add this method to all child classes. The code is given below:

C#
abstract class Animal
        {
            public abstract void Voice();
            public abstract void Walk();
        }
        class Cat : Animal
        {
            public override void Voice()
            {
                Console.WriteLine("meow");
            }
            public override void Walk()
            {
                Console.WriteLine("I'm walking");
            }
        }
        class WhiteBear : Animal
        {
            public override void Voice()
            {
                Console.WriteLine("Bugaga");
            }
            public override void Walk()
            {
                Console.WriteLine("I'm walking");
            }
        }
        class Horse : Animal
        {
            public override void Voice()
            {
                Console.WriteLine("Hee-haw");
            }
            public override void Walk()
            {
                Console.WriteLine("I'm walking");
            }
        }

Yes, now our animals can move. In abstact class Animal, we made the method Walk() and this method was overloaded in classes Cat, WhiteBear, Horse. Then, we understand that a polar bear can also swim, and moreover, a horse can also, but a cat cannot do it. Let's add a new method in our class Animal. Below is the code:

C#
abstract class Animal
        {
            public abstract void Voice();
            public abstract void Walk();
            public abstract void Swim();
        }

Now we need to inherit the method in all classes, which does not triple us. Alternatively, we can make the implementation of each method in a separate class. The code is as follows:

C#
class Program
    {
        abstract class Animal
        {
            public abstract void Voice(); 
        }
        abstract class WalkingAnimal : Animal
        {
            public abstract void Walk();
        }
        abstract class SwimmingAndWalkingAnimal : WalkingAnimal
        {
            public abstract void Swim();
        }
        class Cat : WalkingAnimal
        {
            public override void Voice()
            {
                Console.WriteLine("meow");
            }
            public override void Walk()
            {
                Console.WriteLine("I'm walking");
            }
        }
        class WhiteBear : SwimmingAndWalkingAnimal
        {
            public override void Swim()
            {
                Console.WriteLine("I'm Swimming");
            }
            public override void Voice()
            {
                Console.WriteLine("Bugaga");
            }
            public override void Walk()
            {
                Console.WriteLine("I'm walking");
            }
        }
        class Horse : SwimmingAndWalkingAnimal
        {
            public override void Swim()
            {
                Console.WriteLine("I'm Swimming");
            }
            public override void Voice()
            {
                Console.WriteLine("Hee-haw");
            }
            public override void Walk()
            {
                Console.WriteLine("I'm walking");
            }
        }

Here, we tried to make a classification, but this way is not optimized for us and it can make more cons for us than pros. This is just one example of how adding only one method in the base class leads to changes in the entire functionality. And this is just an example from textbooks, not a real development, and we had to fence additional abstract classes to implement floating animals. It's good that C# doesn't have multiple inheritance. For trying to optimize our code, we can try use interfaces of classes. Let's write all the characteristics in the interfaces. The code is as given below:

C#
class Program
    {
        interface IAnimal
        {
            void Sound();
        }

        interface IWalkingAnimal
        {
            void Walk();
        }

        interface ISwimmingAnimal
        {
            void Swim();
        }

        class WhiteBear : IAnimal, IWalkingAnimal, ISwimmingAnimal
        {
            public void Sound()
            {
                Console.WriteLine("Bugaga");
            }
            public void Swim()
            {
                Console.WriteLine("I'm Swimming");
            }
            public void Walk()
            {
                Console.WriteLine("I'm walking");
            }
        }

        static void Main(string[] args)
        {
            WhiteBear whiteBear1 = new WhiteBear();
            whiteBear1.Sound();
            whiteBear1.Walk();
            whiteBear1.Swim();
        }

However, our code is still not optimal. There is a duplicate code problem. But it can be noted that for each type of animal, each action must be described, which forces us to write the same code.

Using Strategy Pattern

One method for solving the problem of adding new methods to existing code is to use the strategy method. Let's optimize our code and get rid of duplication. The code is here:

C#
class Program
    {
        interface IAnimal { }
        interface IAction
        {
            void Action();
        }

        interface IWalk
        {
            void Walk();
        }

        interface ISwim
        {
            void Swim();
        }

        class SwimAction : IAction
        {
            void IAction.Action()
            {
                Console.WriteLine("I'm swimming");
            }
        }

        class WalkAction : IAction
        {
            void IAction.Action()
            {
                Console.WriteLine("I'm walking");
            }
        }
        class WhiteBear : IAnimal, IWalk, ISwim
        {
            IAction walkAction;
            IAction swimAction;
            public WhiteBear()
            {
                walkAction = new WalkAction();
                swimAction = new SwimAction();
            }
            public void Swim()
            {
                swimAction.Action();
            }
            public void Walk()
            {
                walkAction.Action();
            }
        }

        static void Main(string[] args)
        {
            WhiteBear whiteBear1 = new WhiteBear();
            whiteBear1.Swim();
            whiteBear1.Walk();
            Console.ReadKey();
        }

Here, we have implemented the Strategy pattern. We will customize specific navigation strategies to the base interface. The result is in image 1.

Image 1

Image 1 - Result of Strategy pattern

Using Contain Methods in Interfaces

In C# 8, it became possible for interfaces to contain methods by default. This helps us write code without duplication and will allow us to add new methods without a single problem. Let's create an application that supports C# 8 and higher. The code is as follows:

C#
class Program
    {
        interface IAnimal { }
        interface ISwim
        {
            void Swim()
            {
                Console.WriteLine("I'm swimming");
            }
        }
        interface IWalk
        {
            void Walk()
            {
                Console.WriteLine("I'm walking");
            }
        }

        class WhiteBear : ISwim, IWalk { }

        static void Main(string[] args)
        {
            WhiteBear whiteBear1 = new WhiteBear();
            ISwim swim = (ISwim)whiteBear1;
            swim.Swim();      
            IWalk walk = (IWalk)whiteBear1;
            walk.Walk();
        }
    }

The result is shown in image 2:

Image 2

Image 2 - Result of implementation of functionality in interfaces

It is worth noting that this method is suitable for applications written in C# 8 and above.

Using Traits

Moreover, we have a more interesting solution. There, we can use traits. Let's look at the code.

C#
abstract class Animal { }

    interface IAnimal<T> where T : Animal { }
    class Walk : Animal { }
    class Swim : Animal { }
    class WhiteBear : IAnimal<Walk>, IAnimal<Swim> { }

    static class AnimalStatic
    {
        public static void WalkAction(this IAnimal<Walk> trait)
        {
            Console.WriteLine("I'm walking");
        }

        public static void SwimAction(this IAnimal<Swim> trait)
        {
            Console.WriteLine("I'm swimming");
        }
    }

    class Program
    {   
        static void Main(string[] args)
        {

            WhiteBear whiteBear1 = new WhiteBear();
            whiteBear1.SwimAction();
            whiteBear1.WalkAction();    
        }
    }

We will have the same result. The whole point here lies in the use of the static class. The result is shown in image 3.

Image 3

Image 3 - Result of using traits

Conclusion

In this post, I showed you the problem of inheritance and solution to this problem. You can use these three solutions in your application and easily add a new method in your abstract class.

History

  • 15th March, 2021: Initial version

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Software Developer (Senior)
United States United States
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions

 
AnswerC# .NET and ASP.NET Pin
Member 1510709618-Mar-21 9:51
Member 1510709618-Mar-21 9:51 
GeneralMy vote of 5 Pin
Vincent Radio16-Mar-21 1:08
professionalVincent Radio16-Mar-21 1:08 
GeneralRe: My vote of 5 Pin
Uladzislau Baryshchyk16-Mar-21 14:02
Uladzislau Baryshchyk16-Mar-21 14:02 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.