The addition of many features in Java 8 brought some interesting features, particularly Default Interface Implementation aka Defender Methods. While other additions were important and long overdue, this one stood out.
Default methods in Java share a similar purpose with Extension Methods in C#: extending interfaces without altering existing code. Imagine the pre-Linq era in C# where you wanted to add methods like “Contains, All, Any, Skin, Take…” to the IEnumerable interface. Directly adding these methods would require updating all classes implementing IEnumerable, which is a significant task. C# addressed this with Extension Methods, while Java opted for the more potent Default Interfaces, moving away from their initial consideration of “Java Extension Methods”.
1
2
3
4
5
| public interface SimpleInterface {
public void doSomeWork();
//A default method in the interface created using 'default' keyword
default public void doSomeOtherWork(){
System.out.println('DoSomeOtherWork implementation in the interface');
|
This capability to add behavior to interfaces, combined with the ability to implement multiple interfaces, allows for inheriting behavior from various sources.
C# Extension Methods also offer this behavioral extension, resembling Multiple Inheritance but with limitations. Being a compile-time construct, they lack the runtime flexibility of polymorphism, overriding, and vTables. Therefore, if a derived class overrides a method added via an extension method, invoking it through the interface will use the extension method’s implementation instead of the overridden one.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
|
public interface IPerson
{
string Name{get;set;}
string SayHello();
}
public static class IPersonExtensions
{
public static string SayBye(this IPerson person)
{
return person.Name + " says Bye from Extension Method";
}
}
public class Person:IPerson
{
public string Name {get;set;}
public Person(string name)
{
this.Name = name;
}
public string SayHello()
{
return this.Name + " says Hello";
}
public string SayBye()
{
return this.Name + " says Bye";
}
}
public class Program
{
public static void Main()
{
//the extension method is good to add a SayBye to the IPerson interface
//but as a compile time artifact, it will not take into account if the implementing class has "overriden" it
IPerson p1 = new Person("Iyan");
Console.WriteLine(p1.SayBye()); //writes "says Bye from Extension Method"
Person p2 = p1 as Person;
Console.WriteLine(p2.SayBye()); //writes "says Bye"
}
}
|
In the example, extending the IPerson interface with SayBye using IPersonExtensions doesn’t allow the Person class to effectively override it. When invoked via IPerson, the Extension Method’s implementation takes precedence.
Furthermore, Extension Methods are not visible through reflection; Type.GetMethodInfos() won’t list them. This impacts their usability with dynamic as the reflection-based resolution is not applicable. You can find further details on this at here and here.
These limitations led to simulating “Multiple Inheritance of Behaviour” in C#. This involves creating a class for each interface that needs behavioral extension. This class implements the interface and houses the “default methods.” Classes implementing the interface then hold a reference to this class and can choose to delegate to or override the default implementations.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
| public interface IValidable
{
bool AmIValid();
}
public interface IPersistable
{
string Persist();
int EstimateTimeForFullPersist();
}
public class DefaultValidable: IValidable
{
//just one single method, no calls to other methods in the class, so no need for an Implementer field
public bool AmIValid()
{
return this.GetType().GetProperties(BindingFlags.Public|BindingFlags.Instance).All(PropertyInfo => PropertyInfo.GetValue(this) != null);
}
}
public class DefaultPersistable: IPersistable
{
public IPersistable Implementer { get; set; }
public DefaultPersistable()
{
this.Implementer = this;
}
public string Persist()
{
//notice how we have to use \[this.Implementer.Estimate\] here to allow method overriding to work,
//cause using \[this.Estimate\] would invoke the Default (NotImplementedException) one.
if (this.Implementer.EstimateTimeForFullPersist() > 1500)
return this.ToString();
else
{
//complex logic here
return "this is the result of a complex logic";
}
}
public int EstimateTimeForFullPersist()
{
throw new NotImplementedException();
}
}
public class Book: IValidable, IPersistable
{
protected IValidable ValidableImplementation { get; set; }
protected IPersistable PersistableImplementation { get; set; }
public Book(DefaultValidable validableImp, DefaultPersistable persistableImp)
{
this.ValidableImplementation = validableImp;
this.PersistableImplementation = persistableImp;
}
public bool AmIValid()
{
//delegate to default implementation
return this.ValidableImplementation.AmIValid();
}
public string Persist()
{
//delegate to default implementation
return this.PersistableImplementation.Persist();
}
public int EstimateTimeForFullPersist()
{
//do not delegate to default implementation, "override" it
return 50;
}
}
public class Program
{
public static void Main()
{
DefaultPersistable defPersistable = new DefaultPersistable();
Book b = new Book(new DefaultValidable(), defPersistable);
defPersistable.Implementer = b;
Console.WriteLine("Is the Book valid: " + b.AmIValid().ToString());
Console.WriteLine("Book.Persist: " + b.Persist());
}
}
|
Notice the difference in implementation between DefaultValidable and DefaultPersistable. DefaultPersistable requires the use of the Implementer reference for internal method calls to ensure overridden methods are invoked correctly.
While this approach mimics “Multiple Inheritance of Behavior”, it doesn’t achieve the primary goal of Java’s Default Methods: extending existing interface contracts without breaking existing code. C# still relies on Extension Methods for this.
This discussion reminds me of the article on an interesting post about using ES6 proxies for multiple inheritance in JavaScript. However, it has a drawback: the instanceof operator wouldn’t work with “base classes” as intended unless instanceof itself became interceptable within the proxy, which, according to seems like (at least in the current proposal) it’s not, is not the case.
On a related note, you might find this article about Extension Methods, Mixins, and Traits relevant: my write up.