接口中的默认实现未被编译器识别?

17

这是我的一个C#项目中的代码,目标是 .NET Core 3.0(因此我应该在 C# 8.0 中编写)。我使用的是 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
    }
}

编译错误为:

CS1061 'SumRequest' 不包含名称为 'ToJson' 的定义,且没有找到接受类型为 'SumRequest' 的第一个参数的可访问扩展方法 'ToJson'(是否缺少 using 指令或程序集引用?)

有人能解释一下这种行为吗?


5
即使使用 C# 8.0,类也不会继承接口的实现。你需要将其转换为接口类型以使用默认实现。 - Mihaeru
1
尽管C# 8允许在接口中实现具体方法(https://dev59.com/KF0a5IYBdhLWcg3wboXU#45912519),但我仍然认为这不是面向对象编程的好主意,建议您将“interface IJsonAble”更改为“abstract class AbstractJsonAble”。我知道这个评论与您的问题没有直接关系,但记住可能会有所帮助。 - Fourat
6
遇到了相同的问题,这真是令人抓狂!我的意思是,接口默认实现的整个目的在于我们不再需要将常见功能作为扩展添加到自己的接口中。如果这应该变得有任何意义,那么既不应该通过实现类型的引用来查看普通接口方法,也不应该通过实现类型的引用来查看接口的扩展。我真的很想看到这个决定背后的原因。 - jool
1
@jool的理由是:“它允许您在不担心下游后果的情况下添加到接口”(请参见已接受的答案)。 - Christophe Blin
1
@jool 强烈同意。术语“默认”意味着它可以被实现类选择性地覆盖。首先将其强制转换为接口类型才能找出这一点,这让我感到困惑。这本来可以是一个简单而极其有用的功能。 - Syndog
3个回答

24

方法只能在 接口 上使用,而不能在 上使用。所以你可以这样做:

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

或者:

((IJsonAble)new SumRequest()).ToJson();
这是因为它使您能够在不担心下游后果的情况下添加到界面。例如,ToJson方法可能已经存在于SumRequest类中,您希望调用哪个?

5
“你希望被称为哪个?” 类中实现的那个方法。如果类没有实现该方法,则应调用接口中定义的默认方法。这并不难理解。这则消息令人非常失望。 - Syndog
1
@Syndog 如果有两个实现了ToJson的接口该怎么办呢?可能最终会出现很多奇怪的情况,我猜想这种限制的理由与C#不允许多重继承的原因类似。至少在C++中,你可以更清晰地指定你感兴趣的父方法。 - Frank Schwieterman
如果有两个实现了ToJson方法的接口,那会怎样呢?它们将按照在类声明中列出的顺序依次被调用。 - Syndog
2
按顺序调用两者是一场灾难。但在这种情况下,编译器可以强制继承类指定要调用哪一个(或实现自己的调用)。目前该功能处于无用状态。 - Dante Marshal

1
尝试使用(new SumRequest() as IJsonAble).ToJson();来帮助编译器。
无论如何,我确定你想要的是(this as IJsonAble).ToJson(),假设你想在当前SumRequest实例上应用ToJson。

3
我不建议在这里使用 as。如果对象没有实现 IJsonAble,那么你将会得到一个空引用异常。如果你确定该类型确实实现了它,那么请使用硬转换。 - DavidG
1
考虑到这是C# 8.0,如果启用了可空引用类型功能,则在停止实现IJsonAble时会收到编译器警告。使用硬转换是明智的,特别是在使用旧版本的C#时,但并不会改变答案的本质。 - Cosmin Sontu
2
注意:在使用C# 8.0的硬转换时,它会抑制使用"as"时(当您停止实现IJsonAble接口的情况)出现的“可能的空引用解除引用”警告。我更喜欢编译时警告而不是运行时异常。 - Cosmin Sontu

0
有点冷门,但是如果你想隐式调用默认的方法实现而不必手动实现它,你可以为接口本身创建一个扩展方法。
public static class JsonAbleExtensions {
    public static string ToJson(this IJsonAble jsonAble)
    {
        return jsonAble.ToJson();
    }
}

这样可以避免在运行时使用显式强制转换或者强迫接口的用户在所有继承类中手动实现该方法。

这个方法可以,但它并没有回答问题,只是一个权宜之计。 - undefined

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接