Нечеткость метода интерфейса С#
Рассмотрим следующий пример:
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
не могут быть изменены, как я могу решить эту двусмысленность самым чистым, наиболее предпочтительным способом?
Update
Основываясь на ответах, которые я получал для использования явной реализации интерфейса, я хочу упомянуть, что, хотя это и решает проблему, он не идеально подходит для меня, потому что я использую свой объект AllYourBase
как IAllYourBase
большую часть времени, а не как IBase1
или IBase2
. Это связано главным образом с тем, что IAllYourBase
также имеет методы интерфейса (я не смог подробно описать их в своем фрагменте кода выше, потому что я думал, что они неактуальны), которые реализованы с помощью AllYourBase
, и я тоже хочу получить к ним доступ. Кастинг взад и вперед все время будет очень утомительным и приведет к запутанному коду.
Я попробовал одно решение, которое включало определение свойства Percentage
в IAllYourBase
и не используя явную реализацию интерфейса, которая, по-видимому, избавлялась от ошибки компилятора:
class IAllYourBase : IBase1, IBase2
{
int Percentage { get; set; }
}
Является ли это допустимым решением?
Ответы
Ответ 1
Реализовать явно:
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; } }
}
Теперь вы решили двусмысленность разными именами.
Ответ 2
Внедрить интерфейсы явно.
Явная реализация члена интерфейса - это объявление метода, свойства, события или индексатора, которое ссылается на имя полностью идентифицированного имени интерфейса
См. эту страницу MSDN для подробного руководства.
interface AllYourBase : IBase1, IBase2
{
int IBase1.Percentage { get; set; }
int IBase2.Percentage { get; set; }
}
Ответ 3
Предполагая, что вы хотите, чтобы оба свойства получили доступ к переменной-члену %, вы можете выполнить это с помощью явной реализации интерфейса и расширения интерфейса 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; }
}
}
Это не очень, но это даст вам поведение, о котором я думаю, что вы после.
Ответ 4
Явно реализовать и получить к нему доступ:
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;
Ответ 5
Если вам действительно не нужно возвращать разные значения для свойства 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
. Ваше решение скрывает эти свойства, поэтому они будут доступны только путем переноса объекта на один из этих двух интерфейсов.
Ответ 6
Хотя вы уже приняли ответ. Я бы сказал, что явная реализация требует слишком большого количества избыточной работы (повторное использование того же свойства в вашем случае)!
Как насчет дальнейшей сегрегации интерфейса (принцип цельной цепочки)?
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 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
{
}
Ответ 8
void Foo()
{
IAllYourBase base = new AllYourBase();
int percentage = base.Percentage; // Fails to compile. Ambiguity between 'Percentage' property.
}
В этом примере вы используете ключевое слово base
для имени объекта и которое может вызвать проблему!!!
Ответ 9
Вы можете определить свойство в интерфейсе IAllYourBase
.
Что-то вроде этого:
interface IAllYourBase : IBase1, IBase2
{
new int Percentage { get; set; }
}
Это позволит решить проблемы с неопределенностью между свойствами. И вы можете сохранить структуру интерфейсов.