17

Here is a my code inside a c# project that targets .NET Core 3.0 (so I should be in C# 8.0) with Visual Studio 2019 (16.3.9)

public interface IJsonAble
{
    public string ToJson() => System.Text.Json.JsonSerializer.Serialize(this);
}

public class SumRequest : IJsonAble
{
    public int X { get; set; }

    public int Y { get; set; }

    public void Tmp()
    {
        new SumRequest().ToJson(); //compile error
    }
}

The compile error is:

CS1061 'SumRequest' does not contain a definition for 'ToJson' and no accessible extension method 'ToJson' accepting a first argument of type 'SumRequest' could be found (are you missing a using directive or an assembly reference?)

Can someone shed some light on this behavior ?

DavidG
  • 113,891
  • 12
  • 217
  • 223
Christophe Blin
  • 1,687
  • 1
  • 22
  • 40
  • 4
    Even with C# 8.0, the class doesn't inherit the implementation from the interface. You have to cast to the interface type to use the default implementation. – Mihaeru Nov 20 '19 at 09:36
  • Although C# 8 allows concrete method implementation in interfaces (https://stackoverflow.com/a/45912519/3759822), I still don't think is is a good idea OOP-wise, I suggest you change `interface IJsonAble` to `abtract class AbstractJsonAble`. I know this comment isn't directly related to your problem it may be helpful to keep in mind. – Fourat Nov 20 '19 at 10:59
  • 5
    Ran into the same issue, this is mental! I mean the entire purpose of default impl. on interfaces is that we no longer have to add common functionality as extensions on our own interfaces. If this should be even close to making any sense then neither regular interface methods nor extensions for interfaces should be seen through a reference to an implementing type. I would really love to see the reasoning behind this decision. – jool Apr 07 '20 at 11:24
  • 1
    @jool reason is "it allows you to add to the interface without worrying about the downstream consequences" (see accepted answer) – Christophe Blin Jun 09 '20 at 11:23
  • 1
    @jool Strongly agreed. The term "default" implies that it may be optionally overridden by the implementing class. To find out that you must first cast it as an interface type boggles my mind. This could have been a simple and immensely useful feature. – Syndog Aug 12 '22 at 19:22

2 Answers2

22

Methods are only available on the interface, not the class. So you can do this instead:

IJsonAble request = new SumRequest()
var result = request.ToJson();

Or:

((IJsonAble)new SumRequest()).ToJson();

The reason for this is it allows you to add to the interface without worrying about the downstream consequences. For example, the ToJson method may already exist in the SumRequest class, which would you expect to be called?

DavidG
  • 113,891
  • 12
  • 217
  • 223
  • 4
    > "...which would you expect to be called?" The class-implemented one. If the class doesn't implement one, then the default method defined in the interface should be called. It's not hard to reason through. This news is sorely disappointing. – Syndog Aug 12 '22 at 19:19
  • @Syndog What if there are two interfaces implemented that later add ToJson? There are a lot of weird cases that can eventuality come up, I'm guessing the reasoning is similar to why C# does not allow multiple inheritance. At least c++ made it cleaner to specify which parent method you're interested in. – Frank Schwieterman Oct 31 '22 at 18:45
  • @FrankSchwieterman > "What if there are two interfaces implemented that later add ToJson?" Then they both get called in sequence based on the order they're listed in the class declaration. – Syndog Nov 17 '22 at 15:27
  • Calling both in sequence is a disaster. But in those cases the compiler could have forced the inheriting class to specify which one it is calling (or implement its own). This feature is useless in its current state – Dante Marshal Dec 29 '22 at 11:24
1

Try using (new SumRequest() as IJsonAble).ToJson(); to help the compiler a bit.

Anyway, I'm sure what you're after is (this as IJsonAble).ToJson(), assuming you want to apply ToJson on current SumRequest instance.

Cosmin Sontu
  • 1,034
  • 11
  • 16
  • 3
    I wouldn't recommend using `as` here. If the object doesn't implement `IJsonAble` then you will get a null reference exception. If you are sure the type does implement it, then use a hard cast. – DavidG Nov 20 '19 at 09:42
  • 1
    Given this is c# 8.0, if you enable Nullable reference types feature, you get a compiler warning in case you stop implementing IJsonAble. Using a hard cast is advisable, specially when using older versions of C#, but doesn't change the essence of the answer much. – Cosmin Sontu Nov 20 '19 at 10:44
  • 2
    Note: When using a hard cast with C# 8.0, it supresses the "Dereference of a possible null reference" warning that you get when using "as" (for the case when you stop implementing IJsonAble interface). I prefer compile time warnings to runtime exceptions. – Cosmin Sontu Nov 20 '19 at 10:50