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

本文关键字:显示 接口 歧义 警告 实现 属性 为什么 | 更新日期: 2023-09-27 18:27:22

延伸阅读:C#接口方法歧义

来自同一来源的代码:

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

(但没有回答我的问题——"为什么合同变得模棱两可?

鉴于:

接口是实现类必须遵守的协定。

如果两个(或多个(接口请求相同的协定,并且一个接口将它们"向前"传递,然后类实现它们并接受公共协定应该只作为实现类的一个协定(通过不提供显式实现(。然后

  1. 为什么编译器在常见协定上显示"歧义"警告?

  2. 为什么编译器在尝试通过接口( iayb.百分比( ?

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

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

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?

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

解决方案是使用如下所示的新关键字再次定义属性 Percent:

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#方法与Bjarne StrouStrup在C++14中的方法计划非常不同。在 C# 中,您必须声明该类通过修改类本身来实现接口,而在 C++14 中,它只需要具有与接口定义相对应的方法。因此,C# 中的代码比 C++14 中的代码具有更多的依赖项。

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

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

callvirt   instance int32 IBase1::get_Percentage()

callvirt   instance int32 IBase2::get_Percentage()

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

在实际情况下,您可能需要定义通用属性的更细粒度的接口。

因为编译器无法确定您尝试访问哪个基本接口实现(IBase1.PercentageIBase2.Percentage(,因为您的IAllYourBase接口在它们之后,并且它们都有自己的Percentage属性。

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

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设置为什么值?

我明白你的意思。我想这个编译器限制的主要好处是最好有一个,然后不要。即有更多的伤害,那么你无意界面脸红将被忽略,然后从你想要这种行为的奇怪案例中受益(如果有的话(。

顺便说一句,任何现实世界的场景都有期望的行为会非常有用吗?

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

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

如果不做这些事情之一,编译器最好不要尝试猜测。 假设有接口 IListOfDigits ,其Add方法将整数 0-9 附加到列表中,IBigNumber ,其Add方法以算术方式添加一个数字。 一个还有一个接口IListOfDigitsRepresentingBigNumber,它继承了两者。 给定一个名为 myThingIListOfDigitsRepresentingBigNumber,保存数字"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>,因为即使TU引用相同的类型,编译器仍然会使用 TU 来解决重载。