Infergnome Infergnome - 23 days ago 7
C# Question

C# What is the advantage of using interfaces for simulating multiple inheritance?

Interfaces are a feature of C# that I've never quite been able to see the purpose of. I see them used all the time in professional code, but for the life of me I can't work out the reasoning.

My question here is specifically about interfaces to simulate multiple inheritance. In reading around the subject, I'll often come up on an example like this:

interface IAddition
{
int add(int a, int b);
}

interface ISubtraction
{
int sub(int a, int b);
}

class Calculation : IAddition, ISubtraction
{
public int add(int a, int b)
{
return result1 = a + b;
}

public int sub(int a, int b)
{
return result1 = a - b;
}
}


What I don't understand is - what benefit do the interfaces bring here? It looks to me like you could remove them completely, and the Calculation class would still work exactly the same. What am I missing here?

Any help would be appreciated - I've been looking for something to make interfaces 'click' in my head for quite a while now.

It's worth noting I'm a game developer, and I'm looking for a solution for this to improve my ability to develop games, but I thought that it was a general enough question to post here.

Answer

Yes you're right. The code will still compile after you remove the interfaces. So why add them?

Well, your IAddition and ISubtraction examples aren't really good to make my point here. Since the best way to learn programming is to look at how other people code, I'll use the System.IComparable<T> interface as an example. It is an interface provided by the .NET framework.

The reason why we use interfaces is to achieve polymorphism. Interfaces are like protocols which classes must conform to. If a class implements an interface, it is guaranteed that the class can do the things that the interface specifies. This all sounds quite confusing so let's take a look at the IComparable<T> interface. To make things simpler, let's make it IComparable<int>

IComparable<int> has this method:

int CompareTo(int other);

This basically means

Everything that implements IComparable<int> has the ability to be compared with an integer. We can call its CompareTo method to decide whether it is bigger than, equal to, or less than any int.

Since everything that implemets IComparable<int> must have the method CompareTo, it is guaranteed that the object you're calling CompareTo on can be compared with an int.

So how is this useful?

The ability to be compared with something else is useful when you sort arrays. The Array.Sort method make use of the IComparable<T> interface. Here's how the Sort method works (highly simplified):

It first checks whether the array's elements can be compared by checking whether they implement IComparable<T>. If they do, compare them by calling CompareTo! Why can it be so sure that there is a method called CompareTo? Because the objects all implement IComparable<T>, which guarantees a CompareTo method! After comparing, Sort can figure out which element comes first and last.

You see how different things need to be compared differently? Integers and doubles can be compared by subtracting one from the other and checking whether the result is a positive or negative number. But strings are compared alphabetically. If there weren't a IComparable<T> interface, there would have been a different Sort method for strings, and a Sort method for int, and a Sort method for all the types that can be compared. Worse, if client code creates something that can be compared, the client code needs to write its own Sort method! Don't forget that there are a ton of other methods that make use of the ability to be compared. Do all those methods need a different version for each type?

That's why interfaces are so important. With them, only one Sort method is needed because polymorphism takes care of the rest.

Let me summarise the advantages of interfaces:

  • It provides flexibility (integers and strings can both be compared to integers and strings, but in different ways)
  • It reduces code (You don't need to write extra Sort methods!)
  • It ensures safety (If you forgot to implement a method, the compiler will tell you!)

Let's assume that IComparable<T> does not exist, and string and int and other types that can be compared as their own CompareTo method. The Sort methods can look like this

public static void Sort(int[] arr) {
    // note that I'm using bubble sort here. A real sort method wouldn't use this coz it's slow.
    // source: http://stackoverflow.com/questions/14768010/simple-bubble-sort-c-sharp
    int temp = 0;

    for (int write = 0; write < arr.Length; write++) {
        for (int sort = 0; sort < arr.Length - 1; sort++) {
            if (arr[sort].CompareTo(arr[sort + 1]) > 0) {
                temp = arr[sort + 1];
                arr[sort + 1] = arr[sort];
                arr[sort] = temp;
            }
        }
    }

}

public static void Sort(string[] arr) {
    string temp = 0;

    for (int write = 0; write < arr.Length; write++) {
        for (int sort = 0; sort < arr.Length - 1; sort++) {
            if (arr[sort].CompareTo(arr[sort + 1]) > 0) {
                temp = arr[sort + 1];
                arr[sort + 1] = arr[sort];
                arr[sort] = temp;
            }
        }
    }

}

You could argue that one Sort method that takes an object[] checks the type of each element could also work. Yes, but that suffers from the same problem as multiple Sort methods. You still need to add another sort method or another check if a new comparable class is created.

The problem all end if you have a IComparable<T> interface!

public static void Sort<T>(T[] arr) where T : IComparable<T> {
    T temp;

    for (int write = 0; write < arr.Length; write++) {
        for (int sort = 0; sort < arr.Length - 1; sort++) {
            if (arr[sort].CompareTo(arr[sort + 1]) > 0) {
                temp = arr[sort + 1];
                arr[sort + 1] = arr[sort];
                arr[sort] = temp;
            }
        }
    }
}

Since the type constraint says that T must implement IComparable<T>, CompareTo can be called on every object in the array with no problems!

Now all you need to do if you wish to add another comparable class is to implement the interface. Whereas if the interface didn't exist, you have to write a CompareTo method and a Sort method!