Почему использование закрытого вложенного типа внутри универсального типа в списке интерфейсов не противоречит доступности?

В случае, если название не совсем понятно, вот код, который меня озадачивает:

public interface IFoo<T>
{
}

public class MyClass : IFoo<MyClass.NestedInMyClass>
{
  private class NestedInMyClass
  {
  }
}

Я удивлен, что это компилируется без ошибок. Такое ощущение, что я выставляю тип private. Разве это не должно быть незаконным?

Возможно, вашими ответами будут просто "Нет никаких правил, запрещающих это, так почему бы и нет?" Возможно, не менее удивительно, что MyClass.NestedInMyClass находится даже в "области действия". Если я удалю квалификацию MyClass., он не скомпилируется.

(Если я изменяю IFoo<> на общий класс, который затем должен стать базовым классом MyClass, это недопустимо, поскольку базовый тип должен быть как минимум таким же доступным, как и тип сам.)

Я попробовал это с компилятором С# 4 Visual Studio 2010.


person Jeppe Stig Nielsen    schedule 07.01.2013    source источник
comment
Интересно, что произойдет, если вы создадите в IFoo функцию, которая возвращает или принимает T. У меня сейчас нет ПК, чтобы протестировать ее.   -  person Euphoric    schedule 07.01.2013
comment
@Euphoric Я как раз собирался спросить об этом. Скорее всего, это только разрешено, т.к. IFoo является интерфейсом и как таковой ничего не может сделать с вложенным классом. В этом случае раскрытие его в методе должно быть незаконным. Насколько я понимаю, это сделало бы эту функцию крайне бесполезной.   -  person Wutz    schedule 07.01.2013
comment
@Euphoric Только что попробовал сделать именно это. В Foo добавлен метод, который принимает T, реализована реализация (noop), и этот метод можно вызывать для экземпляра MyClass (хотя я мог передать только null, чтобы он скомпилировался, так как я не мог получить экземпляр извне) . Обратите внимание, что если метод возвращает T, вы получите ошибки компилятора для непоследовательной доступности, выставив NestedInMyClass извне.   -  person Servy    schedule 07.01.2013
comment
@Euphoric Похоже, невозможно написать реализацию IFoo<T>, если IFoo<T> содержит какой-либо член, который использует T.   -  person Jeppe Stig Nielsen    schedule 07.01.2013
comment
@Эйфорик Не уверен. Если вы можете передать T, то вызывающий абонент никогда не сможет получить T, он может передать только null. Так как никакие T не могут быть возвращены таким образом, чтобы публично раскрыть частный класс...   -  person Servy    schedule 07.01.2013
comment
@Servy Хм, у меня это не работает.   -  person Jeppe Stig Nielsen    schedule 07.01.2013
comment
Компилятор @JeppeStigNielsen C # 5.0 для меня, в чем может быть разница.   -  person Servy    schedule 07.01.2013
comment
@JeppeStigNielsen Пробовали ли вы явную реализацию интерфейса?   -  person CodesInChaos    schedule 07.01.2013
comment
@Servy и CodesInChaos Вы правы, у меня работает с явной реализацией интерфейса. Я попытался использовать обычный метод экземпляра public, и, конечно же, это дало мне непоследовательную доступность.   -  person Jeppe Stig Nielsen    schedule 07.01.2013


Ответы (2)


Никакой внешний код не может преобразовать объект в этот интерфейс, так что это не проблема доступности.

public классам даже разрешено реализовывать интерфейсы private или internal, и, аналогично, приведение не может происходить из внешнего кода.

Re: обсуждение реализаций, которые зависят от T - вам будет разрешено, если вы используете явную реализацию интерфейса - потому что в этом случае методы для интерфейса фактически закрыты. Например.:

public interface IFoo<T>
{
  void DoStuff(T value);
}

public class MyClass : IFoo<MyClass.NestedInMyClass>
{
  void IFoo<MyClass.NestedInMyClass>.DoStuff(MyClass.NestedInMyClass value)
  {
  }
  private class NestedInMyClass
  {
  }
}

работает (поскольку метод реализации интерфейса не раскрывается самим классом).

person Damien_The_Unbeliever    schedule 07.01.2013
comment
@Servy Вы уверены, что метод был общедоступным, а NestedInMyClass - частным? - person Euphoric; 07.01.2013
comment
@Servy Можете ли вы опубликовать свой код? Когда я отмечаю этот метод как общедоступный, я получаю несовместимую ошибку доступности. - person CodesInChaos; 07.01.2013
comment
неважно, ты прав. Я играл и не могу воспроизвести это снова, должно быть, что-то не так. - person Servy; 07.01.2013
comment
Тогда что, если public (невложенный) класс C реализует IEnumerable<Secret>, где Secret — это тип, отличный от public? (Для этого примера C не содержит методов public GetEnumerator(), только явные реализации интерфейса.) Предположим, что кто-то может видеть C и имеет его экземпляр, но не может видеть Secret из-за доступности. Могут ли они foreach через свой экземпляр? Я думаю, они могут из-за нестандартного интерфейса IEnumerable. Что, если переменная foreach типизирована неявно (var в foreach (var x in instanceOfC) { ... })? - person Jeppe Stig Nielsen; 07.01.2013
comment
Если они реализуют IEnumerable<Secret>, то они должны использовать явную реализацию интерфейса. Это означает, что вы можете получить доступ к методу IEnumerator<T>s GetEnumerator только после приведения его к IEnumerable<Secret> — это не общедоступный метод класса. И вы не можете сделать бросок, потому что Secret недоступен. (Все это не может разыгрывать вещи, конечно, игнорирует Reflection) - person Damien_The_Unbeliever; 07.01.2013
comment
Я это понимаю. У меня больше нет компилятора C#, но я боюсь, что var в моем примере foreach может быть очень проблематичным. Это основано на разделе Оператор foreach Спецификации языка C#. Но я хотел бы знать, как это реализовано в Visual C#. - person Jeppe Stig Nielsen; 07.01.2013
comment
@JeppeStigNielsen - кажется, я неправильно прочитал ваш предыдущий комментарий. Да, я думаю, вы все еще можете получить доступ к IEnumerable (не универсальному) интерфейсу, но очевидно, что его результаты имеют тип object, так что var сделал бы вывод, если бы вы использовали этот интерфейс. И с самого первого дня существования .NET всегда можно было получить object из-за какого-то фрагмента кода, к которому у вас нет доступа. - person Damien_The_Unbeliever; 07.01.2013
comment
Я попробовал, и, конечно же, foreach игнорирует общий интерфейс IEnumerable<Secret>, если тип Secret не виден из-за его уровня доступа (например, private или internal). В этом случае var не только преобразуется в object, оператор foreach также использует другую явную реализацию интерфейса (с неуниверсальным типом возврата). Это имеет смысл. Но в спецификации C# говорится о том, существует ли неявное преобразование в IEnumerable<T> для некоторых T. По-видимому, доступны только T. - person Jeppe Stig Nielsen; 09.01.2013
comment
Кроме того, они изменили логику foreach, когда реализовано несколько интерфейсов IEnumerable<> (и нет общедоступного метода GetEnumerator()), с C# 4 на C# 5. Интересно, что если в C# 4 я реализую два IEnumerable<>, один видимого типа, другой — IEnumerable<Secret>, компилятор откажется транслировать код, говоря ошибка CS1640: оператор foreach не может работать с переменными типа '(тип)', поскольку он реализует несколько экземпляров 'System.Collections. Generic.IEnumerable‹T›'; попробуйте выполнить приведение к конкретному экземпляру интерфейса (продолжение следует) - person Jeppe Stig Nielsen; 09.01.2013
comment
Затем, если я удалю IEnumerable<Visible>, чтобы у меня были только IEnumerable<Secret> и неуниверсальный IEnumerable, он, конечно, не использует IEnumerable<Secret>. - person Jeppe Stig Nielsen; 09.01.2013

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

Наличие в открытом классе Foo реализации интерфейса закрытого или внутреннего типа IBar позволяет коду, имеющему доступ к IBar, приводить Foo ссылок к IBar. Тот факт, что Foo доступен коду, не имеющему доступа к IBar, никоим образом не означает, что он также не будет использоваться кодом, имеющим такой доступ. В самом деле, было бы совершенно нормально, если бы сборка или класс, в котором определено Foo, захотели бы использовать функции Foo, недоступные для внешнего мира; тот факт, что он реализует IBar, будет просто одной из таких функций.

person supercat    schedule 07.01.2013