Почему реализация нескольких интерфейсов с одним и тем же свойством показывает предупреждение «неоднозначность»? - PullRequest
5 голосов
/ 12 марта 2012

Похожие сообщения: Неопределенность метода интерфейса 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.Percentage)?

Хотелось бы узнать, какую пользу предоставляет компилятор с этим ограничением?

Редактировать: Предоставление реального варианта использования, где я хотел бы использовать контракты через интерфейсы как один контракт.

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 ]

15 голосов
/ 01 мая 2013

Решение состоит в том, чтобы снова определить свойство 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 # подход к интерфейсам очень отличается от подхода плана Бьярна Страуструпа в C ++ 14 . В C # вы должны заявить, что класс реализует интерфейс путем изменения самого класса, в то время как в C ++ 14 ему нужны только методы, которые соответствуют определению интерфейса. Таким образом, код на C # имеет больше зависимостей, чем код на C ++ 14.

3 голосов
/ 12 марта 2012

Поскольку интерфейс IAllYourBase не объявляет само свойство Percentage.

Когда вы присваиваете экземпляр AllYourBase переменной IAllYourBase, компилятору необходимо вывести вызов либо IBase1.Percentage, либо IBase2.Percentage:

callvirt   instance int32 IBase1::get_Percentage()

или

callvirt   instance int32 IBase2::get_Percentage()

Это разные члены разных типов, и то, что они имеют одинаковую подпись, не означает, что они взаимозаменяемы.

В реальной ситуации вам могут потребоваться более тонкие интерфейсы, которые определяют общие свойства.

3 голосов
/ 12 марта 2012

Поскольку компилятор не может определить, к какой реализации базового интерфейса (IBase1.Percentage или IBase2.Percentage) вы пытаетесь получить доступ, потому что ваш IAllYourBase интерфейс принимает после и , и их *Каждый из 1006 * обоих имеет свое собственное Percentage свойство.

Скажем так: только потому, что два интерфейса имеют свойство с одинаковым именем и типом, ониозначает, что свойство должно работать одинаково в обоих интерфейсах.Даже если общий интерфейс наследует от двух интерфейсов с одинаковыми членами, компилятор не может просто объединить два, казалось бы, идентичных свойства в одно, поскольку они являются членами двух разных контрактов .

1 голос
/ 11 января 2018

Строка 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?

0 голосов
/ 13 марта 2012

Если интерфейс наследует два других интерфейса, которые будут иметь одноименные элементы, то должно применяться одно из двух условий:

  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 и U ссылаются на тот же тип, компиляторбудет по-прежнему разрешать перегрузки, используя T и U.

0 голосов
/ 12 марта 2012

Я понимаю вашу точку зрения.Я предполагаю, что главное преимущество этого ограничения компилятора состоит в том, что лучше иметь его, чем нет.Т.е. будет больше вреда, чем ваше непреднамеренное столкновение с интерфейсом будет проигнорировано, а затем (если оно есть) извлечет выгоду из этого странного случая, когда вам нужно такое поведение.

Кстати, в любом реальном сценариижелаемое поведение будет так много полезного?

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...