I have found that inserting a class between an interface and another derived class results in the interface's default implementation being called rather than the derived implementation for the same method. This is unexpected behavior. Why is this happening?
I have created a sample project which reproduces the problem:
public interface IPrinterInterface
{
public void PrintIt() => Console.WriteLine("Interface");
}
public class MiddlePrinter : IPrinterInterface{}
public class Printer : MiddlePrinter
{
public void PrintIt() => Console.WriteLine("Class");
}
class Program
{
static void Main(string[] args)
{
var printer = (IPrinterInterface)new Printer();
printer.PrintIt(); // This prints "Interface"
Console.ReadLine(); // so the app sits
}
}
This code results in Interface
being printed out.
To contrast, if the MiddlePrinter class is removed from the inheritance (as shown in the following code), then the code prints "Class":
public interface IPrinterInterface
{
public void PrintIt() => Console.WriteLine("Interface");
}
public class Printer : IPrinterInterface
{
public void PrintIt() => Console.WriteLine("Class");
}
class Program
{
static void Main(string[] args)
{
var printer = (IPrinterInterface)new Printer();
printer.PrintIt(); // This prints "Class"
Console.ReadLine(); // so the app sits
}
}
I didn't expect to see this type of behavior, can someone explain why this is happening?
This has been reproduced in a .NET5 console application and a modern Xamarin Android application.
Default methods enable you to add new functionality to existing interfaces and ensure binary compatibility with code written for older versions of those interfaces. In particular, default methods enable you to add methods that accept lambda expressions as parameters to existing interfaces.
Default interface methods enable an API author to add methods to an interface in future versions without breaking source or binary compatibility with existing implementations of that interface. The feature enables C# to interoperate with APIs targeting Android (Java) and iOS (Swift), which support similar features.
Default methods were introduced to provide backward compatibility for old interfaces so that they can have new methods without affecting existing code.
The Java Tutorials Documentation defines default interface methods: Default methods enable you to add new functionality to the interfaces of your libraries and ensure binary compatibility with code written for older versions of those interfaces. and then proceeds to supply a convoluted, 50+ line example.
The abstract class can have a state, and its methods can access the implementation's state. Although default methods are allowed in an interface, they can't access the implementation's state. Any logic we write in the default method should be with respect to other methods of the interface — those methods will be independent of the object's state.
Interface inheritance : An Interface can extend other interface. An interface can also extend multiple interfaces. Why Multiple Inheritance is not supported through a class in Java, but it can be possible through the interface?
An interface contains variables and methods like a class but the methods in an interface are abstract by default unlike a class. Multiple inheritance by interface occurs if a class implements multiple interfaces or also if an interface itself extends multiple interfaces.
Multiple Inheritance is not supported by class because of ambiguity. In case of interface, there is no ambiguity because implementation to the method (s) is provided by the implementing class up to Java 7. From Java 8, interfaces also have implementations of methods.
Classes do not inherit members from interfaces, not even with default implementations. Source.
Note that a class does not inherit members from its interfaces; that is not changed by this feature
As such, since MiddlePrinter
does not contain an inherited member for Printer
to override, the most concrete implementation of PrintIt()
from IPrinterInterface
's point of view is its own default implementation.
This is made evident by attempting to apply the override
keyword to Printer.PrintIt()
. You will get an error that no suitable method was found to override.
Without MiddlePrinter
, Printer
provides a more concrete implementation by replacing the default.
The reason is that when casting to an interface, it tries to find the first function that is in the same inheritance level as the interface or a lower inheritance level (same or parent class). Because you defined the functionality for the interface (default fallback function) it can't find a suitable function to override and simply reverts to the default function specified in the interface.
For example I've added another printer called Lower
printer:
public interface IPrinterInterface
{
// Default function if no parent functions are found.
public void PrintIt() => Console.WriteLine("Interface");
}
public class LowerPrinter
{
public void PrintIt() => Console.WriteLine("Lower");
}
public class MiddlePrinter : LowerPrinter, IPrinterInterface
{
}
public class Printer : MiddlePrinter
{
public void PrintIt() => Console.WriteLine("Class");
}
internal class Program
{
private static void Main(string[] args)
{
var printer = (IPrinterInterface)new Printer();
printer.PrintIt(); // This prints "Lower"
Console.ReadLine(); // so the app sits
}
}
But now if I extend it further:
public class BasePrinter
{
public void PrintIt() => Console.WriteLine("Base");
}
public class LowerPrinter : BasePrinter
{
public void PrintIt() => Console.WriteLine("Lower");
}
It will still print Lower
because it is the first suitable function found. The reason it can't search in the children or derived classes is because there is no guarantee the function will in those classes. You might be thinking "but how since they inherit?" - Because if the child class has a function with the same name - it hides the parent method - not implement it.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With