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

Calling C# interface default method from implementing class

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

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

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

我收到一个编译器错误:"The name does not exist in the current context"

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

编辑:

有关 C# 规则的正确响应,请参阅下面的 . For a more concise solution (the original idea of my question!) see my

通过阅读有关这些默认方法的文章,我认为您应该尝试将其向上转换为接口:

((ILoggable)this).Log("Using injected logging")

我没查过,只是根据this article想的。

请参阅 https://docs.microsoft.com/en-us/dotnet/csharp/tutorials/default-interface-members-versions

处的文档

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.

所以方法类似于

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

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

((ILoggable)this).Log("Using injected logging")

接受的答案和其他答案都是正确的。 但是,我想要的是 Log 方法的简洁调用。 我通过 ILoggable 接口上的扩展方法实现了这一点:

public static class ILoggableUtils { // For extension methods on ILoggable
    public static void Log(this ILoggable instance, string message) {
         DoSomethingWith(message, instance.SomePropertyOfILoggable);
    }
}

这样,我至少可以在我的class中调用this.Log(...);而不是丑陋的((ILoggable)this).Log(...)

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

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

    void MyMethod() 
    {
        AsILoggable.Log("Using injected logging"); 
    }
}

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

The most common scenario is to safely add members to an interface already released and used by innumerable clients.

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

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

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

public void Log(string message) => DoSomethingWith(message);

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

如果我们依赖于 实现接口的 class 中的默认接口实现,那么对接口的更改实际上就是对class 的内部实现。这不是接口的用途。接口表示面向外部的行为,而不是内部实现。

就好像 class 正在走出自身,作为外部消费者回头审视自己,并将其用作其内部实现的一部分。 class 没有实现接口,但它依赖于它。真奇怪。

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

我的解决方案是在接口及其实现之间添加新的抽象 class:

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
    };
}

将 class 转换为接口的答案的问题是它可能调用也可能不调用默认接口方法,这取决于 class 是否实现了一个方法来覆盖默认方法。

所以这段代码:

((ILoggable)this).Log(...)

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

如果 class 中有一个方法覆盖了默认方法,那么它将被调用。这通常是所需的行为。但是,如果你总是想调用默认方法,不管实现 class 是否已经实现了它自己的接口方法版本,那么你有几个选择。一种方法是:

  1. 将默认方法声明为静态。别担心,您仍然可以在继承自它的 class 中覆盖它。
  2. 在调用class的静态方法时使用相同类型的语法调用默认方法,仅用接口名称替换class名称。

有关代码示例以及调用默认接口方法的替代方法,请参阅 this answer

这里有两个已经建议的替代解决方案:

首先是简单实现接口方法:

public class MyClass : ILoggable {
    void MyMethod() {
        Log("Using injected logging");
    }

    public void Log(string message) => ((ILog)this).Log(message);
}

这允许直接调用方法,而不必每次都将转换写入 ILog

注意事项:

  • 这将使该方法在 MyClass 上也可供其外部用户使用,而以前它仅在 MyClass 的实例被转换为/用作 [=12= 时才可用]
  • 如果您想在 class 中使用 ILog 中的 10 种不同方法,您可能不想全部实现它们。
  • 另一方面,在许多情况下这是“自然”/预期的方法,主要是当 MyClass 使用一些自定义逻辑(如 ((ILog)this).Log("(MyClass): " + message) )扩展接口方法时

其次是使用扩展方法:

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");
    }
}

ILoggable 包含许多方法/在许多 class 中实现时,这可能很有用。

  • 这仍然允许在 MyClass 中覆盖 Log 并调用覆盖
  • 本质上只是语法糖,将 ((ILoggable)this) 缩短为 this