为什么实现具有相同属性的多个接口会显示“模糊”警告?

时间:2012-03-12 03:52:56

标签: c# interface compiler-errors

相关文章:C# interface method ambiguity

来自同一来源的代码:

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

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

    private interface IAllYourBase : IBase1, IBase2
    {
    }

    private class AllYourBase : IAllYourBase
    {
        private int _percentage;

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

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

(但不回答我的问题 - “为什么合同变得含糊不清?”)

假设:

  

接口是实施类必须遵守的合同。

如果两个(或更多)接口请求相同的合同,并且接口将它们传递给“forward”,然后类实现它们和ACCEPTS,那么普通合同应该只用作实现类的一个合同(通过不提供一个明确的实现)。然后,

  1. 为什么编译器会对共同合同显示“歧义”警告?

  2. 为什么编译器在尝试通过接口(iayb.Percentage)访问不明确的合同时无法编译?

  3. 我想知道编译器使用此限制有什么好处?

    编辑:提供一个真实世界的用例,我希望跨接口使用合同作为一个合同。

    public interface IIndexPriceTable{
          int TradeId{get;}
          int IndexId{get;}
          double Price{get;}
    }
    
    public interface ILegPositionTable{
          int TradeId {get;}
          int LegId {get;}
          int Position {get;}
    }
    
    public interface ITradeTable {
          int TradeId{get;}
          int IndexId{get;}
          int LegId{get;}
          //others
    }
    
    public interface IJoinedTableRecord : IIndexPriceTable, ILegPositionTable, ITradeTable {
         //Just to put all contracts under one interface and use it as one concrete record, having all information across different tables.
    }
    
    • 为什么我想在我的联合表记录中使用3-TradeId,2-LegId,2-IndexId?

6 个答案:

答案 0 :(得分:13)

解决方案是使用new关键字再次定义属性Percentage:

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

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

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

private class AllYourBase : IAllYourBase
{
    private int _percentage;

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

private void Foo()
{
    IAllYourBase iayb = new AllYourBase();
    int percentage = iayb.Percentage; //OK
} 

注意:

接口的C#方法与方法plan by Bjarne StrouStrup in C++14非常不同。在C#中,您必须声明,类通过修改类本身来实现接口,而在C ++ 14中,它只需要具有与接口定义相对应的方法。因此,C#中的代码具有更多的依赖关系,这些依赖关系在C ++ 14中编码。

答案 1 :(得分:3)

因为编译器无法确定您尝试访问的基本接口实现(IBase1.PercentageIBase2.Percentage),因为您的IAllYourBase接口需要在之后< / em>他们和两者都有他们自己的 Percentage属性。

这样说:只是因为两个接口具有相同名称和类型的属性并不意味着该属性在两个接口中的工作方式相同。即使公共接口继承自具有相同成员的两个接口,编译器也不能将两个看似相同的属性合并为一个,因为它们是两个不同合同的成员

答案 2 :(得分:3)

因为接口IAllYourBase没有声明Percentage属性本身。

当您将AllYourBase的实例分配给变量IAllYourBase时,编译器需要输出对IBase1.PercentageIBase2.Percentage的调用:

callvirt   instance int32 IBase1::get_Percentage()

callvirt   instance int32 IBase2::get_Percentage()

这些是不同类型的不同成员,只是因为它们具有相同的签名并不意味着它们可以互换。

在您的实际情况中,您可能需要更精细的粒度接口来定义公共属性。

答案 3 :(得分:1)

int percentage = iayb.Percentage;不知道它正在处理AllYourBase类,只要不管它是什么,它都会实现IAllYourBase接口。

假设我尝试使用DoubleBase类执行相同的语句:

private class DoubleBase : IAllYourBase
{
    int IBase1.Percentage { get; set; } = 10;

    int IBase2.Percentage { get; set; } = 20;
}

int percentage设置的值是什么?

答案 4 :(得分:0)

我明白你的观点。我想这个编译器限制的主要好处是最好有一个,然后不是。即那么你的无意的界面clush会被忽略,然后从这个奇怪的案例中受益(如果有的话)你会想要这样的行为。

BTW任何真实世界的场景中,期望的行为都会非常有用吗?

答案 5 :(得分:0)

如果一个接口继承了另外两个具有同名成员的接口,那么必须应用以下两个条件之一:

  1. 两个接口都从其他接口继承相同的成员。另一个接口必须是公共的,但是可以记录它纯粹是为了继承而存在,并且不希望消费者声明其类型的变量或参数。
  2. 继承其他接口的接口声明为同名的`new` 自己的成员。当一个接口声明一个只读属性而另一个接口声明一个同名的只写属性时,这是一个很好的方法;组合这两个接口的接口可以声明一个读写属性,其实现应该使用只读属性的“getter”和只写属性的“setter”。不过,我不确定它在许多其他情况下是否会好。

如果一个人不做其中的一件事,那么编译器最好不要试图猜测。假设有一个接口IListOfDigits,其Add方法将0-9整数添加到列表中,IBigNumber,其Add方法以算术方式添加数字。一个还有一个继承IListOfDigitsRepresentingBigNumber的接口。给定IListOfDigitsRepresentingBigNumber名为myThing,保持数字“5,4,3,2”,myThing.Add(1)的效果应该是什么?它应该改变myThing以保持“5,4,3,2,1”(IListOfDigits.Add)或“5,4,3,3”的效果(IBigNumber.Add的效果)?如果执行上述任一操作,编译器将毫无困难地确定要使用哪种Add方法。否则,如果两种方法都可以接受int,则不会有线索。

顺便说一句,泛型和重载是一个有趣的案例。如果IFoo<T,U>有成员void Bar(T param)void Bar(U param),则无法将类声明为实施IFoo<int,int>。另一方面,可以将类Foo<T,U>声明为实现IFoo<T,U>,然后将其他类声明为继承自Foo<int,int>,因为即使T和{{1引用相同的类型,编译器仍会使用UT来解决重载。