What is default interface implementation
From C# 8.0 we can use methods in interfaces with concrete implementations. The reason for this change was primarily to enable API authors to add methods to already existing interfaces, without breaking source or binary compatibility with existing implementations of that interface. A class that implements that interface doesn’t need to provide implementation for a method that has a concrete implementation in that interface. Obviously if the author of the class wants, he can provide an implementation and that method will override the implementation of the interface.
There is a problem though in comparison with a virtual method in a class. The class that overrides the implemented method, cannot call the interface implementation and then add more functionality. It is an all or nothing deal, you either use the interface implementation or the class implementation, so this feature cannot substitute multiple inheritance. In Unity though, our gameobjects have to derive from the MonoBehaviour class and sometimes we may find the need to create gameobjects that share common functionality without using reflection. Let’s see a couple of ways that we can achieve this, now that Unity has at last support for default interface methods.
How to use default interface implementation to define shared functionality in Unity
From version 2021.2, Unity has added support for default implementation of methods in interfaces, this can provide common functionality in our MonoBehaviours or even shared functionality that derives from the interface, by using a couple of tricks. Let’s see two different ways that we can achieve that. For example, how to add a Debug.log that prints a common message to the console from the interface and a Debug.log that prints another message to the console from the MonoBehaviour, by calling just one method. Something like this:
Define common behavior in interface first method
With the addition of the default implementation methods in interfaces, now we can have the protected access modifier in a method in our interface. This modifier allows the class that implements that interface to override that method, but it doesn’t allow that method to be called from an external class. This can be done by creating an interface like this:
public interface IMessage
{
void Say()
{
Debug.Log("Hello from Interface");
SayAgain();
}
protected void SayAgain();
}
and then using it like this:
public class DefaultImplementation : MonoBehaviour, IMessage
{
void Start() => (this as IMessage).Say();
void IMessage.SayAgain() => Debug.Log("Hello from MonoBehaviour!");
}
or like this:
public class DefaultImplementation : MonoBehaviour, IMessage
{
void Start()
{
IMessage iMessage = this;
iMessage.Say();
}
void IMessage.SayAgain() => Debug.Log("Hello from MonoBehaviour!");
}
Here is an explanation of what is happening.
By NOT implementing the Say()
method of our interface, any IMessage
object will use the default implementation, that calls the SayAgain()
method. The SayAgain()
method doesn’t have a default implementation so it has to be implemented in our DefaultImplementation
class, but because it has the protected modifier it can only be used by classes that derive from the IMessage interface.
If we have MonoBehaviours that we want to have shared functionality like the Debug.Log("Hello from Interface")
statement, we create an interface that has a public default implementation method that has that functionality and in the end calls a protected not implemented method defined in the same interface. Afterwards any class that implements that interface, has to implement only that protected method explicitly. In our example the void IMessage.SayAgain()
method has the unique functionality that we want which is the Debug.Log("Hello from MonoBehaviour!")
statement.
Obviously that works and in non MonoBehaviour classes:
public class DefaultImplementation : MonoBehaviour
{
private IMessage _message1 = new Message1();
private IMessage _message2 = new Message2();
void Start()
{
_message1.Say();
Debug.Log("--------");
_message2.Say();
}
}
public interface IMessage
{
void Say()
{
Debug.Log("Hello from Interface");
SayAgain();
}
protected void SayAgain();
}
public class Message1 : IMessage
{
void IMessage.SayAgain() => Debug.Log("Hello from class Message1");
}
public class Message2 : IMessage
{
void IMessage.SayAgain() => Debug.Log("Hello from class Message2");
}
The above will log to the console:
Hello from Interface
Hello from class Message1
--------
Hello from Interface
Hello from class Message2
The only thing to remember here, is that the default implemented method in the interface, SHOULD NOT be implemented in the classes that implement the IMessage
interface.
Define common behavior in interface second method
Another way to achieve the same result is the following:
1) We declare the interface with a default implemented method that has the shared functionality that we want like this:
public interface IMessage2
{
void Say() => Debug.Log("Hello from Interface");
}
2) Then we create a private class that derives from the IMessage2
interface inside our MonoBehaviour that also derives from the IMessage2
interface. The private class doesn’t override the default implemented method, but our MonoBehaviour does. In this implementation we call the private’s class method first, that will execute the shared functionality and then we add our own unique for the class functionality. This might seem complicated but it actually isn’t. Here is the code:
public class DefaultImplementation2 : MonoBehaviour, IMessage2
{
private class Message2Default : IMessage2 { }
private static readonly IMessage2 _defaultBehaviour = new Message2Default();
void Start() => Say();
public void Say()
{
_defaultBehaviour.Say();
Debug.Log("Hello from MonoBehaviour!");
}
}
Of course this method too, is not only for MonoBehaviours:
public class DefaultImplementation2 : MonoBehaviour
{
private IMessage2 _message1 = new Message1();
private IMessage2 _message2 = new Message2();
void Start()
{
_message1.Say();
Debug.Log("--------");
_message2.Say();
}
}
public interface IMessage2
{
void Say() => Debug.Log("Hello from Interface");
}
public class Message1 : IMessage2
{
private class Message2Default : IMessage2 { }
private static readonly IMessage2 _defaultBehaviour = new Message2Default();
public void Say()
{
_defaultBehaviour.Say();
Debug.Log("Hello from class Message1");
}
}
public class Message2 : IMessage2
{
private class Message2Default : IMessage2 { }
private static readonly IMessage2 _defaultBehaviour = new Message2Default();
public void Say()
{
_defaultBehaviour.Say();
Debug.Log("Hello from class Message2");
}
}
the above code will also log to the console:
Hello from Interface
Hello from class Message1
--------
Hello from Interface
Hello from class Message2
The first method has the disadvantage as i mentioned that we have to remember to NOT override the default implemented method. This method doesn’t share this problem, but we have to remember to write all that boilerplate code that creates a private empty class in each of our classes that derive from our interface and calls that class default implemented method in our implementation of the interface.
Architectural concerns (or why you shouldn’t be doing this)
The above two methods seem a little “hacky” and actually they are. Although all the above is valid C# code, from an architectural point of view there are some serious concerns. No matter the way, coupling a concrete class with an interface not only by inheritance but also by composition creates a strong coupling that feels unnatural.
The real problem is that interfaces are not abstract classes and should not be used to define the implementation details of a class. Interfaces exist to define behavior and leave the implementation details to each class that implements them. This feature was made to help with API compatibility, but using it like this, the behavior of a class can change even if we don’t make any changes to that class and only change the interface. Someone who reads our code, should be able to understand how a class implements a functionality, by looking at that class only and not having to look the code inside the interface that the class implements.
A better way to achieve the above would be to create a class DefaultBehaviour and use it as a parameter to our method:
public class DefaultImplementation3 : MonoBehaviour, IMessage3
{
DefaultBehaviour _defaultBehaviour = new DefaultBehaviour();
void Start() => Say(_defaultBehaviour);
public void Say(DefaultBehaviour defaultBehaviour)
{
defaultBehaviour.Execute();
Debug.Log("Hello from MonoBehaviour!");
}
}
public interface IMessage3
{
void Say(DefaultBehaviour defaultBehaviour);
}
public class DefaultBehaviour
{
public void Execute() => Debug.Log("Hello from Interface");
}
Still there might be some edge case that we have a MonoBehaviour and we want to have some kind of multiple inheritance while not being able to use composition, so that we can share common functionality between the classes that implement our interface while not having duplicate code. If performance is an issue and reflection cannot be used for this, then the above two methods may be useful even if a little (or more than a little) hacky, by using a feature not the way it is supposed to be used.
As always thank you for reading this article and if you have any questions or comments use the comments section, or contact me directly via the contact form or email, also if you don’t want to miss any of the new articles, you can always subscribe to my newsletter or the RSS feed.