Неоднозначность метода интерфейса C # - PullRequest
30 голосов
/ 16 августа 2011

Рассмотрим следующий пример:

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 свойство вызывать.Предполагая, что интерфейсы IBase1 и IBase2 могут не быть изменены, как мне решить эту двусмысленность самым чистым, наиболее предпочтительным способом?

Обновление

Основываясь на ответах, которые я получил за использование явной реализации интерфейса, я хочу упомянуть, что, хотя это действительно решает проблему, оно не решает ее идеальным способом для меня, потому что я использую свой объект AllYourBase как IAllYourBase большую частьвремя, никогда как IBase1 или IBase2.В основном это связано с тем, что IAllYourBase также имеет методы интерфейса (я не смог подробно описать их в моем фрагменте кода выше, потому что я думал, что они не имеют отношения к делу), которые реализованы AllYourBase, и я тоже хочу получить к ним доступ.Постоянное приведение вперед и назад будет очень утомительным и приведет к грязному коду.

Я попробовал одно решение, включающее определение свойства Percentage в IAllYourBase и не использующее явную реализацию интерфейса, которая, казалось,избавиться от ошибки компилятора хотя бы:

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

Это правильное решение?

Ответы [ 9 ]

21 голосов
/ 16 августа 2011

Реализуйте в явном виде:

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

Теперь вы решили неоднозначность с помощью различныхимена вместо.

10 голосов
/ 16 августа 2011

Предполагая, что вы хотите, чтобы оба свойства обращались к переменной-члену процентов , вы можете сделать это, используя явную реализацию интерфейса и расширяя интерфейс 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; }
   }
}

Это не красиво, но это даст вам поведение, которое, я думаю, вы ищете.

10 голосов
/ 16 августа 2011

Реализуйте интерфейсы явно.

Явная реализация члена интерфейса - это объявление метода, свойства, события или индексатора, которое ссылается на полное имя члена интерфейса

См. эта страница MSDN для подробного руководства.

interface AllYourBase : IBase1, IBase2
{
   int IBase1.Percentage { get; set; }
   int IBase2.Percentage { get; set; }
}
7 голосов
/ 16 августа 2011

Если вам на самом деле не нужно возвращать разные значения для свойства Percentage, вы можете устранить ошибку компилятора, "производя" от каждого интерфейса отдельно, а не от "основного" интерфейса:

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. Ваше решение скрывает эти свойства, поэтому они будут доступны только при приведении объекта к одному из этих двух интерфейсов.

7 голосов
/ 16 августа 2011

Явно реализовать и получить к нему доступ:

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;
4 голосов
/ 09 марта 2012

Хотя вы уже приняли ответ.Я бы сказал, что явная реализация требует слишком много избыточной работы (в вашем случае реализация одного и того же свойства дважды)!

Как насчет дальнейшей сегрегации интерфейса (принцип разделения сегментов)?

  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!!!
        }
    }
2 голосов
/ 16 июня 2017

Вы можете определить свойство в интерфейсе IAllYourBase.

Примерно так:

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

Это решит проблемы с неоднозначностью между свойствами.И вы можете сохранить структуру интерфейсов.

2 голосов
/ 16 августа 2011

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

Если эти проценты имеют разные значения в зависимости от интерфейса, почему бы не дать им несколько значимых имен?

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
{
}
1 голос
/ 16 августа 2011
void Foo()
{
   IAllYourBase base = new AllYourBase();
   int percentage = base.Percentage; // Fails to compile. Ambiguity between 'Percentage' property.
}

В этом примере вы используете ключевое слово base для имени объекта, и это может вызвать проблему !!!

...