【问题标题】:C# interface implementation with derived interface带有派生接口的 C# 接口实现
【发布时间】:2017-12-26 12:50:04
【问题描述】:

在下面的示例类中,“SomeClass”没有实现“ISomeInterface”。为什么我不能通过传递一个实现基本要求的更派生的接口来实现这一点。无论传递什么实例,它仍然会实现基础,我错过了什么吗?

namespace Test
{
    public interface IBaseInterface
    {
        void DoBaseStuff();
    }

    public interface IChildInterface : IBaseInterface
    {
        void DoChildStuff();
    }

    public interface ISomeInterface
    {
        void DoSomething(IBaseInterface baseInterface);
    }

    public class SomeClass : ISomeInterface
    {
        public void DoSomething(IChildInterface baseInterface)
        {
        }
    }
}

【问题讨论】:

    标签: c# .net inheritance interface


    【解决方案1】:

    如果你能做到这一点,那么你可以这样做:

    IAnimal cat = new Cat();
    IAnimalTrainer dogTrainer = new DogTrainer();
    dogTrainer.Train(cat);
    

    IAnimalTrainer 可以训练任何IAnimal。但是DogTrainer 只能训练Dogs。因此DogTrainer实现IAnimalTrainer接口是非法的。

    【讨论】:

      【解决方案2】:

      你应该改变一些接口来使用一些实现 IBaseInterface 的类型, 然后更改方法签名以使用您的 SomeClass 想要的任何孩子。

      public interface ISomeInterface<TSomeChild> where TSomeChild : IBaseInterface
      {
          void DoSomething(TSomeChild baseInterface);
      }
      
      public class SomeClass : ISomeInterface<IChildInterface>
      {
          public void DoSomething(IChildInterface baseInterface)
          {
          }
      }
      

      【讨论】:

        【解决方案3】:

        存在此限制是因为ISomeInterface 预计任何IBaseInterface 都将满足合同。也就是说,如果您有以下情况:

        public interface IBase {}
        public interface IChildA : IBase {}
        public interface IChildB : IBase {}
        

        还有一个需要IBase的接口:

        public interface IFoo { void Bar(IBase val); }
        

        然后根据需要将其限制在派生类中:

        public class Foo : IFoo { public void Bar(IChildA val) {} }
        

        会产生以下问题:

        IChildB something = new ChildB();
        IFoo something = new Foo();
        something.Bar(something); // This is an invalid call
        

        因此,您并没有执行您所说的合同。

        在这种情况下,您有两个简单选项:

        • IFoo 调整为泛型,并接受T,它是IBase 的派生:

          public interface IFoo<T> where T : IBase { void Bar(T val); }
          public class Foo : IFoo<IChildA> { public void Bar(IChildA val) {} }
          

          当然,这意味着Foo 不能再接受任何 IBase(包括IChildB)。

        • 调整Foo实现IFoo,为void Bar(IChildA val)增加一个实用方法:

          public class Foo : IFoo
          {
              public void Bar(IBase val) {}
              public void Bar(IChildA val) {}
          }
          

          这有一个有趣的副作用:当你调用((IFoo)foo).Bar 时,它会期待IBase,而当你调用foo.Bar 时,它会期待IChildA IBase。这意味着它满足合同,同时还具有您的派生接口特定方法。如果你想更多地“隐藏”Bar(IBase) 方法,你可以显式地实现IFoo

          void IFoo.Bar(IBase val) { }
          

          这会在您的代码中产生甚至不一致的行为,因为现在((IFoo)foo).Barfoo.Bar 完全不同,但我将决定权留给您。

          这意味着,对于本节中的第二个版本,foo.Bar(new ChildB()); 现在无效,因为IChildB 不是 IChildA


        为什么我不能通过传递一个更派生的接口来实现这一点,该接口确实实现了基本要求。无论传递什么实例,它仍然会实现基础,我错过了什么吗?

        由于我上面提到的原因,这是不允许的,IFoo.Bar 期望 any IBase,而您希望进一步将类型限制为 IChildA ,这不是IBase的超接口,即使是这样也不会被允许,因为它违反了接口实现,尽管你可以更容易定义那时的第二种方法可以满足您的需求。

        请记住,当您实现 interface 时,您订阅了一个合同,而 C#不会让您违反该合同。

        【讨论】:

        • 很好的解释,感谢您的解决方法!
        【解决方案4】:

        来自MSDN

        当类或结构实现接口时,该类或结构必须为接口定义的所有成员提供实现

        这个方法在派生

        void DoSomething(IChildInterface baseInterface)
        

        与界面中的签名不同:

        void DoSomething(IBaseInterface baseInterface)
        

        IChildInterfaceIBaseInterface 不是同一类型。因此,您的派生类没有实现接口的所有方法,并且您会收到编译错误。

        对于将其作为限制而不是编译器理解继承的可能背后的逻辑,请参见 Liskov 的替换原则,如 SLakes 回答中的

        【讨论】:

          【解决方案5】:

          这违反了Liskov substitution principle

          ISomeInterface 保证可以使用 any IBaseInterface 实例调用该方法。您的实现不能将其限制为仅接受 IChildInterface 接口。

          【讨论】:

          • 虽然以更一般的方式这是正确的,但我认为这里的实际原因是 C# 编译器只需要一对一的实现。如果情况相反(即接口需要子接口,实现允许基接口),那么它不会违反 Liskov,但这也不合法。
          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 2010-09-22
          • 2010-12-12
          • 1970-01-01
          • 1970-01-01
          • 2014-06-17
          • 2019-04-16
          • 1970-01-01
          相关资源
          最近更新 更多