20

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

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

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

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

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

编辑:

有关 C# 规则的正确响应,请参阅接受的答案。有关更简洁的解决方案(我的问题的原始想法!),请参阅下面的我自己的答案

4

8 回答 8

17

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

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

所以方法类似于

public class MyClass : ILoggable {
    void MyMethod() {
        ILoggable loggable = this;
        loggable.Log("Using injected logging");
    }
}
于 2019-09-02T19:45:01.533 回答
8

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

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

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

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

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

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

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

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

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

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

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

就好像该类正在超越自身,以外部消费者的身份回顾自身,并将其用作其内部实现的一部分。该类不实现接口,但它依赖于它。这很奇怪。

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

于 2019-09-02T22:38:10.483 回答
6

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

((ILoggable)this).Log("Using injected logging")
于 2019-09-02T19:45:58.343 回答
3

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

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

我没有检查它,只是我根据这篇文章的想法。

于 2019-09-02T19:42:59.423 回答
3

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

所以这段代码:

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

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

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

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

请参阅此答案以获取代码示例,以及调用默认接口方法的另一种方法。

于 2021-07-13T04:51:41.067 回答
1

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

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

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

于 2019-09-02T20:05:13.480 回答
1

以下是已经建议的两种替代解决方案:

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

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

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

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

注意事项:

  • 这将使该方法也可供MyClass它的外部用户使用,而以前它仅在将 的实例MyClass强制转换为 / 用作ILog
  • 如果你想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当包含许多方法/在许多类中实现时,这可能很有用。

  • 这仍然允许Log被覆盖MyClass并调用覆盖
  • 本质上只是语法糖,缩短((ILoggable)this)this
于 2021-08-16T11:12:55.430 回答
0

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

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
    };
}
于 2020-06-15T06:36:55.790 回答