Почему класс не может расширить свой собственный вложенный класс в C #? - PullRequest
31 голосов
/ 05 ноября 2008

Например:

public class A : A.B
{
    public class B { }
}

, которая генерирует эту ошибку из компилятора:

Круговая зависимость базового класса с участием «A» и «A.B»

Я всегда считал, что вложенный класс ведет себя так же, как обычный класс, за исключением особых правил, касающихся доступа к закрытым членам внешнего класса, но я предполагаю, что между этими двумя классами существует неявное наследование?

Ответы [ 6 ]

34 голосов
/ 05 ноября 2008

Насколько я могу судить, здесь не задействовано неявное наследование. Я ожидал бы, что это будет хорошо - хотя я могу вообразить странность, если бы А и В были родовыми.

Это указано в разделе 10.1.4 спецификации:

Когда класс B происходит от класса A, это ошибка времени компиляции для зависит от B. Класс напрямую зависит на своем прямом базовом классе (если есть) и напрямую зависит от класса в который сразу вкладывается (если любой). Учитывая это определение, полный набор классов, на которых класс зависит переходный Закрытие напрямую зависит от отношения.

Я выделил соответствующий раздел.

Это объясняет, почему компилятор отклоняет его, но не почему язык запрещает это. Интересно, есть ли ограничение CLI ...

РЕДАКТИРОВАТЬ: Хорошо, я получил ответ от Эрика Липперта. По сути, это было бы технически возможно (в CLI нет ничего, что могло бы это запретить), но:

  • Допустить это будет сложно в компиляторе, аннулировать различные текущие предположения относительно порядка и циклов
  • Это довольно странное дизайнерское решение, которое проще запретить, чем поддерживать

Также в ветке электронной почты было отмечено, что это сделает подобные вещи действительными:

A.B x = new A.B.B.B.B.B.B.B.B.B.B.B.B();

... но это уже (как отметил Тинистер) будет действительным, если B получен из A.

Вложенность + наследование = странность ...

13 голосов
/ 05 ноября 2008

Это не C #, а компилятор. Одной из задач компилятора является размещение класса в памяти, который представляет собой набор базовых типов данных, указателей, указателей функций и других классов.

Он не может создать макет для класса A, пока не узнает, что такое макет класса B. Он не может знать, что такое макет класса B, пока не закончит макет класса A. Круговая зависимость.

0 голосов
/ 29 августа 2016

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

Вместо:

public class MyClass<T1, T2, T3> :
   MyClass<T1, T2, T3>.Interface
where T1 : ...
where T2 : ... 
where T3 : ... {
   public interface Interface { Interface SomeMethod(); }

   Interface Interface.SomeMethod() {
      ...
   }
}

// compile error: Circular base class dependency

Сделайте что-то вроде этого:

public sealed class MyClassInterfaces<T1, T2, T3>
where T1 : ...
where T2 : ... 
where T3 : ... {
   public interface Interface { Interface SomeMethod(); }
}

sealed class MyClass<T1, T2, T3> :
   MyClassInterfaces<T1, T2, T3>.Interface
where T1 : ...
where T2 : ... 
where T3 : ... {
   MyClassInterfaces<T1, T2, T3>.Interface
   MyClassInterfaces<T1, T2, T3>.Interface.SomeMethod() {
      ...
   }
}

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

public abstract class MyClassInterfaces<T1, T2, T3>
where T1 : ...
where T2 : ... 
where T3 : ... {
   public interface Interface { Interface SomeMethod(); }
}

sealed class MyClass<T1, T2, T3> :
   MyClassInterfaces<T1, T2, T3>,
   MyClassInterfaces<T1, T2, T3>.Interface
where T1 : ...
where T2 : ... 
where T3 : ... {
   Interface Interface.SomeMethod() {
      ...
   }
}
0 голосов
/ 05 ноября 2008

Относительно вопросов о том, что я пытался сделать:

По сути, я хотел создать класс, который имел бы композиционные отношения с самим собой, но я не хотел, чтобы содержащийся объект содержал другие объекты и, следовательно, создавал цепочку со многими "A has-a A has-a Отношения "есть-есть" ... Поэтому в то время я думал о том, чтобы сделать что-то вроде этого:

public class A : A.AA
{
    public class AA
    {
        // All of the class's logic
    }

    private AA _containedObject;
}

Который в то время казался довольно гладким, но, оглядываясь назад, я не уверен ...

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

Однако в комментариях к сообщению в блоге Эрика Липперта он приводит примеры класса, реализующего вложенный интерфейс, а также класса, реализующего универсальный интерфейс с вложенным классом в качестве аргумента типа (который не компилируется, и он вызывает «ошибку» в текущем компиляторе). Оба этих примера касаются интерфейсов, поэтому мне было интересно, есть ли какие-то специальные правила для вложенных классов. И, кажется, есть.

0 голосов
/ 05 ноября 2008

Я думаю, что вложенность означает, что вложенный тип является частью определения типа вложенности. При таком толковании ограничение имеет смысл, поскольку в момент, когда компилятор достигает определения A, A.B еще не определено, и даже в конце A оно уже определено в терминах A.B.

0 голосов
/ 05 ноября 2008

Это не имеет смысла для меня ... Вы пытаетесь расширить то, что не существует !!! Класс B существует только в рамках класса A, и поэтому я думаю, что есть какое-то наследство.

...