Reputation: 3498
C# 8 supports default method implementations in interfaces. My idea was to inject a logging method into classes like this:
public interface ILoggable {
void Log(string message) => DoSomethingWith(message);
}
public class MyClass : ILoggable {
void MyMethod() {
Log("Using injected logging"); // COMPILER ERROR
}
}
I get a compiler error: "The name does not exist in the current context"
Is it impossible to use default method implementations in this way?
EDIT:
For the correct response regarding C# rules, see the accepted answer. For a more concise solution (the original idea of my question!) see my own answer below.
Upvotes: 43
Views: 16451
Reputation: 2184
As an addendum to the great workaround in the answer from @enzi, here is a version for an interface that itself uses generics:
public interface ILoggable<TItem> {
void Log(TItem message) => DoSomethingWith(message);
}
public static class LogExtensions {
/// <remarks>Allows calling of method without cast to the interface type
/// while still allowing custom implementations.</remarks>
public static void Log<TInterface, TItem>(this TInterface logger, TItem message)
where TInterface : ILoggable<TItem> => logger.Log(message);
}
Upvotes: -1
Reputation: 4929
See the documentation at "Upgrade with default interface methods":
That cast from
SampleCustomer
toICustomer
is necessary. TheSampleCustomer
class doesn't need to provide an implementation forComputeLoyaltyDiscount
; that's provided by theICustomer
interface. However, theSampleCustomer
class doesn't inherit members from its interfaces. That rule hasn't changed. In order to call any method declared and implemented in the interface, the variable must be the type of the interface,ICustomer
in this example.
So the method needs to be something like:
public class MyClass : ILoggable {
void MyMethod() {
ILoggable loggable = this;
loggable.Log("Using injected logging");
}
}
or:
public class MyClass : ILoggable {
void MyMethod() {
((ILoggable)this).Log("Using injected logging");
}
}
Upvotes: 41
Reputation: 4165
Here is an alternative solution to the ones already suggested:
Extension methods:
public static class LogExtensions
{
public static void Log<T>(this T logger, string message) where T : ILoggable => logger.Log(message);
}
public class MyClass : ILoggable {
void MyMethod() {
this.Log("Using injected logging");
}
}
This might be useful when ILoggable
contains many methods / is implemented in many classes.
Log
to be overwritten in MyClass
and the override to be called((ILoggable)this)
to this
Incorrect alternative (see comment)
simply implement the interface method
public class MyClass : ILoggable {
void MyMethod() {
Log("Using injected logging");
}
public void Log(string message) => ((ILog)this).Log(message);
}
This allows the method to be called directly, without having to write the cast to ILog
each time.
Things to note:
MyClass
to outside users of it as well, where previously it was only available when an instance of MyClass
is cast to / used as ILog
ILog
in your class, you probably don't want to implement them all.MyClass
extends the interface method with some custom logic (like ((ILog)this).Log("(MyClass): " + message)
)Upvotes: 4
Reputation: 3837
The problem with the answers that cast the class to an interface is that it may or may not call the default interface method, depending on whether or not the class has implemented a method to override the default method.
So this code:
((ILoggable)this).Log(...)
ends up calling the default interface method, but only if there is no interface method defined in the class that overrides the default method.
If there is a method in the class that overrides the default method, then that is the method that will be called. This is usually the desired behavior. But, if you always want to call the default method, regardless of whether or not the implementing class has implemented its own version of that interface method, then you have a couple of options. One way is to:
See this answer for a code example, along with an alternative way of calling a default interface method.
Upvotes: 6
Reputation: 3812
From reading an article about these default methods, I think you should try to upcast it to the interface:
((ILoggable)this).Log("Using injected logging")
I haven't checked it, just my thought according to this article.
Upvotes: 4
Reputation: 872
My solution is adding new abstract class between interface and it's implementations:
public interface ILoggable {
void Log(string message);
void SomeOtherInterfaceMethod();
}
public abstract class Loggable : ILoggable {
void Log(string message) => DoSomethingWith(message);
public abstract void SomeOtherInterfaceMethod(); // Still not implemented
}
public class MyClass : Loggable {
void MyMethod() {
Log("Using injected logging"); // No ERROR
}
public override void SomeOtherInterfaceMethod(){ // override modifier needed
// implementation
};
}
Upvotes: -1
Reputation: 29217
If you want to avoid clutter and repetitive casting you can add a single property which casts the type as the interface:
public class MyClass : ILoggable
{
ILoggable AsILoggable => (ILoggable)this;
void MyMethod()
{
AsILoggable.Log("Using injected logging");
}
}
But this is off. It seems wrong, regardless of how it's done. From the documentation:
The most common scenario is to safely add members to an interface already released and used by innumerable clients.
When there was some concern about having implementations in interfaces - which previously had none - this was the sentence that made sense of it. It's a way to add to an interface without breaking classes that already implement it.
But this question implies that we are modifying the class to reflect a change to an interface it implements. It's the exact opposite of the stated use case for this language feature.
If we're already modifying the class, why not just implement the method?
public void Log(string message) => DoSomethingWith(message);
When we add a default interface implementation, we provide an implementation to consumers of the interface - classes that depend on an abstraction.
If we depend on the default interface implementation from within the class that implements the interface, then a change to the interface becomes, in effect, a change to the internal implementation of the class. That's not what an interface is for. An interface represents external-facing behavior, not internal implementation.
It's as if the class is stepping outside of itself, looking back in at itself as an external consumer, and using that as part of its internal implementation. The class doesn't implement the interface, but it depends on it. That's weird.
I won't go so far as to say that it's wrong, but it feels like an abuse of the feature.
Upvotes: 14
Reputation: 3498
The accepted answer and the other responses are correct.
However, what I wanted is a concise call of the Log
method.
I achieved that with an extension method on the ILoggable
interface:
public static class ILoggableUtils { // For extension methods on ILoggable
public static void Log(this ILoggable instance, string message) {
DoSomethingWith(message, instance.SomePropertyOfILoggable);
}
}
In this way, I can at least call this.Log(...);
in my class instead of the ugly ((ILoggable)this).Log(...)
.
Upvotes: 0