【问题标题】:Why can't .Net / C# understand interface inheritance with properties of the same name?为什么 .Net / C# 不能理解具有同名属性的接口继承?
【发布时间】:2011-07-29 21:54:21
【问题描述】:

考虑以下类和接口:

    public interface A { string Property { get; set; } }

    public interface B { string Property { get; set; } }

    public interface C : A, B { }

    public class MyClass : C
    {
        public string Property { get; set; }
    }

看起来很简单,对吧?现在考虑以下程序:

    static void Main(string[] args)
    {
        MyClass myClass = new MyClass();
        myClass.Property = "Test";

        A aTest = myClass;
        B bTest = myClass;
        C cTest = myClass;

        aTest.Property = "aTest";
        System.Console.WriteLine(aTest.Property);
        bTest.Property = "bTest";
        System.Console.WriteLine(bTest.Property);
        cTest.Property = "cTest";
        System.Console.WriteLine(cTest.Property);
        System.Console.ReadKey();
    }

看起来不错,但它不会编译。它给了我一个歧义异常:

为什么 C# 不能解决这个问题?从架构的角度来看,我正在做的事情是不是很疯狂?我正在尝试理解为什么(我知道可以通过强制转换来解决)。

编辑

当我引入接口C时出现问题。当我使用MyClass : A, B 时,我一点问题都没有。

最终

刚刚完成了一篇关于该主题的博客:Interface Ambiguity and Implicit Implementation

【问题讨论】:

  • 期望它会调用哪个?
  • 这只是你在搞砸的东西,还是你设计的一部分?
  • @Nix 好吧...我们有一些接口存在这个问题。 A 和 B 是非常小的接口,而 C 在一个需要继承 A 和 B 的大接口中。
  • 如果接口A和B有相同的方法,为什么不从包含这些方法的接口继承呢?这将解决您的问题。
  • 一个更有趣的问题是,为什么即使 A 的属性是只读的而 B 的属性是只写的,您的代码也无法工作。看起来有一个只读属性和一个只写属性不应该使读取或写入模棱两可,但编译器会抱怨模棱两可。

标签: c# .net inheritance interface ambiguity


【解决方案1】:

简而言之,因为它确实模棱两可。

现在更详细的故事。正如您已经看到的那样,存在显式接口实现,因此您可以为 A.Property 和 B.Property 提供两种不同的实现,而当您只有 C 时,您无法判断实现是否相同。由于C#的“哲学”不是去猜测你的意思,而是让你在必要的时候说得更清楚,所以编译器不会选择A.Property或B.Property,而是报错。

【讨论】:

  • +1 终于有人回答了这个问题。此外,我认为混淆来自这样一个事实,即即使您没有明确实现接口,它仍然被认为是模棱两可的。
  • 几乎:这并不能真正解释为什么public class MyClass: A, B 有效而public class MyClass: C 无效。
  • @rsenna... 好吧... 如果 A.Property == B.Property... 应该有一个接口 X 需要由 A 和 B 实现以显示该属性来自同一件事。
  • @Kees:同样,public class MyClass: A, BWORKS。这意味着MyClass 类中的相同string Property { get; set; } 能够实现这两个接口,也意味着你错了。当我们添加一个第三个接口C,继承自接口AB时,问题就出现了。
  • @rsenna 我已经写完一篇关于这个主题的博文。请访问keestalkstech.blogspot.com/2011/04/…了解更多信息。
【解决方案2】:

你需要explicit interface implementation:

public interface A { string Property { get; set; } }

public interface B { string Property { get; set; } }

public interface C : A, B { }

public class MyClass : C
{
    string B.Property { get; set; }
    string A.Property { get; set; }
}

到了给他们打电话的时候,你必须做:

MyClass c = new MyClass();
Console.WriteLine("Property A is ": ((A)c).Property);

你为什么不这样做:

public class MyClass : C
{
    string B.Property { get; set; }
    string A.Property { get; set; }
    string B { get { return B.Property; } set { B.Property=value; } }
    string A { get { return A.Property; } set { A.Property=value; } }

}

并且应该注意这是一个糟糕的设计,如果你要暴露一个接口 C,确保你找到一个更好的方法来暴露 A/B.Property。

【讨论】:

  • +1 但是 OP 仍然必须投射。这并不能真正回答“为什么?”的问题。
  • nitpick:不会编译。删除 public 因为它是一个显式的接口实现......
  • 我尝试做的时候还是有问题:C test = new MyClass(); Console.WriteLine(test.Property);
  • -1 显式实现在通过接口访问时没有帮助,只有在通过类访问时。
【解决方案3】:

要弄清楚什么? cTest 是“C”类型,它从两个不同的类继承“Property”;编译器不知道你想要哪一个。这种行为是从 C++ 继承而来的;这是“为什么多重继承是潘多拉魔盒”的经典例子。

其他面向对象的语言——Java 是一个值得注意的例子——通过定义避免了这个问题:类似命名/类似签名的方法被融合在一个共同的后代中。

【讨论】:

    【解决方案4】:

    当您从单个接口继承时,编译器可以在您添加新方法时准确确定您有兴趣实现的方法。

    但是,当多个接口具有相同的方法时,基本(且正确)的假设是每个接口都期望该方法有不同的实现,因为这些方法或属性是在不同的接口上定义的。

    因此编译器会告诉您,这些不同的接口需要对这些属性中的每一个进行显式实现。

    对于一个属性或方法,两个接口共享同一个 NAME 的事实是任意的 - 没有理由假设它们共享除名称之外的任何其他内容,因此编译器可以防止您犯错误,即在同样的方式。

    【讨论】:

      【解决方案5】:

      不简单,看起来也不简单。如果两个接口之间发生名称冲突,.NET 需要询问您要实现哪个接口。它问你这个问题的方式是通过歧义错误。

      如果你没有出现这种错误,你最终会碰巧实现接口。

      【讨论】:

      • 我没有看到冲突,因为显然 A 和 B 都使用了一个名为 Property 的属性。他们都可以使用相同的字段。但是一引入接口C,就麻烦了。
      【解决方案6】:

      你需要explicity implement每个接口的两个属性:

      public class MyClass : C     
      {         
          string A.Property { get; set; }    
          string B.Property { get; set; }      
      } 
      

      【讨论】:

      • 我尝试做的时候还是有问题:C test = new MyClass(); Console.WriteLine(test.Property);
      • 如果它们没有显式引用,仅仅因为它们具有相同的名称对编译器没有任何意义。没有“财产”;有“A.Property”和“B.Property”
      • 但是当我只使用 public class MyClass : A, B 时我完全没有问题。
      • -1 显式实现在通过接口访问时没有帮助,只有在通过类访问时才起作用
      【解决方案7】:

      因为你做的不对。 A 和 B 冲突并且属性名称相同...您需要使用接口的 Explicit 实现。

      参考here

      【讨论】:

      • 显式实现在通过接口访问时没有帮助,只有在通过类访问时才起作用
      【解决方案8】:

      有很多答案,而且都是正确的,因为显式接口实现是您问题的答案。

      我将尝试通过一个有点复杂的示例来阐明这种设计背后的动机:

      假设我有一个供人们运行的接口(可能的实现如LongDistanceRunnerJoggerMarathonMan 等)

      public interface IRunner 
      {
         void Run();
      }
      

      以及可以打开和运行的设备接口(可能实现BathTubApplicationDishwasher 等)

      public interface IRunnable
      {
         void Run();
      }
      

      现在我想为IMusicallJogger 创建和接口(实现如JoggerWithIpodBoomBoxJogger 等)

      public interface IMusicalJogger : IRunner, IRunnable {}
      
      public class BoomBoxJogger : IMusicalJogger
      {
         // code here
      }
      
      BoomBoxJogger bbJogger = new BoomBoxJogger();
      

      现在,当我说bbJogger.Run() 我的对象应该做什么?它应该开始跑过公园,还是应该打开音箱,或者两者兼而有之,或者完全是别的什么?如果我同时实现了类和调用点,很明显希望我的慢跑者两者都做,但是如果我只控制调用点呢?如果接口的其他实现可以做其他事情怎么办?如果我的慢跑者开始在公园里奔跑,当它被用在一个被视为设备的环境中时(通过铸造)。

      这就是显式接口实现发挥作用的地方。

      我必须像这样定义我的类:

      public class BoomBoxJogger : IMusicalJogger
      {
         void IRunner.Run() //implementation of the runner aspect
         {
            Console.WriteLine("Running through the park");
         }
      
         void IRunnable.Run() //implementation of the runnable aspect
         {
            Console.WriteLine("Blasting out Megadeth on my boombox");
         }
      
         public void Run() //a new method defined in the class itself 
         {
            Console.WriteLine("Running while listening to music");
         }
      
      }
      

      然后,当我打电话时,我必须指定我想要使用的慢跑者的 aspect

      BoomBoxJogger bbJogger = new BoomBoxJogger();
      ((IRunner).bbJogger).Run(); // start running
      ((IRunnable).bbJogger).Run(); // blast the boombox
      //and of course you can now do
      bbJogger.Run //running while listening
      
      ((IMusicalJogger)jogger).Run(); //compiler error here, as there is no way to resolve this.
      

      希望我能帮助澄清这个概念。

      【讨论】:

      • 感谢您的评论。问题不在类级别,而是在接口(C)级别。当我有一个接口 C:A、B 并且我使用该接口时,我遇到了麻烦。隐式继承将作用于对象本身。
      • 是的,你说的相当于IMusicalJogger.Run()。编译器除了痛苦地举起双臂还能做什么?如果我从类中省略显式实现,我的应用程序的行为将不直观,但仍会运行(双关语)。
      • @Kees,顺便说一句,你可以做interface C: A, B { new string Property { get; set; }; },但我认为这会让问题变得更糟。
      • 是的,我认为@Konstantin 迄今为止给出了最好的答案。 @codymanix 还提供了一些关于如何解决问题的好信息。
      • @Kees:是的,但我喜欢我想出的例子,我想我会把它写成一篇博文:)
      猜你喜欢
      • 1970-01-01
      • 2017-10-06
      • 2013-08-01
      • 2014-09-09
      • 2016-08-06
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2014-06-01
      相关资源
      最近更新 更多