从实现类调用C#接口默认方法

And*_*ndi 6 c# c#-8.0 default-interface-member

C#8支持接口中的默认方法实现。我的想法是将一个日志记录方法注入这样的类中:

public interface ILoggable {
    void Log(string message) => DoSomethingWith(message);
}

public class MyClass : ILoggable {
    void MyMethod() {
        Log("Using injected logging"); // COMPILER ERROR
    }
}
Run Code Online (Sandbox Code Playgroud)

我收到一个编译器错误:“名称在当前上下文中不存在”

以这种方式使用默认方法实现是不可能的吗?

编辑:

有关C#规则的正确答案,请参见接受的答案。有关更简洁的解决方案(我的问题的初衷!),请参阅下面的答案

Bur*_*sBA 7

请参阅https://docs.microsoft.com/zh-cn/dotnet/csharp/tutorials/default-interface-members-versions中的文档

SampleCustomer到的转换ICustomer是必要的。本SampleCustomer类并不需要提供一个实现ComputeLoyaltyDiscount; 由ICustomer界面提供。但是,SampleCustomer该类不会从其接口继承成员。该规则没有改变。为了调用在接口中声明和实现的任何方法,ICustomer在此示例中,变量必须是接口的类型。

所以方法就像

public class MyClass : ILoggable {
    void MyMethod() {
        ILoggable loggable = this;
        loggable.Log("Using injected logging");
    }
}
Run Code Online (Sandbox Code Playgroud)

  • 知道_为什么_是这样吗?这就是为什么类不从其接口继承成员的原因? (3认同)
  • @kofifus想象一下,如果该类实现多个接口,并且两个接口具有同名方法的默认实现。哪一个会被叫到?这是 c# 试图避免的[多重继承问题](/sf/ask/15815061/)。 (3认同)
  • 只要只有一种实现,编译器就可以满足。编译的时候应该就知道了?只要有多个实现,错误就可以了。 (3认同)
  • @kofifus - 这是一个语言缺陷,IMO。Java 允许这样做并且非常有用。必要时,可以通过强制覆盖选择以下方式来消除接口歧义: `public interface A { default void x() { ... } }` `public interface B { default void x() { ... } }` `public class C实现 A, B { @Override void x() { A.super.x(); }` 真的值得,因为如果可能的话,您可以实现特征和混合。 (3认同)

ing*_*var 6

在 CLR 中,所有接口成员实现都是显式的,因此在您的代码Log中将ILoggable仅在实例中可用,就像在这里推荐的那样:

((ILoggable)this).Log("Using injected logging")
Run Code Online (Sandbox Code Playgroud)


Sco*_*nen 6

如果您想避免混乱和重复转换,您可以添加一个将类型转换为接口的属性:

public class MyClass : ILoggable 
{
    ILoggable AsILoggable => (ILoggable)this;

    void MyMethod() 
    {
        AsILoggable.Log("Using injected logging"); 
    }
}
Run Code Online (Sandbox Code Playgroud)

但这是关闭的。这似乎是错误的,不管它是如何完成的。从文档:

最常见的场景是安全地将成员添加到已被无数客户端发布和使用的接口中。

当有人担心在接口中实现实现时 - 以前没有 - 这就是有意义的句子。这是一种在不破坏已经实现它的类的情况下添加到接口的方法。

但是这个问题意味着我们正在修改类以反映对其实现的接口的更改。它此语言功能的所述用例完全相反

如果我们已经在修改类,为什么不直接实现该方法呢?

public void Log(string message) => DoSomethingWith(message);
Run Code Online (Sandbox Code Playgroud)

当我们添加默认接口实现时,我们为接口的使用者提供了一个实现——依赖于抽象的类。

如果我们依赖于实现接口的类内部的默认接口实现,那么对接口的更改实际上变成了对类内部实现的更改。那不是接口的用途。接口代表面向外部的行为,而不是内部实现。

就好像这个类走出了自身,将自身视为外部消费者,并将其用作其内部实现的一部分。类不实现接口,但它依赖于它。这很奇怪。

我不会说这是错误的,但感觉就像是对该功能的滥用。

  • [Microsoft](https://learn.microsoft.com/de-de/dotnet/csharp/whats-new/csharp-8) 指出:“默认接口成员还支持类似于“traits”语言功能的场景。” - 我不认为这感觉像是虐待!仅仅在类中实现“Log”方法对我来说是没有选择的。除了这个简单的示例之外,“ILogging”接口包含许多我想在许多类中使用的方法。 (4认同)
  • 这不是滥用,而是核心用例——特征。OP 的代码来自 Mads Torgersen 的[这篇博客文章](https://devblogs.microsoft.com/dotnet/default-implementations-in-interfaces/)。该日志记录代码“不是”内部实现,而是提供给类的特征。代码实际上是类的“外部”,除了通过接口方法之外无法与其通信 - 这就是抽象方法存在的原因 (3认同)

Bob*_*yan 6

将类强制转换为接口的答案的问题在于,它可能会也可能不会调用默认接口方法,具体取决于该类是否实现了覆盖默认方法的方法。

所以这段代码:

((ILoggable)this).Log(...)
Run Code Online (Sandbox Code Playgroud)

最终会调用默认接口方法,但前提是类中没有定义覆盖默认方法的接口方法。

如果类中有一个方法覆盖了默认方法,那么该方法就会被调用。这通常是所需的行为。但是,如果您总是想调用默认方法,无论实现类是否已实现该接口方法的自己版本,那么您有几个选择。一种方法是:

  1. 将默认方法声明为静态。不用担心,您仍然可以在继承它的类中重写它。
  2. 调用类的静态方法时,使用相同类型的语法调用默认方法,只需将接口名称替换为类名称。

请参阅此答案的代码示例,以及调用默认接口方法的替代方法。