Подтвердить что ты не робот

Почему свойство считается двусмысленным, когда другой интерфейс установлен только для набора?

В следующем коде:

interface IGet { int Value { get; } }

interface ISet { int Value { set; } }

interface IBoth : IGet, ISet { }

class Test
{
    public void Bla(IBoth a)
    {
        var x = a.Value; // Error: Ambiguity between 'IGet.Value' and 'ISet.Value'
    }
}

Я получаю сообщение об ошибке:

Неоднозначность между "IGet.Value" и "ISet.Value"

Почему компилятор не может определить, что доступ к ресурсу должен быть от IGet?

Изменить. Я предполагаю, что компилятор сначала попытается определить, к какому ресурсу обращаются, и только затем проверяет, является ли он get или set. Вопрос в том, почему? Почему бы не исключить тех кандидатов, которые не предоставляют геттера? Почему бы не решить фактические методы get/set и игнорировать факт, что это свойство?

Обновить: Еще один код с использованием методов вместо свойств, где проблема не существует. Свойства не совсем похожи на пару методов.

4b9b3361

Ответ 1

Я предполагаю, что компилятор сначала пытается определить, к какому ресурсу обращаются, и только затем проверяет, является ли он get или set.

Ваша догадка правильная.

Вопрос в том, почему? Почему бы не исключить тех кандидатов, которые не предоставляют геттера? Почему бы не решить фактические методы get/set и игнорировать факт, что это свойство?

Компилятор может разрешить все кандидаты на свойства, а затем исключить те, которые не предоставляют get - почему он этого не делает? - sinelaw

Потому что языковые дизайнеры так не проектировали? - Роберт Харви

@sinelaw Потому что это не так, как определяется язык С#. Дело не в том, что этого не может быть сделано, просто это не сделано. - user2864740

Я уверен, что они спроектировали его таким образом (вряд ли они упустили эту ситуацию) - но какие рассуждения? - sinelaw

@sinelaw Предположительно из-за того, что они не чувствовали, что преимущество такой функции может привести к дополнительной сложности в ее разработке. - p.s.w.g

pswg находится на правильном пути здесь, но мы можем быть более конкретным.

Основной принцип проектирования здесь анализ исходит изнутри наружу без учета "контекстных реплик". Это сбивает с толку читателя и сложно для компилятора и разработчика IDE, когда значение выражения зависит от его непосредственного контекста. То, что мы хотим сделать, - это однозначно определить значение каждого выражения, а затем проверить, что оно работает в его контексте. Мы не хотим идти в другую сторону и говорить "хорошо, это выражение неоднозначно, поэтому позвольте мне использовать контекст как ключ".

Более конкретно: сначала компилятор должен определить значение a, а затем a.Value, а затем определить, является ли назначение законным. Компилятор не говорит "хорошо, я не мог понять, какой из двух свойств a.Value означает, потому что он неоднозначен, но я собираюсь запутать себя, делая вид, что я это понял, и возвращаюсь и исправляю вещи когда я понимаю, что я на стороне ценности задания, и только одна из этих вещей имеет значение". Также компилятор не говорит: "Я собираюсь использовать один алгоритм поиска, когда я нахожусь слева от задания, а другой, когда я нахожусь справа".

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

Есть некоторые исключения из этого общего правила, которые направлены на конкретные общие ситуации. Компилятор знает, например, что в выражении формы a.B(), что B должно быть что-то invokable; алгоритм поиска элемента автоматически отклоняет не вызывающие вызов элементы без ошибки. Лямбдас, конечно же, категорически отвергает этот принцип; значение лямбда полностью определяется его контекстом. Выполнение этой работы заняло огромную работу - это была одна из моих функций для С# 3 - и мы сделали большие инвестиции, чтобы гарантировать, что алгоритмы являются первичными в общих сценариях. Каждый раз, когда вы рассуждаете извне внутри и снаружи, вы оказываетесь в потенциально экспоненциальных ситуациях, когда вы должны сделать все возможное связывание в процессе, а затем выбрать уникальный, который работает. Эта стоимость стоит того, чтобы отличная функция, такая как тип-выводящая лямбда. Создание других форм работы с чувствительностью к контексту, особенно для неясных сценариев, как тот, который вы описываете, не является хорошим способом потратить ограниченный бюджет.

Итак, пример кода в моем ответе работает, потому что он "объединяет" два определения свойств в один вызов (устраняя неоднозначность компилятора) при выполнении двух интерфейсных контрактов как для геттера, так и для сеттера? Роберт Харви

Чтобы уточнить, код Роберта из его удаленного ответа:

public class GetSet : ISet, IGet
{
    public string Value { get; set; }
}
...
getSet.Value = "This is a test";
Debug.Print(getSet.Value); //Prints "This is a test"
Роберт, я не уверен, что понимаю ваш вопрос. Ваш код работает, потому что сначала выполняются контракты для ISet и IGet . Класс GetSet имеет все члены, необходимые каждому, и однозначное отображение. И второе, потому что ваш сайт вызова вообще не использует интерфейсы; он просто вызывает членов класса напрямую. Почему бы не работать?

Теперь, чтобы указать точку в удаленном ответе:

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

Нет, это не правильный анализ. Это не имеет никакого отношения к тому, действительно ли свойство реализовано как поле, генерируемое компилятором, или нет. Помните, что свойства на интерфейсах - это просто причудливые способы определения методов get_Value и set_Value. Пока свойство с требуемым методом существует в классе реализации, выполняется требование интерфейса. Как это свойство реализовано до класса.

свойство одного класса выполняет интерфейс двух разных контрактов.

Да! Это не проблема. Пока сопоставление элемента интерфейса с классом/структурой может быть однозначно определено, оно отлично. Например:

interface IFoo
{
    void M();
}

interface IBar
{
    void M();
}

class C : IFoo, IBar 
{ 
    public void M() { } 
}

M может выполнять двойную функцию как IFoo.M, так и IBar.M.

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

http://blogs.msdn.com/b/ericlippert/archive/2006/04/05/odious-ambiguous-overloads-part-one.aspx

http://blogs.msdn.com/b/ericlippert/archive/2006/04/06/odious-ambiguous-overloads-part-two.aspx

И для некоторых интересных родственных махинаций см. этот вопрос и ответы, на которые мы с Лучианом обратились:

Ковариация общих типов и реализация нескольких интерфейсов

Ответ 2

Я согласен с вашим анализом. Кажется, что компилятор должен разрешить символ a.Value, прежде чем он анализирует, как вы его используете, чтобы преобразовать свойство getter в вызов get_Value.

Возможно, стоит отметить, что если вы это сделаете:

public void Bla(ISet a)
{
    var x = a.Value; 
}

Вы не получите ошибку "не содержит определение". Вы получите следующее:

Свойство или индекс 'ISet.Value' не может использоваться в этом контексте, потому что ему не хватает get accessor

Компилятор нашел символ, связал его с ISet.Value и только после этого пожаловался на то, как он использовался (потому что ISet не предоставляет getter).

Ответ 3

Создатели .NET решили, что свойство должно быть особым видом сущности в своем собственном праве, а не просто позволять альтернативную форму вызова для подходящих имен методов с атрибутом "свойство". Это порой раздражает; один потенциально полезный аспект заключается в том, что он обеспечивает предотвращение возможности кода, переопределяющего свойство getter, не понимая, что свойство также имеет сеттер или переопределяет средство определения свойств, не понимая, что геттер делает что-то, кроме возвращения ожидаемого поля поддержки.

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

interface IReadableList<out T> {
    T this[int index] { get; }
}

interface IWritableList<in T> {
    T this[int index] { set; }
}

interface IMutableList<T>: IReadableList<T>, IWritableList<T> {
    new T this[int index] { get; set; }
}

В свою очередь, необходимо, чтобы код определял реализации для всех трех свойств. Неявная реализация интерфейса в С# может облегчить нагрузку на определение трех свойств, поскольку класс, который определяет T this[int index] {get {...}; set {...};}, заставит компилятор реализовать метод только для чтения с использованием указанного getter, метод записи только с использованием указанного setter, и метод чтения-записи с использованием обоих, но с точки зрения Framework, на самом деле существуют три отдельных свойства, а get/set свойства read/write не зависит от свойств get/set, связанных с только для чтения или только для записи.

Лично я считаю, что это раздражает то, что ни vb.net, ни С# не хотят использовать тот факт, что свойство читается как ключ к тому, что свойства записи только не должны учитываться при разрешении перегрузки, а также тот факт, что он должен быть исключен только для чтения, но я не разрабатывал эти языки и Framework.