Calling C# interface default method from implementing class
Asked Answered
D

8

39

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.

Dorseydorsiferous answered 2/9, 2019 at 19:22 Comment(2)
Take a look at this: learn.microsoft.com/en-us/dotnet/csharp/programming-guide/…Rush
Related: #62334732Swedenborgian
D
37

See the documentation at "Upgrade with default interface methods":

That cast from SampleCustomer to ICustomer is necessary. The SampleCustomer class doesn't need to provide an implementation for ComputeLoyaltyDiscount; that's provided by the ICustomer interface. However, the SampleCustomer 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");
    }
}
Dumont answered 2/9, 2019 at 19:45 Comment(6)
any idea why is this ? that is why class doesn't inherit members from its interfaces ?Es
@Es Imagine if the class implements more than one interface and two interfaces have default implementations of a method with the same name. Which one would get called? This is the problem with multiple inheritance that c# is trying to avoid.Reprove
As long as there is only one implementation, the compiler could be satisfied with that. It should be known at the time of compiling? Error is fine as soon as there are multiple implementations.Dorseydorsiferous
You can load/create additional interfaces at runtime.Dumont
@Es - It's a language defect, IMO. Java allows this and it's very useful. Interfaces are disambiguated when necessary by forcing an override to choose: public interface A { default void x() { ... } } public interface B { default void x() { ... } } public class C implements A, B { @Override void x() { A.super.x(); } Really worth it as you can implement traits and mixins if this is possible.Manzanilla
Using IInterfaceName.MethodName() to select the specific interface method to call is one of the Java features I miss most in C#. Its omission is a little weird since the language does allow you to qualify which interface method to override, see e.g. IEnumerable.GetEnumerator().Swedenborgian
I
12

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.

Izettaizhevsk answered 2/9, 2019 at 22:38 Comment(6)
It's not an abuse, it's a core use case - traits. The OP's code comes from this blog post by Mads Torgersen. That logging code is not an internal implementation, it's a trait provided to the class. The code is actually external to the class and can't communicate with it except through interface methods - that's why abstract methods are thereAverroism
In the past people attempted to add eg logging or undoing to a class with code like MyClass : Loggable<MyClass> or MyClass:Undoable<MyClass>, composition/delegation, or through the use of reflection. Needless to say, the first attempt abuses inheritance, delegation, adds a lot of complexity and reflection is too slow.Averroism
Microsoft states: "Default interface members also enable scenarios similar to a "traits" language feature." - I don't think it feels like abuse! Just implementing the Log method in the class is no option for me. Other than in this simple example, the ILogging interface contains lots of methods which I want to use within many classes.Dorseydorsiferous
@PanagiotisKanavos - That's why I qualify everything and try to avoid absolute statements. I'm going to struggle with this one. Just so I can get my head around this - are you saying that a core use case was for a class to internally cast itself as an interface it implements so that it can call a method it doesn't implement of an interface it partially implements? I'm having a hard time with that, but I can get over it.Izettaizhevsk
Not sure if you were implying this, but I have to state that default interface methods are a mess. Microsoft shipped them too soon and they are a half-baked idea that are barely useful in current form.Sibert
I haven't had a use for them, although I once answered a question where they were what someone needed. My particular concern was for this weird use case where a class implements an interface and then casts itself as that interface so it can call the static method. That's some weird stuff.Izettaizhevsk
P
9

In CLR all interface member implementations are explicit, so in your code Log will be available in instances of ILoggable only, like it's recommended to do here:

((ILoggable)this).Log("Using injected logging")
Permeate answered 2/9, 2019 at 19:45 Comment(0)
R
6

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:

  1. Declare the default method as static. Don't worry, you will still be able to override it in a class that inherits from it.
  2. Call the default method using the same type of syntax when calling a static method of a class, only substitute the interface name for the class name.

See this answer for a code example, along with an alternative way of calling a default interface method.

Rigsdaler answered 13/7, 2021 at 4:51 Comment(1)
yes it was strange isn't it? implementing class has an interface method implemented, and you are never again able to call the default interface method :|Dram
E
4

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.

Eadmund answered 2/9, 2019 at 19:42 Comment(0)
O
3

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.

  • this still allows for Log to be overwritten in MyClass and the override to be called
  • essentially just syntactic sugar, shortening ((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:

  • this will make the method available on 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
  • if you want to use 10 different methods from ILog in your class, you probably don't want to implement them all.
  • on the flipside, there are many scenarios where this is the "natural" / intended approach, primarily when MyClass extends the interface method with some custom logic (like ((ILog)this).Log("(MyClass): " + message) )
Opuscule answered 16/8, 2021 at 11:12 Comment(2)
Unfortunately, the first solution causes a stack overflow when Log is called, because it will call itself. If you implement an interface method explicitly in the class, it is then called instead of the default implementation (and casting the instance to ILog doesn't change that).Salad
Robert is correct, I updated the answer.Opuscule
D
1

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(...).

Dorseydorsiferous answered 2/9, 2019 at 20:5 Comment(1)
This introduces a bug - if MyClass implements Log, the extension method will still call the default implementation.Averroism
I
0

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
    };
}
Internationale answered 15/6, 2020 at 6:36 Comment(5)
This was exactly what i was trying to figure out. Did C# require me to use an abstract class like Java, or could this be done directly in the interface like Python.Tyro
Blindingly following this example, but I wonder by you need to add 'public' before the abstract method in the abstract class?Tyro
@DanCiborowski-MSFT because it won't build :) Abstract method cannot be privateInternationale
Turns out "protected" was the option I was really looking for!Tyro
This would be useless in one of the most needed scenarios of Interface usage in C# which is multiple inheritance. With such pattern, we're back where we started, i.e. stuck with singular inheritance :(Camouflage

© 2022 - 2024 — McMap. All rights reserved.