【问题标题】:Calling C# interface default method from implementing class从实现类调用 C# 接口默认方法
【发布时间】:2019-09-02 19:22:49
【问题描述】:

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

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

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

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

这样就不能使用默认方法实现了吗?

编辑:

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

【问题讨论】:

标签: c# c#-8.0 default-interface-member


【解决方案1】:

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

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

所以方法类似于

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

【讨论】:

  • 任何想法为什么这是?这就是为什么类不从其接口继承成员的原因?
  • @kofifus 想象一下,如果该类实现了多个接口,并且两个接口具有同名方法的默认实现。哪个会被叫到?这是 c# 试图避免的 the problem with multiple inheritance
  • 只要只有一个实现,编译器就可以满足。应该在编译的时候就知道?只要有多个实现,错误就可以了。
【解决方案2】:

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

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

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

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

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

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

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

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

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

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

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

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

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

【讨论】:

  • 这不是滥用,而是核心用例 - 特征。 OP 的代码来自 Mads Torgersen 的 this blog post。该日志记录代码不是内部实现,它是提供给类的特征。代码实际上是类的外部,只能通过接口方法与它通信——这就是抽象方法存在的原因
  • 过去人们尝试使用MyClass : Loggable<MyClass>MyClass:Undoable<MyClass> 之类的代码、组合/委托或通过使用反射来添加例如日志记录或撤消到类。不用说,第一次尝试滥用继承、委托,增加了很多复杂性,反射也太慢了。
  • Microsoft 声明:“默认接口成员还启用类似于“特征”语言功能的场景。” - 我不认为这感觉像是虐待!仅在类中实现Log 方法对我来说是没有选择的。除了这个简单的例子,ILogging 接口包含很多我想在很多类中使用的方法。
  • @PanagiotisKanavos - 这就是我限定所有内容并尽量避免绝对陈述的原因。我要与这个斗争。只是为了让我明白这一点 - 你是说核心用例是一个类在内部将 itself 转换为它实现的接口,以便它可以调用它没有实现的方法它部分实现的接口?我很难过,但我可以克服它。
【解决方案3】:

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

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

【讨论】:

    【解决方案4】:

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

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

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

    【讨论】:

      【解决方案5】:

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

      所以这段代码:

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

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

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

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

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

      【讨论】:

        【解决方案6】:

        接受的答案和其他答案都是正确的。 但是,我想要的是对 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(...)

        【讨论】:

        • 这引入了一个bug——如果MyClass实现Log,扩展方法仍然会调用默认实现。
        【解决方案7】:

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

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

        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 包含许多方法/在许多类中实现时,这可能很有用。

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

        【讨论】:

          【解决方案8】:

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

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

          【讨论】:

            猜你喜欢
            • 2015-07-31
            • 1970-01-01
            • 2018-08-28
            • 2013-12-23
            • 2014-12-06
            • 2016-06-16
            • 2018-06-13
            • 2021-11-21
            • 1970-01-01
            相关资源
            最近更新 更多