C#接口方法的歧义性

36

考虑以下示例:

interface IBase1
{
   int Percentage { get; set; }
}

interface IBase2
{
   int Percentage { get; set; }
}

interface IAllYourBase : IBase1, IBase2
{
}

class AllYourBase : IAllYourBase
{
   int percentage;

   int Percentage {
      get { return percentage; }
      set { percentage = value; }
   }
}

void Foo()
{
   IAllYourBase iayb = new AllYourBase();
   int percentage = iayb.Percentage; // Fails to compile. Ambiguity between 'Percentage' property.
}
在上面的示例中,存在调用哪个Percentage属性的歧义。假设IBase1IBase2接口可能不会被改变,我该如何以最干净、最优选的方式解决这个歧义?

更新

基于使用显式接口实现会得到的回复,我想提到一点:虽然这确实解决了问题,但对于我来说并不是最理想的解决方式,因为我大部分时间都将我的AllYourBase对象用作IAllYourBase,很少用作IBase1IBase2。这主要是因为IAllYourBase也有实现了的接口方法(我在上面的代码片段中没有详细说明它们,因为我认为它们与问题无关),而我也想访问这些方法。反复转换类型将变得非常乏味,并导致混乱的代码。
我尝试了一种解决方案,涉及在IAllYourBase中定义Percentage属性,并且不使用显式接口实现,这似乎至少消除了编译器错误。
class IAllYourBase : IBase1, IBase2
{
   int Percentage { get; set; }
}

这是一个有效的解决方案吗?


8
有趣的问题,我认为它不应该存在。 - NotMe
“base” 不是一个好的变量名,即使是局部变量也容易与“base”关键字混淆,该关键字用于从派生类中访问基类成员。 - Jesse C. Slicer
@Jesse,实际上这是完全不合法的。base始终是一个关键字。 - dlev
如果您能提供有关类结构代表的更多详细信息,那么它们可以进行重新设计,并且您可以摆脱这个问题。 - sll
@Jesse:我知道这不是有效的,这只是一个人为制造的例子。我甚至还没有编译它。但你明白我试图表达的基本观点了... - void.pointer
你忘记添加中介者类“AreBelongToUs”了。 - devinbost
9个回答

24

明确实现:

public class AllYourBase : IBase1, IBase2
{
    int IBase1.Percentage { get{ return 12; } }
    int IBase2.Percentage { get{ return 34; } }
}

如果你这样做,那么你当然可以像处理普通属性一样处理你的非模糊属性。

IAllYourBase ab = new AllYourBase();
ab.SomeValue = 1234;

然而,如果你想访问百分比属性,这种方法行不通(假设它能行得通,那么会返回哪个值?)

int percent = ab.Percentage; // Will not work.

你需要指定要返回的百分比是哪个。这可以通过将其转换为正确的接口来完成:

int b1Percent = ((IBase1)ab).Percentage;

就像你所说的那样,你可以重新定义接口中的属性:

interface IAllYourBase : IBase1, IBase2
{
    int B1Percentage{ get; }
    int B2Percentage{ get; }
}

class AllYourBase : IAllYourBase 
{
   public int B1Percentage{ get{ return 12; } }
   public int B2Percentage{ get{ return 34; } }
   IBase1.Percentage { get { return B1Percentage; } }
   IBase2.Percentage { get { return B2Percentage; } }
}

现在你通过使用不同的名称解决了歧义。


如果我这样做,我将无法像使用 IAllYourBase 对象一样访问 AllYourBase 对象的“百分比”属性,是吗? 这对我来说将是一个巨大的负面影响,因为我主要将此对象用作 IAllYourBase 而不是 IBase1IBase2。 我发现有效的方法是在 IAllYourBase 接口中再次定义接口属性,这似乎也可以解决歧义问题。 - void.pointer
非常好的回复。这超出了我的帮助预期(虽然我也赞同其他帮助回答)。 - void.pointer

14

假设您希望两个属性都可以访问percent成员变量,则可以通过显式接口实现并扩展IAllYouBase接口来实现:

interface IAllYourBase : IBase1, IBase2
{
    new int Percentage { get; set; }
}

class AllYourBase : IAllYourBase
{
   int percentage;

   public int Percentage {
      get { return percentage; }
      set { percentage = value; }
    }

    int IBase1.Percentage {
      get { return percentage; }
      set { percentage = value; }
    }

    int IBase2.Percentage {
      get { return percentage; }
      set { percentage = value; }
   }
}

虽然不美观,但这将会给你我认为你想要的行为。


+1 - 这是最干净的解决方案,限制最少。 - Daniel
3
如果两者返回相同的值,则不需要显式实现。 - Mateusz Myślak
我甚至不知道你可以在接口中使用new关键字。谢谢! - ashbygeek

10

显式实现接口。

显式接口成员实现是指引用了完全限定的接口成员名称的方法、属性、事件或索引器声明。

请参阅此 MSDN 页面以获得详细教程。

interface AllYourBase : IBase1, IBase2
{
   int IBase1.Percentage { get; set; }
   int IBase2.Percentage { get; set; }
}

2
我遇到了一个“显式接口声明只能在类或结构中声明”的错误。 - Kees C. Bakker

8
尽管您已经接受了答案,但我认为显式实现要求太多的冗余工作(在您的情况下实现相同的属性两次)!
进一步分离接口(接口隔离原则)如何?
  internal interface IPercentage
    {
        int Percentage { get; set; }
    }

    internal interface IBase1 : IPercentage
    {
    }

    internal interface IBase2 : IPercentage
    {
    }

    internal interface IAllYourBase : IBase1, IBase2
    {
    }

    internal class AllYourBase : IAllYourBase
    {
        private int percentage;

        public int Percentage
        {
            get { return percentage; }
            set { percentage = value; }
        }

        void Foo()
        {
            IAllYourBase iayb = new AllYourBase();
            int percentage = iayb.Percentage; // Compiles now!!!
        }
    }

7

明确实现并访问它:

interface IAllYourBase : IBase1, IBase2 { } 
public class AllYourBase : IAllYourBase
{     
      int IBase1.Percentage { get{ return 12; } }     
      int IBase2.Percentage { get{ return 34; } } 
} 

IAllYourBase base = new AllYourBase();    
int percentageBase1 = (base as IBase1).Percentage;
int percentageBase2 = (base as IBase2).Percentage;

7
如果您实际上不需要为“百分比”属性返回不同的值,您可以通过单独从每个接口“派生”,而不是从“主”接口中“派生”,来消除编译器错误:
public class AllYourBase : IBase1, IBase2
{
    // No need to explicitly implement if the value can be the same
    public double Percentage { get { return 12d; } }
}

当然,如果您需要单独的值,那么您将不得不明确地实现接口,并通过适当类型的引用访问属性。
public class AllYourBase : IBase1, IBase2
{
    // No need to explicitly implement if the value can be the same
    public double IBase1.Percentage { get { return 12d; } }
    public double IBase2.Percentage { get { return 34d; } }

}

和代码:

public void SomeMethod()
{
    AllYourBase ayb = new AllYourBase();
    IBase1 b1 = ayb
    double p1 = b1.Percentage;

    IBase2 b2 = ayb;
    double p2 = b2.Percentage;
}

在显式实现接口时需要考虑的一个重要因素是,AllYourBase 本身不再具有 Percentage 属性。只有在对象通过作为其中一个接口类型的引用进行访问时才能访问它:
public void SomeMethod()
{
    AllYourBase ayb = new AllYourBase();
    double d = ayb.Percentage;   // This is not legal
}

更新:根据您的编辑,您的解决方案很好,假设您不需要针对“IBase1”和“IBase2”采取不同的行为。 您的解决方案隐藏了这些属性,因此只能通过将对象转换为这两个接口之一来访问它们。

3

尽管前面提到的显式实现是正确的,但请出于清晰起见考虑以下情况(或仅为将来阅读您的代码的人考虑):

如果这些百分比根据接口具有不同的含义,为什么不给它们一些有意义的名称呢?

interface IBase1 
{ 
    int PercentageBase1 { get; set; } 
}

interface IBase2
{
    int PercentageBase2 { get; set; } 
}

另一方面,如果它们的含义相同,为什么不在一个界面中只使用一个百分比,并从另一个百分比派生出一个呢?

interface IBase1 
{ 
    int Percentage { get; set; } 
}

interface IBase2 : IBase1
{
}

然而,如果由于某种原因最后的操作不可行,请考虑创建一个基础接口,其中包含了它们共有的属性:

interface IBase0
{
    int Percentage { get; set; } 
}

interface IBase1 : IBase0
{ 
}

interface IBase2 : IBase0
{
}

3
你可以在 IAllYourBase 接口中定义该属性。
类似这样:
interface IAllYourBase : IBase1, IBase2
{
   new int Percentage { get; set; }
}

这将解决属性之间的歧义问题,同时保持接口结构。

1
void Foo()
{
   IAllYourBase base = new AllYourBase();
   int percentage = base.Percentage; // Fails to compile. Ambiguity between 'Percentage' property.
}

在这个例子中,您使用了base关键字作为对象名称,这可能导致问题!

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接